java string底层实现_Java基础底层-String类基础

一、概念

String是所有语言中最常用的一个类;是不可变的,是最终类(final),最终类不能被继承。

核心特点 对String对象的任何改变都不影响到原对象,相关的任何操作都会生成新的对象

二、声明方式

ad1093b25837cc857b56698fc2ca257d.png

2.1 指令码分析

2.1.1 直接赋值

在编译期间,会将等号右边的abc常量放入常量池(ldc),在程序运行时,会将str1变量压栈,栈中的str1变量直接指向字符串常量池的abc项,没有经过堆内存

c39420aa6ab1e5f11f91ccb290a869a5.png

2.1.2构造方法实现实例化

在编译期间,会将等号右边的abc常量放入常量池,在程序运行时,现在堆中创建一个String对象,该对象的内容指向常量池中“abc”项,然后将str2变量压栈(如图所示)

2cf1bf1f6facea39234ba1e572aff4b1.png

2.2 图解

031ff8b5a25f9be66844af96e271db4e.png

三、String对象为什么不可变

3.1demo代码层面

demo代码

String str = "abc";

String str1 = "abc";

System.out.println("str="+str);

System.out.println("str的地址="+System.identityHashCode(str));

System.out.println("str1的地址="+System.identityHashCode(str1));

System.out.println("对str值进行修改之后");

str = str + "ABC";

System.out.println("str="+str);

System.out.println("str的地址="+System.identityHashCode(str));

System.out.println("str1的地址="+System.identityHashCode(str1));

str.replace("b","B");

运行结果

str=abc

str的地址=1639705018

str1的地址=1639705018

对str值进行修改之后

str=abcABC

str的地址=1627674070

str1的地址=1639705018

解释:

str1 从开始到最后都没有改变,指向常量池中的值一直不变;str从刚开始是“abc”,到最后的“abcABC”,虽然表面是对变量str进行了修改,但是通过运行结果可以知道,地址发生了改变。此处实际上是重新创建了一个“abcABC”的对象,让str重新指向了这个值;而原先的“abc”一直没变。

3c8672cc383175775e6c9d9128a5f540.png

3.2源码层面(jdk7之后)

43f81fba42b95cc41a8e7222339e6148.png

从上述代码知,在java中String类其实就是对字符数组的封装。在jdk7中,只有一个value变量,也就是value中所有的字符都属于String这个对象。在java中,数组实际上只是一个引用,它指向一个真正的数组对象,也就是如下布局(数组是对象,此处的value实际上是指向对象的一个引用而已)

94a38d5017a61fde16fabdbf8396ec30.png

value这个变量是private的,也没有提供对应的set方法,所以String类的外部无法修改String,也就是说String一旦初始化就不能被修改。

此外这个变量是final的类型,也就是说在String类的内部,一旦变量初始化了,也不能被修改。所以可以认为String对象不可变。

replace,substring等方法解释

源码中存在一些方法,调用后可以得到改变后的值(replace,replaceAll,toLowerCase等);通过源码可以知道,这些值表面上改变了,实际上方法内部创建了一个新的String对象并把这些对象重新赋值给当前的引用。

四、String对象真的不可变

由上可知,String的成员变量都是用private final修饰的,初始化后不可以改变。但是value比较特殊,它是一个引用变量,并不是真正的对象。value是final修饰的,也就是说value不能再指向其他数组对象,但我们可以改变value指向的数组中的值。

使用普通的代码无法做到,因为我们不能访问到value的引用,更不能通过引用去修改数组。我们可以使用反射访问私有成员,进而改变value所指向数组的结构

代码demo演示

public static void main(String[] args) throws Exception {

//创建字符串"Hello World", 并赋给引用s

String s = "Hello World";

System.out.println("s = " + s); //Hello World

System.out.println("修改前的str的内存地址" + System.identityHashCode(s));

//获取String类中的value字段

Field valueFieldOfString = String.class.getDeclaredField("value");

//改变value属性的访问权限

valueFieldOfString.setAccessible(true);

//获取s对象上的value属性的值

char[] value = (char[]) valueFieldOfString.get(s);

//改变value所引用的数组中的第5个字符

value[5] = '_';

System.out.println("s = " + s); //Hello_World

System.out.println("修改前的str的内存地址" + System.identityHashCode(s));

}

运行结果

s = Hello World

修改前的str的内存地址1639705018

s = Hello_World

修改前的str的内存地址1639705018

结论:

通过反射是可以实现修改String值的同时而不改变String的地址,实现真正意义上的修改

五 、常量池和堆(intern)

常量池和堆的区别如上的声明方式

字符串常量池: 属于堆内存中的一部分,是存放字符串常量的地方

堆内存: 创建对象的操作都会经过堆内存,堆中的对象都会指向字符串常量池中的值;

区别:实际上使用赋值和构造方法实现实例化两个操作都是指向常量池中的值,但是构造的变量是指向堆中的对象,堆中的对象在指向常量池中的值。

intern方法:主要的作用是先在常量池中找是否有对应的常量,如果有则返回该常量的地址,如果没有则创建新的常量值然后返回该常量的地址;即通过intern方法获取的变量所指向的地址就是常量池中的地址

demo代码演示

/**

* str5 在编译后会直接去常量池中str3的值和地址

* str4 和str6 都是 在堆中创建一个新的对象

* @param args

*/

public static void main(String[] args) {

String str1 = "a";

String str2 = "b";

String str3 = "ab";

String str4 = str1 + str2;

String str5 = "a"+"b";

String str6 = new String("ab");

System.out.println(str3==str5);

System.out.println(str3==str4);

System.out.println(str5==str4);

System.out.println(str3==str6);

System.out.println(str4==str6);

/**

* intern 引用常量池中与value相等的常量地址,没有则新增value到常量池中然后引用该地址

*/

System.out.println(str4.intern() == str5);

System.out.println(str6.intern() == str3);

}

运行结果

103013e489904c85844917544720535a.png

六、String,StringBuffer,StringBuilder

本文地址:https://blog.csdn.net/Huang1178387848/article/details/109838287

希望与广大网友互动??

点此进行留言吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值