java字符串与字符串常量池

文章目录

  • 字符串
    • 基本特性
    • String类的改变
    • 字符串拼接操作
    • intern()
    • 常见面试题
  • 字符串常量池
    • 基本特性
    • 字符串常量池的内存位置
      • 为什么要移动字符串常量池的位置


字符串

基本特性

  • String类被声明为final的,不可以被继承
  • String实现了Serializable接口:表示字符串支持序列化;实现了Comparable接口:表示String可以进行大小比较
  • String是不可变的字符序列,简称:不可变性
            > 当对字符串重新赋值,需要重写指定内存区域赋值,不能使用原有的value进行赋值
            > 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
            > 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value赋值
  • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
  • 单个字符串保存时是有长度限制的,其内部value值是一个数组,length()方法返回的是一个int型数据,也就是说其数组长度不能超过int型最大限制2^31-1,也就是4GB的数据,但是阅读《深入理解java虚拟机》类文件结构一节后,知道常量池中CONSTANT_String_info的index为u2,所以在编译时期,字符串长度不能超过【0-65565】,但由于虚拟机需要一字节指令结束,所以编译期实际范围为【0-65564】,详情请点击这里

String类的改变

jdk8及以前:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    private final char value[];

java9及以后:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
	@Stable
    private final byte[] value;

        String在jdk8及以前内部定义了private final char[] value;用于存储字符串数据。jdk9时将value类型改进为byte[],因为String为堆空间中存储的主要数据,而大部分String对象只包含Latin-1格式字符,而Latin-1格式字符使用一个字节就能存下,使用char的话就会浪费掉1个字节的空间,所以jdk9将charp[]改为了byte[],对于大于一个字节的字符(如:中文),jvm增加了一个字符编码集的标识encoding-flag,相应的StringBuffer和StringBuilder也做了同样的改变
详情请看这里

字符串拼接操作

  • 常量与常量的拼接结果会通过编译器优化而放入常量池,如,String str = “a”+“b”;经过编译器优化后,编译的文件为String str = “ab”;
  • 拼接时,只要其中一个是变量,那么结果就放在堆(堆中常量池之外的地方)中。如:
/**
 * 下面3行代码:编译后是通过StringBuilder实现的
 * StringBuilder sb = new StringBuilder();
 * sb.append("a");
 * sb.append("b");
 * String str =  sb.toString(); 注意:toString()产生的字符串不会被放入到字符串常量池中
 */
String a = "a";
String b = "b";
String str = a+ b;

        我们平时拼接字符串时,建议大家使用StringBuilder进行拼接。尤其是在循环中进行拼接的时候,每次循环都会创建一个StringBuilder对象及其toString()方法产生的String对象,这会占用非常多不必要的内存

intern()

        jdk6中:intern()方法会尝试将该字符串放入字符串常量池中,如果字符串常量池中有该字符串,则返回常量池中该字符串的地址;如果没有,则将当前对象复制一份放入到字符串常量池中,并返回其在池中的地址
        jdk7起:如果字符串常量池中有该字符串,则返回常量池中该字符串的地址;如果没有,则会把对象的引用地址复制一份放入池中,并返回其在池中的地址

        通俗来讲:intern()方法确保字符串在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度

String str = "abc";
String newStr = new String("abc");
newStr  == str ;// false

String str2 = "abcd";
String newStr2 = new String("abcd").intern();
newStr2 == str2 ;// true

常见面试题

1、String str = new String(“abc”)创建了几个对象?
答:两个,一个是new关键字在堆中创建的对象即str变量实际引用的对象;另一个是字符串"abc",它会被存放到字符串常量池中。当然这个答案不绝对,详情请看这里
2、String str = new String(“a”)+new String(“b”)会创建几个对象?
答:6个,分别是:new StringBuilder()new String("a") 、 常量池中的"a" 、 new String("b") 、 常量池中的"b" 、 StringBuilder的toString()产生的对象。注:StringBuilder的toString()方法使用的是new String(“ab”),它的字符串"ab"不会被放入到字符串常量池中
3、关于intern()的面试题

	public static void main(String[] args) {
        String s = new String("1");
        s.intern();
        String s2 = "1";
        System.out.println(s == s2);// jdk6 : false   jdk7 : false

        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);// jdk6 : false   jdk7 : true
    }

        首先,对于第一个结果来说:在s调用intern()方法前,JVM就因为new String(“1”)这条语句在字符串常量池中放入了"1",而s则是指向的堆区的new String(“1”)对象的地址,该地址对象会指向常量池中“1”这个字符串;String s2 = “1”,所以s2直接指向常量池中“1”这个字符串,所以第一个打印语句打印的false

        对于第二个打印语句结果来说:String s3 = new String(“1”) + new String(“1”);这条语句编译后通过StringBuilder进行拼接后调用toString方法返回的s3,这个过程不会在字符串常量池中加入字符串"11"。而intern()在jdk6时会将当前对象复制一份放入池中,从jdk7开始会把对象的引用地址复制一份放入池中,所以在jdk7中,在将字符串"11"加入常量池时,发现堆区(字符串常量池以外)已经有了"11"这个字符串,那么为了节省空间,常量池中保存的是该堆区对象的地址,而String s4 = “11”;也就是s4指向的是常量池。所以整个过程就是s4指向常量池,常量池存储的是s3的地址,所以第二个打印语句在jdk6时返回false,在jdk7时返回true

字符串常量池

  • 在java语言中有8中基本数据类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度最快、最节省内存,都提供了一种常量池的概念
  • 常量池类似于一个java系统级别提供的缓存。8中基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:
            > 直接使用双引号声明出来的String对象会直接存储在常量池中,如:String str = “wgc”;
            > 如果不是双引号声明的String对象,可以使用String提供的intern()方法,动态的将其放入字符串常量池中

基本特性

  • 字符串常量池中是不会存储相同内容的字符串的
  • 字符串常量池是一个固定大小的HashTable,如果存放的String非常多,致使Hash冲突严重,从而导致链表很长,直接造成的影响是当调用String.intern时性能会大幅下降
  • 使用-XX:StringTableSize可设置字符串常量池的长度
  • 在jdk6中StringTable长度默认值是1009;在jdk7中StringTable的长度默认值为60013;在jdk8开始,1009是StringTable可设置的最小值

字符串常量池的内存位置

版本 位置
jdk6 及以前 方法区、永久代
jdk7及以后 java堆

为什么要移动字符串常量池的位置

  1. 在之前的永久代中,永久代最大可分配空间,32位机器默认64M,64位机器默认82M。而永久代方法区中还有其他的诸如运行时常量池区域共同占用这些空间,导致字符串常量池存放内容有限
  2. 永久代GC频率过低,不能及时的进行回收,而且《Java虚拟机规范》中说过可以不要求虚拟机在方法区实现垃圾回收,而jdk7以前字符串常量池在方法区中

你可能感兴趣的:(基础知识,String,java,java,字符串)