String temp = “a”;
这种方式,会先去找字符串常量池里是否有保存了同样值的字符串对象,如果有,那么直接将该对象的地址赋值给temp,如果没有,那么就在字符串常量池里申请地址然后存放,temp变量保存的是常量池里的地址
//看起来像是两个对象,实则是同一个对象 String one = “a”; String two = “b”; sout(one == two);
第二种就是使用构造方法创建
构造方法可以根据字符串或者字符数组去建立
不过,这里与第一种不同的是,创建的字符串对象是保存在堆中的,而不是字符串常量池里面
String three = new String(“a”);
如果使用字符串作为构造参数去构造的话,仍然要用到字符串常量池(因为要在字符串常量池新创建或者里面就有,才可以使用),如果使用到的字符串参数不用,后面会被gc收集器回收。
这样设计的优点是,可以带来共享的效率
比较
字符串比较最好不要使用双等于,而是使用equal方法
双等于比较的只是引用地址,而equal方法是先比较引用地址,然后再比较里面的值
public boolean equals(Object anObject) { //先判断应用地址,如果相等,肯定是相同对象 if (this == anObject) { return true; } //如果应用地址不正确,再判断是否是String类型 if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; //遍历底层字符串底层的字符数组进行比较 //先判断底层字符数组长度是否相等,不相等也不用比了,肯定不对 if (n == anotherString.value.length){ char v1[] = value; char v2[] = anotherString.value; int i = 0; //进行遍历比较 while (n-- != 0) { //只要有一个比不上,就确认不相同 if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
底层
从上面的源码可以看到,底层是一个字符数组,所以,它可以存储多字节的字符。
前面提到过,字符里面存储的是代码单元,所以底层字符串数组存的就是多个代码单元,这样就可以去表示各种字符串了。
这里要提及String.length方法,返回的是字符数组的长度,也就是代码单元的长度,所以不一定是字的个数
常用构建字符串的构造器有StringBuffer与StringBuilder
AbstractStringBuilder
我们可以看到,无论是StringBuffer或者是StringBuilder都是继承了这个类,所我们先研究这个类
里面共有4个属性
- value:存储字符串的(字符串底层是字符数组,默认容量为16)
- count:记录字符数组的长度,这样做的好处就是让返回字符数组长度的时间复杂度为1,而不用去遍历数组
- MAX_ARRAY_SIZE:字符数组允许的最大长度,为( 2 31 − 8 2^{31}-8 231−8),从注释上就可以看到,这个变量并不是必要的,只不过是避免大数组会导致内存溢出(超出虚拟机的限制)。
下面就来看看append的源码
public AbstractStringBuilder append(String str) { //判断是否为空 if (str == null) //没有的话就调用这个方法,这个方法其实是返回一个NULL,然后value里面就存储了Null return appendNull(); //记录长度 int len = str.length(); //也是判断底层数组长度是否足够,不够就要进行扩容 ensureCapacityInternal(count + len); //调用字符串的getChars方法进行拼接 str.getChars(0, len, value, count); count += len; return this; } appendNull的实现 private AbstractStringBuilder appendNull() { //就很简单使用一个字符数组,存储null,然后返回 int c = count; //判断底层数组的长度是否足够,不够需要进行扩容 ensureCapacityInternal(c + 4); final char[] value = this.value; value[c++] = ‘n’; value[c++] = ‘u’; value[c++] = ‘l’; value[c++] = ‘l’; //记录此时底层字符数组长度 //至于这里为什么只用count = c //看看value[]里面的是什么 //再看看c是什么吧 count = c; return this; } ensureCapacityInternal的实现 private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code //minimumCapacity是最小需要的底层字符数组长度 if (minimumCapacity - value.length > 0) { //如果不够,进行复制 //不够的话,要按照规则进行扩容 //Arrays.copyof其实就是新建一个数组然后存放而已 value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } newCapacity的实现(扩容的规则) private int newCapacity(int minCapacity) { // overflow-conscious code //新的规则为,底层数组长度话为二进制然后左移一位,相当于扩展2倍 //然后再加2,所以新道德数组长度为原来的2倍加2 int newCapacity = (value.length << 1) + 2; //再判断新的长度是否大于旧的长度 if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } //如果新的长度不小于0(这里可能是因为超过了int类型的4个字节), //又不大于最大底层字符数组长度(这个变量防止内存溢出) //就返回新的长度 //如果大于最大底层数组长度,就调用hugeCapacity return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity)newCapacity; } hugeCapacity的实现:处理超大底层数组长度 private int hugeCapacity(int minCapacity) { //如果是newCapacity <= 0,代表int类型字节不够存储,内存溢出