字符串变量
定义一个字符串变量并初始化非常简单
String s = "hello world";
这种方式称为 字符串字面量(String Literal) 创建。
但是要 new 一个String出来就没有这么简单
可以看到有很多重载方法,这里介绍几个最经使用的
String s1 = new String();
String s2 = new String("hello world"); // hello world
byte[] bytes = {104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100};
String s3 = new String(bytes); // hello world
在jdk提供的类中,很多字符操作的底层都是通过字节数组来实现的,本质上这些数字对应这ASCII码表的字符。
温馨提示:当你你要的字符需要转义时,你可以直接通过字节数组构造。byte是一个字节,存储数的范围是-128~127。
s2保存的是什么?
这是创建s2的内存简单示意图,s2实际上是保存对象的引用,也就是在堆区的地址。在调用toString方法的时候底层经过了处理,返回的不是地址。
字符串内存
在此之前我们来了解一下字符串常量池(String Table),我们知道String类是字符不可变的,当改变字符其实就是创建了新的String对象。常量池的存在就是为了提高字符串的利用效率。
本篇文章并不详细探讨String Table的实现,事实上String Table在jdk1.8不是由java实现的
什么时候会把字符串对象放入常量池?
可以通过一道简单的面试题来揭露一角
题目:String a = new String("1")
创建了多少个对象
答:创建了一个或两个对象,运行这个语句首先会在堆中创建新的字符串对象;另外,如果该字符串对象的值在常量池中不存在的话,还会在常量池中创建一个新的字符串对象(已有则不用创建)。
我们来看一下最简单的例子
String s = new String("123");
String s1 = "123";
String s2 = "123";
System.out.println(s1 == s); // true
System.out.println(s1 == s2); // false
- 在堆区创建新的字符串对象,同时常量池为空,创建新的字符串对象
- 通过字符串字面量方式创建,先在常量池寻找是否存在值相等的对象,有则返回地址
s2
同理,保存的是常量池值相等对象的地址
内存示意图:
当我们需要判断字符串内容是否相等请使用equal
和equalsIgnoreCase
字符串的拼接底层原理
- 字符串字面量相拼接
String s = "abc123";
String s1 = "abc" + "123";
System.out.println(s == s1); // true
为什么是true,不妨打开编译后的class文件
可以看到class文件直接就是“abc123”,因为jvm对其进行了优化,s和是拿到的都是常量池的“abc123”,所以相等
- 变量参与拼接
String s1 = "abc";
String s2 = "123";
String s3 = s1 + "123";
String s4 = s1 + s2;
通过javap -c
命令反编译来查看
可以看到只要有变量参与,都使用了makeConcatWithConstants
来进行优化。
在jdk8之前是使用StringBuilder(或StringBuffer)
来做的,开销比较大,感兴趣的可以去看看。
回到makeConcatWithConstants
,我们来大致了解一下它的工作流程。首先Ctrl + N
找到这个方法,在java.lang.invoke
包下
- lookup:MethodHandles.Lookup 对象,用于在运行时查找和操作方法。
- name:字符串,可能是方法的名称。
- concatType:MethodType,表示拼接操作的方法类型。
- recipe:字符串,可能是用于指定拼接规则或配方的参数。
- constants:对象数组,可能包含用于拼接的常量。
本质上会创建一个数组进行字符串拼接
如果大家感兴趣我就再扒一扒
makeConcatWithConstants
的原理~