具体哪些String是相等的,各种String的情况,看这个:
https://javaguide.cn/java/basis/java-basic-questions-02.html#string-%E4%B8%BA%E4%BB%80%E4%B9%88%E6%98%AF%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%9A%84
String的基本特性
- String:字符串,使用一对“”引起来表示
- 声明方式和基础类型类似:String str = “abc”;
- 也可以:String str = new String(“hello”);
- String声明为final的,不可被继承
- String实现了Serializable接口:表示字符串是支持序列化的
- 实现了Comparable接口:表示String可以比较大小
- String 在Java8时,内部使用final char[] value来存储字符串数据。Java9改成了byte[]加编码标记哪种正在使用哪种字符编码,节省了一些空间
- 原因是设计者们发现String里面大部分是拉丁字符,而拉丁字符只占一个字节,浪费了一半的空间
- 和String有关的类,比如StringBuilder,StringBuffer都做出了相应的改变
- String的不可变性
- 当对字符串重新赋值时,会新建一个字面量,然后把新的字面量的地址赋值给String变量
- 当对现有的字符串进行连接操作时,也是创建新对象,,,
- 当调用String的replace方法修改指定字符或字符串时,也是返回一个新对象,,,
- 字符串常量
- 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
- 字符串常量池是不会存储相同内存的字符串的【Java语言规范里要求完全相同的字符串字面量应该包含同样的Unicode字符序列(包含同一份码点序列的常量),并且必须是指向同一个String类实例】
- 字符串常量池是一个固定大小的HashTable(数组+链表),默认大小长度为1009。如果放进常量池的String非常多,就会造成Hash冲突严重,导致链表会很长,链表长了之后调用String.intern时的性能会大幅下降
- -XX:StringTableSize 可设置StringTable的长度
- jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTableSize设置没有要求
- jdk7中StringTable的长度默认值是60013
- jdk8中默认值一样,但是设置StringTable的长度时,1009是可设置的最小值
String的内存分配
- 在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念
- 常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的。String的常量池比较特殊。它的主要使用方法有两种
- 直接使用双引号声明出来的String对象会直接储存在常量池中
- 比如 String info = “atguigu.com”;
- 如果不是用双引号声明的String对象,可以使用String提供的intern()方法
- 直接使用双引号声明出来的String对象会直接储存在常量池中
- 变化
- Java 6及以前,字符串常量池存放在永久代
- Java 7中Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆中
- 所有的字符串都保存在堆中,和其他普通对象一样,这样可以让你在进行调优应用使仅需要调整堆大小就可以了
- 字符串常量池原先使用得比较少(因为怕永久代的OOM),现在常量池放进堆里面,就可以重新考虑在Java 7中使用String.intern
- Java 8元空间,字符串常量仍然在堆中
- 为什么字符串常量池要移入堆中?
- 1.因为永久代比较小,字符串一多就容易OOM
- 2.永久代很少GC,大量的空间被占用,不能及时清理
字符串拼接操作
- 1.常量与常量的拼接结果在常量池,原理是编译期优化
- 2.常量池中不会存在相同内容的常量
- 3.只要其中有一个是变量【注意是变量,加了final就是常量引用,是常量相加,不是变量相��】,则相当于在堆中new String() (新建了一个对象),它的值为拼接的结果。变量的拼接原理是StringBuilder
- 所以,类,方法,变量等,能用final修饰的尽量用
- 4.如果拼接的结果调用intern()方法,如果常量池没有和这个拼接结果一样的字符串,常量池就新建个对象,并返回此对象地址。如果有了就直接返回那个字符串的地址
- 拼接之后不调用 intern()方法 常量池是没有这个对象的
- 使用StringBuilder拼接字符串比直接拼接字符串好
- 1.使用StringBuilder拼接比直接拼接快很多倍。直接拼接,会创建过多的StringBuilder和String对象。而使用StringBuilder拼接,自始至终只产生一个StringBuilder对象
- 2.直接拼接,创建过多对象,给GC带来压力
- 改进:如果确定拼接之后的字符串长度不会超过某个最大值,可以用StringBuilder构造器指定大小,防止StringBuilder频繁扩容
- StringBuilder s = new StringBuilder(high level);
intern()的使用
- 如果不是用双引号声明的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
- 比如:String myInfo = new String(“I love atguigu”).intern();
- 也就是说,如果在任意字符串上调用String.intern方法,其返回结果指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定为true:
- (“a”+“b”+“c”).intern() == “abc”
- 通俗点讲,Interned String 就是确保字符串在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)
-
new String(“a”) 会产生几个对象?2个
- 一个是new 的 String对象
- 一个是在常量池产生的常量 “a”(前提是常量池之前没有"a")
-
new String(“a”) + new String(“b”)会产生几个对象? 6个
- 一个是StringBuilder因为是变量和变量相加,不是常量+常量
- 一个是 new 的String
- 一个是在常量池产生的常量"a"
- 一个是new 的String
- 一个是在常量池产生的常量"b"
- 还有一个!StringBuilder最后还会调用toString()方法,toString方法会new 一个String()对象。
- 但是!!!在字节码文件看toString的指令,里面没有在常量池创建常量,就是这里的代码,常量池不会多一个"ab"
-
一个很难的面试题
- Java7中,将常量池放入了堆中。为了节省空间,它在常量池里不是新建一个常量"11",而是新建一个指针 指向"11",所以s3和s4一样
- 而在其他情况下,一般都是直接赋值 “”比如 String s = “11”,或者 String s = new String(“11”) 这两个过程都是会在常量池里生成常量的
- String s3 = new String(“1”) +new String(“1”) 这种生成了String对象,但是没有在常量池里生成常量的情况,真的很特殊
- **也只有在这种情况下使用 s3.intern()方法会使用 现有String的对象,而不是自己创建一个【其他情况调用****intern()**还是会直接新建常量的】
- 拓展
- 总结String的intern()方法的使用:
- jdk1.6中,将这个字符串对象尝试放入常量池
- 如果常量池有,则不会放入。返回已有的常量池中的对象的地址
- 如果没有,就会将此对象复制一份(就是新建一个值一样的对象),放入常量池,并返回常量池中的对象地址
- jdk1.7中,将这个字符串对象尝试放入常量池
- 如果常量池有,则不会放入。返回已有的常量池中的对象的地址
- 如果没有,则会把对象的引用地址复制一份,放入常量池,并返回常量池中的引用地址
- jdk1.6中,将这个字符串对象尝试放入常量池
- 当有大量字符串循环赋值的时候,使用intern()可以大大节省空间和加快速度
G1对堆中String对象的去重(常量池都是唯一的,不用去重)
- Java7中,将常量池放入了堆中。为了节省空间,它在常量池里不是新建一个常量"11",而是新建一个指针 指向"11",所以s3和s4一样
-
感觉应该挺好用,不知道为什么不默认开启