String基本特性
String : 字符串,使用一对""引起来表示。
String声明为final的。,不可被继承
String 实现了Serializeable接口:表示字符串支持序列化的。实现了Comparable 接口:表示String可以比较大小
String在jdk8 及以前内部定义了final char[] value 用于存储字符串数据。jdk9 时改为byte[]
String存储结构变更
String使用byte[]加上编码标记,节约了内存空间
StringBuffer 和StringBuilder 都也有相应的改动。
String的基本特性
String代表不可变的字符序列,即不可变性
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连续操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
- 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
字符串常量池中是不会存储相同内容的字符串。
String的String Pool 是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool 的String非常多,就会造成Hash冲突,从而导致链表会很长,而链表长了后会造成的影响就是当调用String.intern时性能会大幅下降。
使用-XX:StringTableSize 可设置StringTable的长度
在jdk6中StringTable是固定的,就是1009 的长度,所以如果常量池中的字符串过多就会导致效率下降地很快。StringTableSize设置没有要求
在jdk7中,StringTable的长度默认值是60012 ,jdk8开始,1009是可以设置的最小值。
String 的内存分配
Java6及之前,字符串常量池存放在永久代。
Java7 中Oracle的工程师对字符串池的逻辑做了很大的变化,即将字符串常量池的位置调整到Java堆内。
- 所有的字符串都保存在堆(Heap)中,就像其他普通对象一样,这样可以让你在进行调优应用时仅需调整堆大小就可以了。
- 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在java7 中使用String.intern()。
Java8 元空间,字符串常量在堆。
String的基本操作
Java语言规范里要求完全相同的字符串字面量,应该包含同样的Unicode字符序列(包含同一份码点序列的常量),并且必须是指向同一个String类实例。
字符串拼接操作
- 常量与常量的拼接结果是在常量池,原理是编译期优化
- 常量池中不会存在相同内容的常量
- 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
- 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象地址。
@Test
public void test3(){
String s1 = "a";
String s2 = "b";
String s3 = "ab";
/*
如下的s1 + s2 的执行细节:(变量s是我临时定义的)
① StringBuilder s = new StringBuilder();
② s.append("a")
③ s.append("b")
④ s.toString() --> 约等于 new String("ab")
补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
*/
/*
1. 字符串拼接操作不一定使用的是StringBuilder!
如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
*/
/**
* 如何保证变量s指向的是字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s = "shkstart";//字面量定义的方式
* 方式二: 调用intern()
* String s = new String("shkstart").intern();
* String s = new StringBuilder("shkstart").toString().intern();
*
*/
public class StringIntern {
public static void main(String[] args) {
String s = new String("1");
s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
String s2 = "1";
System.out.println(s == s2);//jdk6:false jdk7/8:false
String s3 = new String("1") + new String("1");//s3变量记录的地址为:new String("11")
//执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
s3.intern();//在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址。
// jdk7:此时常量中并没有创建"11",而是创建一个指向堆空间中new String("11")的地址
String s4 = "11";//s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
System.out.println(s3 == s4);//jdk6:false jdk7/8:true
}
}
public class StringIntern1 {
public static void main(String[] args) {
//StringIntern.java中练习的拓展:
String s3 = new String("1") + new String("1");//new String("11")
//执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
String s4 = "11";//在字符串常量池中生成对象"11"
String s5 = s3.intern();
System.out.println(s3 == s4);//false
System.out.println(s5 == s4);//true
}
}
总结String 的intern()的使用
Jdk1.6中,将这个字符串对象尝试放入串池
- 如果串池中有,则并不会放入,返回已有的串池中的对象的对象地址
- 如果没有,会把此对象赋值一份,放入串池,并返回串池中的对象地址
jdk1.7起,将这个字符串对象地址尝试放入串池
- 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
- 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中 的引用地址。
参考资料:宋红康JVM教程
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。