字符串
String声明为final,不可被继承的。
在Java8中,String内部使用char数组进行存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
在Java9之后,String类的实现改用byte数组存储字符串,同时使用coder来标识使用了哪种编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
value数组被声明为final,这意味着value数组初始化后就不能再引用其它数组。并且String内部没有改变value数组的方法,因此可以保证不可变。
String的不可变性
好处
-
可以缓存hash值
因为String的hash值经常被使用。
例如:String做HashMap的key。不可变的特性可以使得hash值也不可变,因此只需要进行一次计算。
-
String Pool的需要
如果一个String对象已经被创建过了,那么就会从String Pool中取得引用。只有String是不可变的,才可能使用String Pool。
-
安全性
String经常作为参数,String不可变性可以保证参数不可变。
例如:在作为网络连接参数的情况下如果String是可变的,那么在网络连接过程中,String被改变,改变String的那一方以为现在连接的是其它主机,而实际情况却不一定是。
-
线程安全
String不可变性具备线程安全,可以在多个线程中安全地使用。
String,StringBuffer and StringBuilder
-
可变性
- String不可变
- StringBuffer和StringBuilder可变
-
线程安全
- String不可变,因此是线程安全的
- StringBuilder不是线程安全的,效率高
- StringBuffer是线程安全的,内部使用synchronized进行同步,效率低
-
内存解析
StringBuffer sb = new StringBuffer();//new char[16];底层创建了一个长度是16的数组
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
扩容问题:如果要添加的数据底层char数组装不下,那就需要进行扩容。
- 默认情况下,扩容为原来容量的2倍+2,同时将原有的数组重点元素复制到新的数组中。
- 指导意义,开发中建议使用StringBuffer(int capacity)或StringBuilder(int capacity)。
String Pool
字符串常量池保存着字符串字面量,这些字面量在编译时期就确定。还可以使用String的intern()方法在与逆行过程将字符串添加到String Pool中。
当调用intern()方法时,如果String Pool中已经存在一个字符串和该字符串值相等(equal()方法确定),那么就会返回String Pool中字符串的引用;否则就会在String Pool中添加一个新的字符串,并返回这个字符串的引用。
代码示例:
其中s1和s2采用new的方式创建了两个字符串。s1和s2会在堆中创建两个地址,两个地址指向同一个常量池中对象。
s3和s4采用intern()方法,返回的是同一个常量池中的地址。
s5和s6这种字面量的形式创建的字符串,会自动地将字符串放入常量池中。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4); // true
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
在Java6时,String Pool在方法区(永久区)
在Java7时,String Pool在堆空间,因为永久区的空间有限
在Java8时,String Pool在方法区(元空间)
new String(“abc”)
这种方式会创建两个对象
- "abc"属于字面量,因此会在编译期会在String Pool中创建一个字符串对象。
- 使用new的方式会在堆中创建一个对象,该对象指向String Pool中的“abc”。
类型转换
-
与基本数据类型、包装类的转换
String —>> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
String s1 = "123"; int num = Integer.parseInt(s1);
基本数据类型、包装类 —>> String:调用String重载的valueOf(xxx)
int num = 123; String s2 = String.valueOf(num);
-
与字符数组之间的转换
String —>> char[]:调用String的toCharArray()
String s1 = "abc123"; char[] charArray = s1.toCharArray();
char[] —>> String:调用String构造器
char[] arr = new char[]{'h', 'e', 'l', 'l', 'o'}; String str = new String(arr);
-
与字节数组之间的转换
String —>> byte[]:调用String的getBytes()
又称为编码
String s1 = "abc123中国"; byte[] bytes1 = s1.getBytes();//使用默认的字符集UTF-8,进行转换 byte[] bytes2 = s1.getBytes("gbk");//指定字符集标准gbk
byte[] —>> String
又称为解码
String s3 = new String(bytes1);//使用默认的字符集UTF-8,进行解码 String s4 = new String(bytes2);//编码(gbk)与解码(UTF-8)不同,出现乱码 String s5 = new String(bytes2, "gbk");//指定解码方式(gbk)