String
String的基本特性
- String: 字符串,使用一对 “”"引起来
- string s1 = “” //字面量定义方式
- String s2 = new String(); // new一个方法
- String不可被继承
- 在jdk1.8版本之前 是char型数组
- 在jdk1.9改为了byte
- 动机
- 一个char占两个字节
- 又发现我们堆空间的大部分组成都是string, 而都是一个byte就可以存储了, 如果字符集是ISO用一个, 如果是utf-8就还是2个字节
- String 代表不可变的字符序列, 不可变性;
public void test2() {
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
//只要在赋值的时候右边有变量就相当于在堆中new一个
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s3 == s6); //false
System.out.println(s3 == s7); //false
System.out.println(s5 == s6); //false
System.out.println(s5 == s7); //false
System.out.println(s6 == s7); //false
s7 = "javaEEhadoop";
System.out.println(s3 == s7); //true
String s8 = s5.intern(); //返回值的得到的 s8使用的常量值中已经存在的“javaEEhadoop"
System.out.println(s3 == s8); //true
final String s9 = "hadoop";
String s10 ="javaEE"+ s9;
System.out.println(s10 == s3); //true
}
@Test
public void StringC() {
String s1 = new String("abc");
String s2 = "abc";
String s3 = "abc";
System.out.println(s1 == s2); //false
System.out.println(s2 == s3); //true
Person p1 = new Person("Tom", 18);
Person p2 = new Person("Tom", 18);
System.out.println(p1.name == p2.name);//true;
}
- 字符串池是不会存储相同的字符串
- 字符串常量池底层就是个StringTable就是个map(数组加链表) jdk7之后长度默认60013, jdk81009是最小值, jdk6之前是默认1009.
String的内存分配
- 在java语言中有8种基本数据类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念
- 常量池就是类一个java系统级别提供的缓存, 8中数据类型的常量池都是系统协调的
- String类型的常量池比较特殊。它的主要使用方法由两种
- 直接使用双引号 声明出来的String对象会直接存储在常量池中
- 如果不是用双引号声明的String对象, 可以使用String提供的intern方法
- StringTable为什么要调整?
① permSize默认比较小
②永久代垃圾回收频率低 - 什么叫相同的字符串
字符串拼接
- 常量与常量的拼接结果放在常量池, 原理使编译器优化
- 常量池中不会存在相同内容的常量
- 只要其中有一个是变量, 结果就在堆中, 变量拼接的原理是Stringbuilder
- 如果拼接的结果调用intern()方法, 则主动将常量池中还没有的字符串放入池中, 并返回此对象的地址
public class StringTest {
public static void main(String[] args) {
// test2();
test3();
}
public void test1() {
String s1 = "a" + "b" + "c"; // 编译就是再常量常量池就直接放一个abc
String s2 = "abc";
/*
* 最终java编译成.class, 再执行
* String s1 = "abc"
* String s2 = "abc"
* */
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
}
public static void test2 () {
/*
* 如果拼接的符号的前后出现了变量, 则需要再堆空间中new一个new String() 内容是
* */
String s1 = "a";
String s2 = "b";
String s3 = "ab";
/*
*
* 如下的s1 s2的执行细节
* StringBuilder s = new StringBuilder;
* s.append("a")
* s.append("b")
* 调用。toString ---> 类似于new String("abc");
*
* */
String s4 = s1 + s2 ;
System.out.println(s3 == s4); //false
}
public static void test3() {
/*
* 字符串拼接操作不一定使用的是StringBuilder
* 如果拼接的左右两边都是字符串常量或者常量引用仍然使用编译器优化, 非
*
* 针对final又是类、方法、基本数据类型、引用数据类型的量的结构是, 建议用上
*
* 每次 string + 跟利用StringBuilder的效率
*
* StringBuilder的append() 方式添加字符串的效率远高于string的字符串的拼接方式
* ① StringBuilder的append() 方式自始自终只创建一个stringBuilder的对象
* 使用String的字符串拼接方式: 创建过多个StringBuilder和String的对象
* ② 使用string的字符串拼接方式: 内容中由于创建了较多的StringBuilder和String的对象内存占用更大, 如果达到Gc的话还得Gc
*
* 利用StringBuilder的改进的空间直接利用有参构造器来防止扩容。
*
* */
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4);//true
}
}
- intern()的使用
- 是一个本地方法, 当这个方法被调用的时候如果equals与常量池的字符串相等的, 就返回其地址否则添加到常量池。
- intern就是从常量池中拿如果没有就把这个放入, 有则返回。
- 如何保证变量s指向的是字符串常量池中的数据呢?
- 两种方式:
- 方式一: String s = “”; //使用字面量
- 方式二:String s = 调用字符串的.intern()
- 两种方式:
- 面试题:
- new String(“ab”) 会创建几个对象。 new String(“a”) + new String(“b”)呢
- 会在堆空间new String, 常量池创建一个ab, 字节码指令 一个有new一个时 ldc
- new String(“a”) + new String(“b”)几个
- 第一个:new StringBuilder()
- 第二个: newString()
- 第三个: 常量池a
- 第四个 newString
- 第五个:b
- 第六个:new String()
- toString()方法没有生成字符串常量池中ab
- new String(“ab”) 会创建几个对象。 new String(“a”) + new String(“b”)呢
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: true
//这一块相当于执行了 new String("11");
String s3 = new String("1") + new String("1");
//此时常量池中还没有“11”;
s3.intern(); // 在常量池中生成11, 如何理解? jdk6:在常量池中创建了一个新的对象“11”, 也就有新的地址
// jdk7/8: 同样也是在常量池中并没有创建11而是呢创建了一个指向new String("11")的地址
String s4 = "11"; // jdk7/8 : 这个拿到的就是那个地址
System.out.println(s3 == s4); //jdk6:false; jdk7/8 : true
}
}
- StringTable 的垃圾回收
存在String的垃圾回收行为。