字符串常量池,intern,以及收集面试题


参考文章
美团技术团队:深入解析String#intern

https://www.zhihu.com/question/29884421

https://javaranch.com/journal/200409/ScjpTipLine-StringsLiterally.html

字符串常量池

字符串常量池是什么?

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个HashTable,默认值大小长度是1009;字符串常量由一个一个字符组成,放在了StringTable上。

  • 在JDK6.0,StringTable长度是固定了1009,如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降
  • JDK7.0,StringTable的长度可以通过参数指定:
-XX:StringTableSize=66666

字符串常量池存储位置

  • JDK6:常量池存储在永久代中
  • JDK7:常量池存储在堆中。

字符串常量池存储着什么

个人认同:

字符串常量池:存放的是字符串的引用地址。

驻留intern 方法

当对象s调用intern(),首先查找字符串常量池中引用的字符串的值,看有没有和s的相等;如果有相等,则返回与s值相等的字符串的引用;如果找不到,则这把s的引用存入字符串常量池中,并返回。

String重载“+”,“+=”

Java对String的+操作符进行了重载。
例子:

public class Demo {
    public static void main(String[] args) {
        String a = "1";
        String b = a + "2";
    }
}
  • 编译:javac Demo.java
  • 反编译:javap -c Demo
public class basis.wei.Demo {
  public basis.wei.Demo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String 1
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String 2
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_2
      23: return
}

编译器自动创建StringBuilder对象, 并调用append()方法进行拼接,最后toString()生成结果。

String a = “1”+“2” 创建了几个对象?

答案: 如果常量池没"12",则创建1个;如果常量池有"12",则创建0个,返回该对象引用

原因: 编译时,会自动优化,进行拼接成“12”;
证据:

public class Demo {
    public static void main(String[] args) {
        String s = "1"+"2";
    }
}
  1. javac Demo.java #编译
  2. javap -verbose Demo #查看 常量池
public class basis.wei.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = String             #14            // 12
   #3 = Class              #15            // basis/wei/Demo
   #4 = Class              #16            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               Demo.java
  #13 = NameAndType        #5:#6          // "<init>":()V
  #14 = Utf8               12
  #15 = Utf8               basis/wei/Demo
  #16 = Utf8               java/lang/Object
{
  public basis.wei.Demo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String 12
         2: astore_1
         3: return
      LineNumberTable:
        line 10: 0
        line 11: 3
}

编译后只有一个“12”。

String a = new String (“1”);创建了几个对象?

参考文章:请别再拿“String s = new String(“xyz”);创建了多少个String实例”来面试了吧

答案: 两个,一个是字符串字面量"1"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与"xyz"相同的实例

在这里插入图片描述

String s = new String(“1”);String s2 = “1”;System.out.println(s== s2);System.out.println(s.intern()== s);System.out.println(s.intern()== s2);

本题参考String#intern

 	    public static void main(String[] args) {
        String s = new String("1");
        String s2 = "1";
        System.out.println(s== s2);
        System.out.println(s.intern()== s);
        System.out.println(s.intern()== s2);
    }

主要考查intern的使用,以及字符串常量池的概念;
JDK8环境,答案

false
false
true

图解在这里插入图片描述
s== s2 为false;可以很好理解,s1和s2的引用地址不同。

s.intern()== s为false,s.intern()在String Pool中找到有“1”与s的值相等,所以返回找到的值相等的引用值,但与s的引用并不相等;所以false

第三个要先了解以下内容

在创建字符串对象时,会先到字符串常量池查到字符串引用的值,看有没有值相等,如果有值相等,则将查到字符串的引用赋值给变量;如果查不到则在堆上创建实例,并将实例的引用存储在String Table,并将引用赋值给变量。

String s2 = “1”; “1” 可以在String pool找到引用对象的值相等;所以直接将引用赋值给s2

所以上图表示的s1的引用和常量池中的"1"的引用指向同一地址。so,s.intern()返回的是常量池中“1”的引用 与s1的引用相等。

如果以上理解了,在看看这题

  public static void main(String[] args) {
        String s = new String("1")+"1";
        System.out.println(s.intern()== s);
        String s2 = "11";
        System.out.println(s.intern()== s2);
    }

jdk 8答案

true
true

在这里插入图片描述
第一次s.intern() 发现String Pool中的引用的值没有和11相等的,就将“11”的引用驻留在了String Pool;并将引用返回。s.intern()== s ;等于true。

第二个true;直接将“11”的引用返回给s2,如图。

String,StringBuilder,StringBuffer区别

String StringBuilder StringBuffer
储存结构 private final char value[]; char[] value; private transient char[] toStringCache;
线程是安全性 安全 不安全 安全,synchronized修饰方法
运算 1. 编译器优化 2. “+”,“+=” 重载 效率高 效率低
使用场景 操作少量数据 单线程,操作大量数据 多线程,操作大量数据

写一个方法来判断一个String是否是回文(顺读和倒读都一样的词)

  /**
     * 判断是否是回文
     * @param s
     * @return
     */
    public static boolean isPalindromeString(String s){
        StringBuilder stringBuilder = new StringBuilder(s);
        return stringBuilder.reverse().toString().equals(s);
    }

为什么我们在使用HashMap的时候总是用String做key

我们知道HashMap的key只能是引用类型,而在进行路由寻址时,是根据key的hashcode来计算的。
而String有以下特点。

  • String 有个hash属性,用来储存hashcode,可避免重复计算hashCode。
  • String的值是根据值来计算的,所以String 作为key,如果他的值相等,那么hashcode也一定相等;这样的能减少很多实际工作中可能会出现的麻烦。

参考文章:为什么 HashMap 常用 String 对象作 key

//缓存hashcode
/** Cache the hash code for the string */
    private int hash; // Default to 0

	// 根据值计算hashcode
	public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

没事我来瞅瞅

在这里插入图片描述
如果有什么地方不对,麻烦请指出!谢谢!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章