- 字符串是啥?字符串常量池是啥?
先讲一下字符串,在Java中字符串对象就是一个普通对象,如下图所示,String类用final修饰,表示不可继承,有一个私有成员变量char数组,也用final修饰,表示不可修改。
所以String对象一旦被创建就是固定不变的了,提供的任何change方法都会生成新的对象。
正因为String对象不可修改,并且和其他对象一样创建和销毁的成本很高,所以出现的字符串常量池,常量池中的字符串都是唯一的。
- StringBuilder? StringBuffer? 加号+?
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
其中StringBuilder中最主要的两个方法append()和toString()方法源码详细介绍一下
append()方法
//char数组
char[] value;
//数组长度
int count;
public StringBuilder append(String str) {
super.append(str);
return this;
}
public AbstractStringBuilder append(String str) {
//获取字符串长度
int len = str.length();
//调用Arrays.copyOf方法将原先的char数组拷贝一份至新数组,扩容至count + len
ensureCapacityInternal(count + len);
//然后调用String的getChars将str字符串的char数组添加到value数组后面
str.getChars(0, len, value, count);
//长度加len
count += len;
return this;
}
//生成长度为minimumCapacity的新char数组
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
//String的方法
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
toString()方法
public String toString() {
//根据char数组value生成一个String对象
return new String(value, 0, count);
}
加号
然后讲一下这个+号,都说在编译期,编译器会帮我们生成一个StringBuilder对象,使用+拼凑字符的过程,将转换成调用StringBuilder的append( )方法进行拼凑,但实际上还是有区别的,编译器只能将有+号操作转换为StringBilder对象的append方法,但每个+操作后,又调用了StringBuilder的toString()方法,正如上面贴的toString()方法源码,调用会将char数组生成一个String对象。所以每一次+号操作都会生成一个字符串对象
。
循环进行new StringBuilder()-> append()->toString()->new String()
- 字符串常量池在哪?
说到字符串常量池在哪,需要先说一下方法区的概念。
方法区是 JVM 规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,并没有规定如何去实现它,不同的厂商有不同的实现。Hotspot对于方法区的落地实现在JDK1.8之前称之为永久代,之后改为了元空间。其和堆一样也是线程共享的。
永久代,顾名思义永远存在,跟常量池的常量一个概念, 所以在JDK1.7之前字符串常量池是放在方法区的,但从JDK1.7开始将静态变量和字符串常量池从方法区移到了堆中。
我们后面讲到的字符串常量池,都将围绕在JDK1.7之后移到了堆中展开。
- 字符串常量池案例
- 字符串常量池总结
根据上面的案例分析,一共有4点总结
- 字符串常量池中有两种情况:引用(指针) 或 常量。
- 用""(双引号)创建的字符串对象,如果常量池中不存在,会生成对象放入常量池。
- intern()方法,如果常量池中不存在,会指向堆中的对象。
- 堆中可以有任意个相同的字符串,常量池只能有一个。
- 其他常量池
顺便讲一下Java中经常被一起提到的另外两种常量池
-
class常量池
编译时生成。
我们熟知的javac后生成的字节码文件(.class文件)中包含类的版本、字段、方法、接口等描述信息和常量池(constant pool table),用于存放编译器生成的各种 字面量 (Literal)和 符号引用 (Symbolic References)
字面量 :1.八种基本类型的值 2.被声明为final的常量
符号引用 :1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。(描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值)。详情可以看专门讲字节码文件的博客:juejin.cn/post/697800…
-
运行时常量池
运行时生成。
当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。