🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
![](https://markdown-1258186581.cos.ap-shanghai.myqcloud.com/20190606104746.png) [参考地址](https://mp.weixin.qq.com/s?__biz=MzI2MzQzNzU5Mg==&mid=2247483762&idx=1&sn=e2f2a1530bc092ef78ffa3d5ed50f0e5&chksm=eabaad05ddcd241353a7045363efb1179fd9c55695aa17464bd8941a35a6adf74984da4e2442&token=1734300795&lang=zh_CN#rd) ### 知道 String 的常量池吗? String 对象时被写为 Final 的,是不可重写和继承的,在每次使用的时候都会重新在堆内存中创建一个新的对象。 但是比如我们在创建 ```java //这种效率是比较高的,因为在编译时期就被组装成了”abac"" String s = "ab"+"ac"; ``` 在编译的时候会自动拼接成 "abcd",所以在使用常量进行拼接的时候,使用 String 是比较方便和快速的。如果是有少量的变量,比如 ```java String a = "aaa"; String b = "bbb"; String c = "ccc"; String d = "ddd"; String e = "eee" + a + b + c + d; ``` 这样的话,就会多次创建 StringBuilder 对象来进行处理,还是建议使用 StringBuilder 来处理。 下面的两种情况比较特殊: ```java String s1 = "test"; String s2 = new String("test"); String s3 = "test"; String s4 = new String("test"); System.out.println(s1==s2); System.out.println(s1==s3); System.out.println(s1==s4); System.out.println(s2==s4); false true false false ``` 如果使用 == 进行比较的话, 上面的 s1 和 s3 是相等的,因为他们指向了相同的堆内存地址。 上面的 s2 和 s4 和其他的都不相等,因为他们指向了不同的堆内存地址。 涉及到了字符串常量池的概念,在创建 s1 的时候,把`"test"` 添加到了字符串常量池中去,创建 s3 的时候,直接从常量池里面拿。但是创建 s2 和 s4 的时候,都会重新创建一个新的对象。 但是可以使用 String 对象的 intern() 方法去从缓存池拿。 比如: ```java String s1 = "test"; String s2 = new String("test").intern(); String s3 = "test"; String s4 = new String("test"); System.out.println(s1==s2); System.out.println(s1==s3); System.out.println(s1==s4); System.out.println(s2==s4); true true false false ``` 可以看到最终输入的结果就变了。s1 和 s2 的堆内存地址是相同的了。 具有同样特性的还有 Integer 的缓存池,具体看下买呢 ### Integer 的缓存池 如果使用 Integer 创建的值在 -128 ~ 127 之间,会放到常量池中,但是如果是使用 new Integer(127),不会放到常量池中。 ~~~ java Integer a1 = -129; Integer a2 = -129; System.out.println(a1 == a2); Integer b1 = -128; Integer b2 = -128; System.out.println(b1 == b2); Integer c1 = 127; Integer c2 = 127; System.out.println(c1 == c2); Integer d1 = 128; Integer d2 = -128; System.out.println(d1 == d2); ~~~ 结果: ``` false true true false ``` 如果使用 new Integer(127) ~~~ Integer e1 = 127; Integer e2 = new Integer(127); System.out.println(e1 == e2); // 结果为 false Integer f1 = 127; Integer f2 = new Integer(127); System.out.println(f1 == f2.intValue());// 结果为true ~~~ 这个 String 常量池和 Integer 的常量池一定要注意下。 ### 讲一下 String 和 StringBuffer、StringBuilder 的源码 String 内部是一个被 Final 修饰过的 char 数组。所以是不可变的,是不可扩容的。 StringBuilder 和 StringBuffer 都是继承于 AbstractStringBuilder 的,AbstractStringBuilder 是一个抽象类,内部有一个数组: ```java abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count; AbstractStringBuilder() { } AbstractStringBuilder(int capacity) { value = new char[capacity]; } @Override public int length() { return count; } public int capacity() { return value.length; } } ``` 可见内部也是使用数组实现的,但是内部的数组没被 Final 修饰,所以是可变的。StringBuilder 和 StringBuffer 对象在创建的时候,都是调用 super(capacity); 初始化这个数组。 ```java @Override public StringBuilder append(String str) { super.append(str); return this; } public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } ``` 可以看到在调用 append 的时候,是调用的父类的 append 办法,在 append 方法的内部,会计算出要插入的字符串的长度,然后执行 ensureCapacityInternal(count + len); ,这个 count+ len 指的是插入新的字符串以后的长度。 ```java private void ensureCapacityInternal(int minimumCapacity) { // 需要的长度大于当前 char 数组的长度的时候,才会去扩容 if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } // 计算新数组的长度的 private int newCapacity(int minCapacity) { // 默认每次扩容是原来容量的 2 倍+2 int newCapacity = (value.length << 1) + 2; // 如果默认的小于需要的,那么就使用实际需要的容量 if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } // newCapacity 小于等于 0,或者 超过了 Integer 的最大值 -8 的话,直接扩充大Integer 的最大值。 // 不然就使用新的计算出来的长度扩容 return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; } ``` 最后调用 getChars 创建新的数组,并改变当前 values 数组的长度: ```java public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); } ``` 所以,并不会创建新的对象,而是不断的给数组扩容来实现的。效率上自然是高了不少。 ### String、StringBuilder、StringBuffer 分别对应什么样的场景? StringBuffer 内部的很多方法都是使用了 synchronized 关键字的,所以它是线程安全的, 1. String适用与少量字符串操作 2. StringBuilder适用单线程下在字符缓冲区下进行大量操作的情况 3. StringBuffer使用多线程下在字符缓冲区进行大量操作的情况