Java基础02—— String类型相关

121 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

创建字符串

查看字节码

 javap -v 全路径/test.class

字符串常量池在JVM源码中对应的是StringTable类,底层实现是一个Hashtable(数组+链表的形式,初始长度1009),用于存储常量池中字符串对象的引用。(存放时,通过字面量的哈希值确定存放的下标,若冲突则equals比较字符串值是否相同,相同则拒绝插入,不相同则采用链表法插入元素)

使用new关键字新建一个字符串对象时,JVM首先在字符串常量池中查找有没有"xyz"这个字符串对象

如果字符串常量池中有,则直接在中创建一个"xyz"字符串对象,然后将中的这个"xyz"对象的地址返回;

如果字符串常量池中没有,则首先在字符串常量池创建一个"xyz"字符串对象,然后再在创建一个"xyz"字符串对象,然后将中这个"xyz"字符串对象的地址返回。

 String s1 = new String("xyz");//创建两个对象,字符串常量池中一个"xyz",堆上一个"xyz",char数组对象
 String s2 = new String("xyz");//堆上创建一个"xyz"
 System.out.println(s1==s2);

因此上述代码一共创建了三个字符串对象,new第一个String对象时,字符串常量池中没有"xyz",所以在字符串常量池中创建一个"xyz",堆上在创建一个"xyz",new第二个String对象时,只在堆上创建一个"xyz"。

重要:blog.csdn.net/Soul_wh/art…

         char[] chs={'a','b','c','d'};
         //堆上生成String对象,字符串常量池中没有
         String s1=new String(chs);
         //intern发现字符串常量池中没有该字符串,将s1指向的string对象地址存入字符串常量池并返回
         String s2=s1.intern();//获得inter()方法返回常量池中的对象地址
         String s3="abcd";//获得常量池中对象的地址
         System.out.println(s1==s2);//true  s1从字符串常量池获取的string对象地址,与s2指向同一个对象
         System.out.println(s3==s1);//true

intern()方法(native方法)

image-20220626101647390

调用intern() 方法时,使用equals(object)方法确定在Stringtable类中是否包含(并不是地址相等)等于该String对象的字符串,有则返回池中字符串的引用(注意是常量池中的对象,不是堆中的对象)。否则,将此String对象添加到池中,并返回此String对象的引用

以下例子中,b指向堆中的String对象(“字面量”),调用b.intern()方法时由于Stringtable没有“字面量”,因此将b指向的堆中的String对象的地址存入到Stringtable中。

         String c = "量字面";
         String b = new StringBuilder("字面").append("量").toString();//b指向堆的String对象
         String d = new StringBuilder("量").append("字面").toString();//d指向堆的String对象
         System.out.println(b == b.intern()); //true       b堆   b.intern()池->堆
         System.out.println(d == d.intern()); //false      d堆   d.intern()池

Hashtable的key是字面量,value是地址值,如果按照网上的说法,Hashtable存的是引用是针对value来说的。Hashtable存的是字符串对象是把key和value看做一个整体来说的,如果在面试中被问到 ,需要向面试官解释一下答案的角度。

如何保证变量s指向的是字符串常量池中的数据?

 String s="aaa";//字面量定义的方式   方式一
 String s=new String("aaa").intern();//方式二

intern方法的难题:

image-20220322153936195

解释:

image-20220322154744138

拼接String字符串

+如何拼接String字符串?

  • 变量拼接的原理是StringBuilder对象拼接
  • 常量拼接的原理是编译器优化

字符串变量在拼接过程中,会先创建一个StringBuilder对象,然后再使用append()方法将需要连接的字符串添加到该对象中,最后使用StringBuilder对象的toString()方法获得一个String字符串。

concat如何拼接字符串?

首先分别获得原字符串和拼接字符串长度,按照该长度用Arrays.copyOf() 获得一个新字符数组,且将原字符串复制到字符数组中,再用getChars() 将拼接字符串复制到新字符数组中,再新建一个String字符串返回。

创建了几个对象?

 String str = "abc" + new String("def");
 //等同于
 String s = new String("def");
 new StringBuilder().append("abc").append(s).toString();

对比等价代码,执行【new String("def");】会在字符串常量池分别创建一个 "def"对象,然后在字符串常量池创建一个 "abc"对象,由于使用了【+】,先在堆上创建一个StringBuilder对象,然后使用该对象的append() 方法将 "abc"和s字符串添加到StringBuilder对象中,最后使用toString() 方法获得一个String对象。

因此,字符串常量池中创建了 "def"对象"abc"对象、堆中有StringBuilder对象"abcdef"对象"def"对象,共5个对象

问题:会创建几个对象

 new String("ab");//? 2个
 //一个对象是new关键字在堆空间创建的
 //另一个对象是字符串常量池中的对象。证明,查看字节码指令可以得知
 ​
 new String("a")+new String("b");//? 6个
 //对象1:new StringBuilder()
 //对象2、3:new String("a")、常量池中的"a"
 //对象4、5:new String("b")、常量池中的"b"
 //对象6:new String(value,0,count)
 ​
 //强调:注意字符串常量池中没有"ab"字符串

hashcode的计算

string 类型hashcode的计算方法,由每个字符的AscII码加权求和得到,第i个字符乘31^(n-1-i)次幂

image-20220321154454903

为什么权是31?satckoverflow有讨论:

主要是因为31是一个奇质数,所以【31* i=32 * i-i=(i<<5)-i】,这种位移与减法结合的计算相比一般的运算快很多。

String、StringBuffer、StringBuilder

StringStringBufferStringBuilder是编程中经常使用的字符串类,总结一下他们的不同与相同。

1. 可变与不可变

  String类中的字符数组使用有“final”修符,不可变。

  StringBuilderStringBuffer的字符数组无"final"修饰,这两种对象的字符都是可变的

2. 是否多线程安全

  String中的对象不可变,可以理解为常量线程安全

  StringBuffer方法synchronized进行同步,线程安全,效率低。

  StringBuilder的方法没有同步,非线程安全,效率高。

其他

  都继承公共父类AbstractStringBuilder(抽象类)。