java string底层实现_JAVA中String的底层解析常见面试问题

JAVA中String 是Final类不能被继承。JAVA 对String的处理和一般Class有所不同。 这文章主要是解释一下String的存储模式和java的字符串常量池的机制,和几个涉及底层的引用问题解析。

首先提出几个问题:

1.String的内容为什么是不可更改的?

2.JAVA中“adc”这种创建的字符串的创建过程是怎样的?

3.String(String string)的构造方法是如何工作的?

4.一个线程中内容为“adc”的String对象,存储的char[]是否是同一个,char[]数组是否一定在字符串常量池中?

5思考java中String 不可更改的好处在哪?

6 intern方法和字符串常量池的关系?

7string的+编译器是如何处理的?

1.String的内容为什么是不可更改的?

我们通过源代码可以看到存储string内容的char[]是这么定义的:

private final char value[];

复制代码

可能有人会有疑问既然是final引用却没有附初始值。

答案是final变量是可以在构造方法中进行赋值的。

所以value的所有赋值都在String的几个构造方法中。

这样从代码逻辑上控制了String不可变。

2.JAVA中“adc”这种创建的字符串的创建过程是怎样的?

这个问题比较简单,就是”adc“会被放到字符串常量池中,可以称为字面量,所有String s=“adb” 的字符串的引用都是指向字符串常量池中的。

3.String(String string)的构造方法是如何工作的?

public String(String original) {

this.value = original.value;

this.hash = original.hash;

}

复制代码

这说明从用String去new String构造新的对象是,确实会产生新的对象,而且新的value和hash值都和原String对象一致。 当然String还是有其他构造方法,都会去new char[]

4一个线程中内容为“adc”的String对象,存储的char[]是否是同一个,char[]数组是否一定在字符串常量池中?

答案其实可以根据3推导出。

由String产生的String里面的char[]是同一个,其他方式产生的都是新的。“”包裹产生的字符串会在常量池中,其他的都是正常的存在堆中。所以堆中可以有n份“adb”的串,常量池中的“adc”永远只有一个,可以被多个引用所指向。后续将会用代码解释上述所有现象。

public static void main(String[] args)

throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {

// TODO Auto-generated method stub

String string = "abc";

String string2 = "abc";

String string3 = new String(string);

char[] charString={'a','b','c'};

String string4 = new String(charString);

String string5 = new String(string4);

charString[2]='e';

Class sClass = String.class;

Field privateStringField = sClass.getDeclaredField("value");

privateStringField.setAccessible(true);

char[] chars = (char[]) privateStringField.get(string);

char[] chars2 = (char[]) privateStringField.get(string2);

char[] chars3 = (char[]) privateStringField.get(string3);

char[] chars4 = (char[]) privateStringField.get(string4);

char[] chars5 = (char[]) privateStringField.get(string5);

System.out.println("++" + chars + " " + Arrays.toString(chars));

System.out.println("++" + chars2 + " " + Arrays.toString(chars2));

System.out.println("++" + chars3 + " " + Arrays.toString(chars3));

System.out.println("++" + chars4 + " " + Arrays.toString(chars4));

System.out.println("++" + chars5 + " " + Arrays.toString(chars5));

System.out.println("++" + charString + " " + Arrays.toString(charString));

System.out.println(string==string2);

System.out.println(string3==string);

System.out.println(string4==string);

System.out.println(string5==string4);

}

复制代码

控制台输出:

++[C@4e25154f [a, b, c]

++[C@4e25154f [a, b, c]

++[C@4e25154f [a, b, c]

++[C@70dea4e [a, b, c]

++[C@70dea4e [a, b, c]

++[C@5c647e05 [a, b, e]

true

false

false

false

复制代码

我们用反射可以拿到char[] 的引用,并打印出char[] 指向的内存地址。 可见前三个String都是指向同一个地址的(字符串常量池),后三个String都是指向堆中的地址。

5思考java中String 不可更改的好处在哪?

1.如果可变复用将变得不稳定。复用可以节约内存

2.hashcode被缓存,没必要重复结算

3.线程安全(网上的说法,我并不完全赞同,能保证值得安全,并不能保证引用的安全总是)

4.如果定义为final 也就是String的引用和内容都会稳定不可变(当然不包括使用反射的情况)

6intern方法的作用?

intern的作用是 将string放入常量池,并返回该引用,如果已经在常量池中直接返回引用。

在JDK1.6之后 intern方法是将不存在字符串常量池的String去记录到常量池里(存的是引用)实例还是在堆上

在JDK1.6之前intern方法是把String复制到字符串常量池里是新对象了(据说是在永久代中)目前可以确定String实例肯定不是一个了,里面的char[]是否复用,这个还不确定,手里没有JDK16的环境,可以用我上面的反射方法来进行验证。

7string的+编译器是如何处理的?

两种情况:

如果只是“” +“”+“”+… 不涉及变量的,javac会直接合并所有的"" “” ,形成utf8 结构体,保存下来,结构体中有个2byte的length字段记录字面量(”“这种东西官方称为字面量)在类运行中会把结构体的东西加载到内存的字符串常量池中(具体加载细节后续再聊)

第二种情况:

String j1="a";

String j2="b";

String j12="ab";

String j3="a"+j12;

System.out.println(j3 == j12); (输出是false)

复制代码

遇到这种情况javac并不会合并”“ ”“,尽管j12也是字面量的变量。我们可以通过javap -c查看字节码:

Code:

0: ldc #2 // String a

2: astore_2

3: ldc #3 // String b

5: astore_3

6: new #4 // class java/lang/StringBuilder

9: dup

10: invokespecial #5 // Method java/lang/StringBuilder."":()V

13: ldc #2 // String a

15: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

18: aload_3

19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

22: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

25: astore 4

复制代码

很清楚的看到,这里会new StringBuilder 调用append方法,最后toString。StringBulider的toString方法我们可以看一下,是根据当前的char[]去 newString的,所以必然不是用一个对象,所以输出的结果是false。

未完待续…

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值