-
String
定义:
public final class String
implements java.io.Serializable, Comparable, CharSequence {
可以看出 String 是 final 类型的,表示该类不能被其他类继承,同时该类实现了三个接口:java.io.Serializable Comparable
属性:
private final char value[];
这是一个字符数组,并且是 final 类型,用于存储字符串内容。从 final 关键字可以看出,String 的内容一旦被初始化后,其不能被修改的。
所以对String的修改都是将引用指向了新的字符串。
构造方法:
String可以用byte[],String,StringBuffer,StringBuilder,char[]构造
其中用byte[]构造的时候,涉及到编码问题,编码用Charset指定。
其中还有一个构造方法要注意:
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
这个构造方法是把char[]的引用直接给String内部的char[],而其他构造方法则是new一个char[],值复制。这个是一个protected方法,所以不用担心安全问题。
其他方法:
String的其他方法在api文档直接查就可以了,这里我就不列举了。
由于String的特性,对String有修改的方法都是返回一个新的String对象。
编码问题
之前提到的String内部是用一个char[]来存储的。而char是unicode编码,是两个字节存一个字符。
但是用getbytes()
方法获得的byte[]的长度并不是char[]长度*2。原因是getbytes()
方法获得的byte[]并不是unicode编码。String类型的默认编码方式是和本地编码方式相关。编码方式也可以指定。
之所以提这个是因为我之前写的代码需要把固定长度的String转换为固定长度的byte[],但是GBK,UTF-8,UTF-16编码都是不等长编码,而iso8859-1编码不支持中文。而在charest的编码集里没有找到等长编码的unicode编码。
最后我用toCharArray()
获得String内部的char[]的副本,因为char是unicode编码,所以我把char[]转换成了char[]长度*2的byte[]数组。
String常量池与字符串拼接性能优化
String常量池
String常量池是一个初始为空的字符串池,它由类 String 私有地维护。
String s1="hanhan";
String s2="hanhan";
System.out.println(s1==s2);//true
当我们创建String对象采用字面量形式时,JVM首先会对这个字面量进行检查,如果常量池中存放有该字面量,则直接使用,否则创建新的对象并将其引用放入常量池中。
String s1="gh";
String s2=new String("gh");
s2.intern();
System.out.println(s1==s2);//false;
s2=s2.intern();
System.out.println(s1==s2);//true;
当调用 intern 方法时,如果池已经包含一个等于此 String
对象的字符串(equals(Object)
方法确定),则返回池中的字符串。否则,将此 String
对象添加到池中,并返回此 String
对象的引用。
常量池中存的是对对象的引用,存储于JVM的方法区中,而且引用的对象存储于堆中。
当常量池中的引用没有被任何变量引用时,就会被GC回收!
String常量池
String s1="a"+"b"+"c";
正常情况下,执行声明s1,代码会生成3个对象,即对象a、对象ab、对象abc,其中对象a和对象ab都是中间的临时变量,最后的对象abc才赋值给了s1。因此在使用字符串拼接的时候,拼接的数量越多,性能越低!
但是java编译器在编译的时候做了优化,在编译时新建一个对象StringBuilder来拼接,这样就避免了产生很多临时对象,从而提升了性能!
String s="";
for(int i=0;i
但是这个做法效率会很低。因为循环内,每次都在做字符串拼接,每次都在产生一个StringBuilder对象,造成内存的浪费!因此这种错误要尽量避免,稍做以下优化即可完美改造:
String s="";
StringBuilder sb=new StringBuilder();
for(int i=0;i
-
Stringbuffer与Stringbuilder
在线程安全上,StringBuilder
是线程不安全的,而StringBuffer
是线程安全的。因为StringBuffer
的大部分方法带有synchronized
修饰符,而StringBuilder
没有。
StringBuilder
:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer
:适用多线程下在字符缓冲区进行大量操作的情况
这两个类的大多数方法都是继承自父类AbstractStringBuilder
。或者做了少量的应用。所以要了解这两个类,应该去了解父类AbstractStringBuilder
。
/**
* 底层存储的字符数组
*/
char[] value;
/**
* 用于记录存的字符的数目
*/
int count;
从它的成员变量我们可以发现和ArrayList
的有些类似,事实上看过源码之后你会发现他们的底层机制是很类似的。
我们主要研究append()
方法,以append(String str)
为例
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
其实就是把str的内容复制添加到内部的char数组value的末尾。
关键是ensureCapacityInternal(count + len);
这行代码。它的作用是检查value的容量够不够,不够的话就扩充,这个和ArrayList
的扩充机制基本一致,看过源码后你会发现,连写法都差不多。
int newCapacity = value.length * 2 + 2;
新的容量是旧容量的2倍+2,默认容量是16。