java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)

36fd46ff13022ae18d847bd57c882b1b.gif

做一个积极的人

编码、改bug、提升自己

我有一个乐园,面向编程,春暖花开!

一、初识String类

首先JDK API的介绍:

publicfinalclassStringextendsObject

implementsSerializable,Comparable,CharSequence

String类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。

字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。例如:

Stringstr="abc";

等效于:

chardata[]={'a','b','c'};

Stringstr=newString(data);

从JDK API中可以看出:

String类是final类,那么String类是不能被继承的。

实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

实现了Serializable接口,支持序列化,也就意味了String能够通过序列化传输。

二、字符串的不可变性

从上面的介绍中发现:字符串是常量,它们的值在创建之后不能更改。为什么会这样呢?要了解其原因,简单看一下String类的源码实现。

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第1张图片

从上面源码中可以看出String类其实是通过char数组来保存字符串的,注意修饰这个char前面的关键字 final。final修饰的字段创建以后就不可改变。

注意: privatefinalcharvalue[]; 这里虽然value是不可变,也就是说value这个引用地址不可变。但是因为其是数组类型,根据之前学过的内容,value这个引用地址其实是在栈上分配 ,而其对应的数据结构是在堆上分配保存。那也就是说栈里的这个value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,

finalint[]value={1,2,3}

int[]another={4,5,6};

//编译器报错,final不可变

value=another;

value用final修饰,编译器不允许我把value指向栈区另一个地址。但如果直接对数组元素进行修改,分分钟搞定。

finalint[]value={1,2,3};

//这时候数组里已经是{1,2,100}

value[2]=100;

所以String是不可变的关键都在底层的实现,而不是一个final。

也可以通过上面的 concat(Stringstr) 和 replace(charoldChar,charnewChar)方法简单进行了解,所有的操作都不是在原有的value[]数组中进行操作的,而是重新生成了一个新数组buf[]。也就是说进行这些操作后,最原始的字符串并没有被改变。

如果面试有问到的话要修改String中value[] 数组的内容,要怎么做,那么可以通过反射进行修改!实际使用中没有人会去这么做。

三、字符串常量池和 intern 方法

Java中有字符串常量池,用来存储字符串字面量! 由于JDK版本的不同,常量池的位置也不同,根据网上的一些资料:

jdk1.6及以下版本字符串常量池是在永久区中。

jdk1.7、1.8下字符串常量池已经转移到堆中了。(JDK1.8已经没有去掉永久区)

因为字符串常量池发生了变化,在String内对intern()进行了一些修改:

jDK1.6版本中执行intern()方法,首先判断字符串常量池中是否存在该字面量,如果不存在则拷贝一份字面量放入常量池,最后返回字面量的唯一引用。如果发现字符串常量池中已经存在,则直接返回字面量的唯一引用。

jdk1.7以后执行intern()方法,如果字符串常量池中不存在该字面量,则不会再拷贝一份字面量,而是拷贝字面量对应堆中一个引用,然后返回这个引用。

String 类型的常量池比较特殊。它的主要使用方法有两种:

直接使用双引号声明出来的 String 对象会直接存储在常量池中。

如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。不同版本的intern 表现看上面介绍。

说明:直接使用new String() 创建出的String对象会直接存储在堆上

通过一个栗子,看一下上面说的内容:

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第2张图片

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第3张图片

使用JDK1.8版本运行输出的结果: false 和 true 。

先上面示例的示意图:

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第4张图片

str1直接创建在字符串常量池中, str2使用new关键字,对象创建在堆上。所以 str1==str2 为false。

str3是 str2.intern(),根据上面的介绍,在jdk1.8首先在常量池中判断字符串 aflyun是否存在,如果存在的话,直接返回常量池中字符串的引用,也就是 str1的引用。所以 str1==str3为true。

如果你理解了上面的内容,可以在看一下下面的栗子,运行结果是在JDK1.8环境:

栗子1:

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第5张图片

栗子2:

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第6张图片

栗子3:涉及到final关键字,可以试着理解一下

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第7张图片

总结:

直接定义字符串变量的时候赋值,如果表达式右边只有字符串常量,那么就是把变量存放在常量池里。

new出来的字符串是存放在堆里面。

对字符串进行拼接操作,也就是做"+"运算的时候,分2中情况:

表达式右边是纯字符串常量,那么存放在字符串常量池里面。

表达式右边如果存在字符串引用,也就是字符串对象的句柄,那么就存放在堆里面。

四、面试题

1、 String s1 = new String("hello");这句话创建了几个字符串对象?

情况1:

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第8张图片

如果上面代码的话,这种情况总共创建2个字符串对象。常量池中没有字符串"hello" 的话,一个是new String 创建的一个新的对象,一个是常量“hello”对象的内容创建出的一个新的String对象。

情况2:

Strings2="hello";

Strings1=newString("hello");

String s1 = new String("hello"); 此时就创建一个对象,而常量“hello”则是从字符串常量池中取出来的。

2、有时候在面试的时候会遇到这样的问题:都说String是不可变的,为什么我可以这样做呢,String a = "1";a = "2";

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第9张图片

【首先创建一个String对象s,然后让s的值为“aflyun”, 然后又让s的值为“hello aflyun”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢?】

其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。

也就是说,s只是一个引用,它指向了一个具体的对象,当s=“hello aflyun”; 这句代码执行过之后,又创建了一个新的对象“hello aflyun”, 而引用s重新指向了这个新的对象,原来的对象“aflyun”还在内存中存在,并没有改变。内存结构如下图所示:

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第10张图片

类似的一张图(图片来源网络):

java字符串字面量存放在_Java内存管理-探索Java中字符串String(十二)_第11张图片

总结一下:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何改变操作都会生成新的对象”。

参考资料

java的线程安全、单例模式、JVM内存结构等知识学习和整理

Java-String.intern的深入研究

深入理解Java中的String

由于本人能力有限,文中若有错误之处,欢迎指正。

2f82bd13146bbdce189d5ab7fdf45cab.png

谢谢你的阅读,祝你有所收获,也愿你每天开心愉快!

如果觉得本文对你有所帮助,欢迎转发,您的转发,就是对我最大的鼓励!

文章推荐

Java相关技术

高效软件工具

干货技术资源

前沿技术分享

FOLLOW US

你点的每个在看,我都当成了喜欢!

你可能感兴趣的:(java字符串字面量存放在)