![](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使用多线程下在字符缓冲区进行大量操作的情况
- Java 面试题
- String、StringBuffer、StringBuilder 的区别?
- Java 中的四种引用
- 接口和抽象类的本质区别
- 集合框架
- 集合概述
- ArrayList 源码分析
- LinkedList 源码分析
- HashMap 源码分析
- LinkedHashMap 源码分析
- Android提供的 LruCache 的分析
- LinkedList 和 ArrayList 的区别
- 多线程
- 实现多线程的几种方式
- 线程的几种状态
- Thread 的 start() 和 run() 的区别
- sleep() 、yield() 和 wait() 的区别 ?
- notify() 和 notifyAll() 的区别?
- 保证线程安全的方式有哪几种?
- Synchronized 关键字
- volatile 和 synchronized 的区别?
- 如何正确的终止一个线程?
- ThreadLocal 原理分析
- 线程池
- 多线程的三个特征
- 五种线程池,四种拒绝策略,三种阻塞队列
- 给定三个线程如何顺序执行完以后在主线程拿到执行结果
- Java 内存模型
- 判定可回收对象算法
- equals 与 == 操作符
- 类加载机制
- 类加载简单例子
- 算法
- 时间、空间复杂度
- 冒泡排序
- 快速排序
- 链表反转
- IO
- 泛型
- Kolin 面试题
- Android 面试题
- Handler 线程间通信
- Message、MessageQueue、Looper、Handler 的对象关系
- Handler 使用
- Handler 源码分析
- HandlerThread
- AsyncTask
- IntentService
- 三方框架
- Rxjava
- rxjava 操作符有哪些
- 如何解决 RxJava 内存泄漏
- Rxjava 线程切换原理
- map和 flatmap 的区别
- Databinding引起的 java方法大于 65535 的问题
- Glide
- Glide 的缓存原理
- Glide 是如何和生命周期绑定的?不同的Context 有什么区别?
- Glide 、Picasso 、的区别,优劣势,如何选择?
- Jetpack
- 源码分析
- EventBus
- EventBus 源码分析
- RxBus 替代 EventBus
- OkHttp
- OkHttp 源码分析
- OkHttp 缓存分析
- RxPermission
- RxPermission 源码分析
- Retrofit
- create
- Retrofit 源码分析
- 优化
- 启动优化
- 布局优化
- 绘制优化
- 内存优化
- 屏幕适配
- 组件
- Activity
- Frgment
- Service
- ContentProvider
- BroadcastReceiver
- 进程间通信
- Binder机制和AIDL
- AILD 中的接口和普通的接口有什么区别
- in、out、inout 的区别
- Binder 为什么只需要拷贝一次
- 在android中,请简述jni的调用过程
- 生命周期
- Activity 生命周期
- Fragment 生命周期
- Service 生命周期
- onSaveInstanceState() 与 onRestoreIntanceState()
- 前沿技术
- 组件化
- 模块化
- 插件化
- 热更新
- UI - View
- Android 动画
- 事件分发机制
- WebView
- 系统相关
- 谈谈对 Context 的理解
- Android 版本
- App应用启动流程
- App 的打包
- App 的加固
- App 的安装
- Activity 启动流程
- ClassLoader
- Lru 算法加载 Bitmap 三级缓存原理
- Parcelable 和 Serializable 的区别
- Activity的启动流程
- 相关概念
- 网络相关
- Http
- Https
- Http 和 Https 的区别
- 为什么要进行三次握手和四次挥手?
- OkHttp使用Https访问服务器时信任所有证书
- 设计模式
- 单例模式
- 构建者模式
- 工厂模式
- 外观模式
- 代理模式