String类源码学习
一、初始String类:
- String类是final类型的,表示该类不能被继承。如下图所示:
- 这是一个字符数组,且是final的,用于存储字符串的内容。
本质:String是用char[ ]实现的。/** The value is used for character storage. */ private final char value[];
<blockquote><pre> * String str = "abc"; * </pre></blockquote><p> * is equivalent to: * <blockquote><pre> * char data[] = {'a', 'b', 'c'}; * String str = new String(data);
- 因为String实现了Serializable接口,所以支持序列化和反序列化。Java的序列化机制是通过在运行时判断类的
serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体(类)的serialVersionUID
进行比较,如果相同就认为是一致的,可以反序列化,否则会出现序列化版本不一致的异常(InvalidCastException)。/** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a> */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
二、构造方法:
String的底层是由char[ ]实现的:通过一个char[]类型的value属性。
- 使用字符数组、字符串构造一个String
/** * Allocates a new {@code String} so that it represents the sequence of * characters currently contained in the character array argument. The * contents of the character array are copied; subsequent modification of * the character array does not affect the newly created string. * * @param value * The initial value of the string */ public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
- 使用字节数组构造一个String:
public String(byte bytes[], Charset charset) { this(bytes, 0, bytes.length, charset); }
- 使用StringBuffer和StringBuilder 构造一个String
/** * Allocates a new string that contains the sequence of characters * currently contained in the string buffer argument. The contents of the * string buffer are copied; subsequent modification of the string buffer * does not affect the newly created string. * * @param buffer * A {@code StringBuffer} */ public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } }
/** * Allocates a new string that contains the sequence of characters * currently contained in the string builder argument. The contents of the * string builder are copied; subsequent modification of the string builder * does not affect the newly created string. * * <p> This constructor is provided to ease migration to {@code * StringBuilder}. Obtaining a string from a string builder via the {@code * toString} method is likely to run faster and is generally preferred. * * @param builder * A {@code StringBuilder} * * @since 1.5 */ public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }
三、String 中常用的方法:
/*String a = "abc";//放在常量池里面
String b = "abc";
String c = new String("abc");//在对内存开辟空间
System.out.println(a == b);//true
System.out.println(a == c);//false
*/
//测试String_StringBuilder_StringBuffer速度:
long start = System.currentTimeMillis();
//String s ="abc" + "de";// 等同于 String s ="abcde";
String s1 = "abc";
s1 += "de";
//StringBuffer sb = new StringBuffer().append("abc").append("de");
//StringBuilder sdr = new StringBuilder().append("abc").append("de");
long end = System.currentTimeMillis();
System.out.println(end - start) ;
//String类中常用的方法:
String str = new String(" wuxia is Perfect....");
System.out.println(str.length());//1.返回字符串的长度
System.out.println(str.isEmpty());//2.判断是否为空
System.out.println(str.substring(1, 4));//3.左闭右开,返回子串
System.out.println(str.charAt(2));//4.返回下标为2的字符
System.out.println(str.toUpperCase());//5.转大写
System.out.println(str.toLowerCase());//6.转小写
System.out.println(str.trim());//7.去掉两端空格,注意中间的没有去掉
char[] cha = str.toCharArray();//8.转为 字符数组 char[]
System.out.print("转为数组:[");
for(int i = 0;i < cha.length ; i ++) {
if(i == cha.length - 1) {
System.out.print(cha[i]+"]");
}else {
System.out.print(cha[i] +",");
}
}
System.out.println();
System.out.println(str.concat("专业有可能骗人,但是认知和格局是不会骗人的!"));//9.拼接字符串
System.out.println(str.replace("...", "!!!"));//10.替换字符串
System.out.println(str.contains("w"));//11.判断是否包含某个字符串
System.out.println(str.matches("."));//12.是否匹配给定的正则表达式
String[] strs = str.split(" ");//13.按照字符分成分割,返回String数组
System.out.print("转为数组:[");
for(int i = 0;i < strs.length ; i ++) {
if(i == strs.length - 1) {
System.out.print(strs[i]+"]");
}else {
System.out.print(strs[i] +"|");
}
}
System.out.println();
String str1 = "ABC";
byte[] bytes = str1.getBytes();//14.将字符串转成字节数组
for(int i = 0;i < bytes.length ; i ++) {
if(i == bytes.length - 1) {
System.out.print(bytes[i]+"]");
}else {
System.out.print(bytes[i] +"|");
}
}
System.out.println();
System.out.println(str1.equals("你好,吴霞"));//15.
System.out.println(str1.contentEquals(new StringBuffer("你好,吴霞")));
System.out.println(str1.equalsIgnoreCase("abc"));
System.out.println(str1.compareTo("abc"));
System.out.println(str1.compareToIgnoreCase("abc"));
System.out.println(str1.regionMatches(1, "c", 1, 1));
System.out.println(str1.substring(1));//重新new 了 一个String 避免了内存泄露,但牺牲了性能
System.out.println(str1.replaceFirst("A", "吴霞"));
System.out.println(str1.replaceAll("AB", "你好"));
char[] c = {'w','c'};
//valueOf
System.out.println(str.valueOf("qqq"));
System.out.println(str.copyValueOf(c));//new String(data);
//intern()方法
/**
When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.*/
String s = new String("intern");
System.out.println(s.intern());//重要,当该方法被调用时,如果对象池包含这个字符串,则返回
//对象池的实例,否则添加字符串到对象池并返回该字符串的引用
String s2 = new String("java1");
String s3 = new String("吴霞");
String s4 = "吴霞";
System.out.println("java1" == s2);//false
System.out.println("java1" == s2.intern());//true 思考:为什么?
System.out.println(s3 == s3.intern());//false 思考:为什么? s3是堆上的引用,s3.intern()是从常量池返回的
System.out.println(s3.intern() == s3);//false 思考:为什么?
System.out.println(s4 == s3.intern());//true 思考:为什么?
2.重载运算符:
//String 对 "+"的重载 ,"+"是java 唯一一个重载运算符
//反编译后:
String a = "abc";
String a1 =a + "def";
System.out.println(a1);
int i = 5;
String i1 = "" + i;//相当于 String i1 =(new StringBuilder()).append(i).toString();
String i2 = String.valueOf(i);//调的是 Integer.toString(i); toString(i) 是new 的一个新对象
String i3 = Integer.toString(i);
System.out.println( i2 == i1);//false
System.out.println( i2 == i3);//false
字符串常量池:
思考:
①字符串常量池的设计意图是什么?
②字符串常量池在哪里?
③如何操作字符串常量池?
字符串常量池的设计思想:
a.字符串的分配,和其他对象分配一样,耗费高昂的时间和空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度影响程序的性能
b.JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:
- 为字符串开辟了一个字符串常量池,类似缓冲区;
- 创建字符串常量时,先检查字符串常量池是否存在该字符串;
- 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
c.实现的基础:
- 实现该优化的基础是因为字符串是不可变的,不用担心数据冲突进行共享
- 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,即它们一直引用着字符串常量池中的对象,故,在常量池中的这些字符串不会被垃圾收集器回收
字符串常量池在哪里:
堆:
- 存储的是对象,每个对象都包含一个与之对应的class
- JVM只有一个人堆区,被所有线程共享;堆中只存放对象本身,不存放基本类型和对象引用
- 对象由垃圾回收器负责回收
栈:
- 每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用
- 栈中数据(原始类型和对象引用)都是私有的
元空间:
字符串常量池存在于元空间
操作字符串常量池的方式:
String.intern()
字面量和常量池初探:
字符串对象内部是用字符数组存储的
思考:
String m = “Hello,world”;
String n = “Hello,world”;
String u = new String(m);
String v = new String(“Hello,world”);
①会分配一个11长度的char数据,并在常量池分配一个由这个char数组组成的字符串,然后m去引用这个字符串
②用n去引用常量池里边的字符串,即n和m是同一个对象
③生成一个新的字符串,但内部的字符数组引用着m内部的字符数组
④生成一个新的字符串,但内部的字符数组引用常量池中的字符串内部的字符数组,和u是同样的字符数组
如下图: