字符串内存内部

本文基于我对StackOverflow的回答 。 我正在尝试解释String类如何存储文本,内部存储和常量池如何工作。

这里要理解的要点是String Java对象与其内容– private value字段下的char[]之间的区别。 String基本上是char[]数组的包装器,将其封装并使其无法修改,因此String可以保持不变。 另外, String类还记住该数组的实际部分(请参阅下文)。 这一切都意味着您可以拥有两个指向相同char[]不同String对象(相当轻量)。

我会告诉你一些例子,连同hashCode()的每个StringhashCode()内部的char[] value字段(我将其称之为文本字符串从区分)。 最后,我将显示javap -c -verbose输出以及测试类的常量池。 请不要将类常量池与字符串文字池混淆。 它们并不完全相同。 另请参见了解常量池的javap输出 。

先决条件

为了进行测试,我创建了一个实用程序方法来破坏String封装:

private int showInternalCharArrayHashCode(String s) {final Field value = String.class.getDeclaredField("value");value.setAccessible(true);return value.get(s).hashCode();
}

它将打印char[] value hashCode() ,有效地帮助我们了解此特定String是否指向相同的char[]文本。

一个类中的两个字符串文字

让我们从最简单的示例开始。

Java代码

String one = "abc";
String two = "abc";

顺便说一句,如果您只写"ab" + "c" ,则Java编译器将在编译时执行串联,并且生成的代码将完全相同。 仅当在编译时知道所有字符串时,此方法才有效。

类常量池
每个类都有自己的常量池 -常量值列表,如果它们在源代码中多次出现,则可以重用。 它包括常见的字符串,数字,方法名称等。 这是上面示例中常量池的内容:

const #2 = String   #38;    //  abc
//...
const #38 = Asciz   abc;

需要注意的重要事项是String常量对象( #2 )和字符串指向的Unicode编码文本"abc"#38 )之间的区别。

字节码
这是生成的字节码。 请注意, one引用和two引用都分配有指向"abc"字符串的相同#2常量:

ldc #2; //String abc
astore_1    //one
ldc #2; //String abc
astore_2    //two

输出量
对于每个示例,我将打印以下值:

System.out.println("one.value: " + showInternalCharArrayHashCode(one));
System.out.println("two.value: " + showInternalCharArrayHashCode(two));
System.out.println("one" + System.identityHashCode(one));
System.out.println("two" + System.identityHashCode(two));

这两对相等并不奇怪:

one.value: 23583040
two.value: 23583040
one: 8918249
two: 8918249

这意味着不仅两个对象都指向相同的char[] (下面的相同文本),所以equals()测试将通过。 但更重要的是, onetwo是完全相同的引用! 因此, one == two也是正确的。 显然,如果onetwo指向同一个对象,则one.valuetwo.value必须相等。

文字和new String()  

Java代码
现在,我们都在等待该示例–一个字符串文字和一个使用相同文字的新String 。 这将如何运作?

String one = "abc";
String two = new String("abc");

在源代码中两次使用了"abc"常量这一事实应该给您一些提示……

类常量池与上面相同。

字节码

ldc #2; //String abc
astore_1    //onenew #3; //class java/lang/String
dup
ldc #2; //String abc
invokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
astore_2    //two

仔细地看! 第一个对象的创建方法与上面相同,不足为奇。 它只需要从常量池中常量引用已经创建的String#2 )。 但是,第二个对象是通过常规构造函数调用创建的。 但! 第一个String作为参数传递。 可以将其反编译为:

String two = new String(one);

输出量
输出有点令人惊讶。 第二对表示对String对象的引用是可以理解的-我们创建了两个String对象-一个在常量池中为我们创建,第二个是为two手动创建的。 但是,为什么第一对建议两个String对象都指向同一个char[] value数组呢?

one.value: 41771
two.value: 41771
one: 8388097
two: 16585653

当您查看String(String)构造函数的工作原理时,这一点变得很清楚(此处已大大简化):

public String(String original) {this.offset = original.offset;this.count = original.count;this.value = original.value;
}

看到? 在基于现有对象创建新的String对象时,它会重用 char[] valueString是不可变的,不需要复制已知永远不会修改的数据结构。 而且,由于new String(someString)创建了现有字符串的精确副本,并且字符串是不可变的,因此显然没有理由同时存在两者。
我认为这是一些误解的线索:即使您有两个String对象,它们仍可能指向相同的内容。 如您所见, String对象本身很小。

运行时修改和intern()  

Java代码
假设您最初使用了两个不同的字符串,但是在进行一些修改之后,它们都是相同的:

String one = "abc";
String two = "?abc".substring(1);  //also two = "abc"

Java编译器(至少是我的)不够聪明,无法在编译时执行此类操作,请看一下:

类常量池
突然我们以指向两个不同常量文本的两个常量字符串结尾:

const #2 = String   #44;    //  abc
const #3 = String   #45;    //  ?abc
const #44 = Asciz   abc;
const #45 = Asciz   ?abc;

字节码

ldc #2; //String abc
astore_1    //oneldc #3; //String ?abc
iconst_1
invokevirtual   #4; //Method String.substring:(I)Ljava/lang/String;
astore_2    //two

拳头弦照常构造。 通过首先加载常量"?abc"字符串,然后在其上调用substring(1)来创建第二个。

输出量

这里不足为奇–我们有两个不同的字符串,指向内存中两个不同的char[]文本:

one.value: 27379847
two.value: 7615385
one: 8388097
two: 16585653

好吧,文本并没有真正的不同equals()方法仍然会产生true 。 我们有两个不必要的相同文本副本。
现在我们应该进行两次练习。 首先,尝试运行:

two = two.intern();

在打印哈希码之前。 onetwo不仅指向同一文本,而且它们是相同的参考!

one.value: 11108810
two.value: 11108810
one: 15184449
two: 15184449

这意味着one.equals(two)one == two测试都将通过。 我们还节省了一些内存,因为"abc"文本在内存中仅出现一次(第二个副本将被垃圾回收)。
第二个练习略有不同,请查看以下内容:

String one = "abc";
String two = "abc".substring(1);

显然onetwo是两个不同的对象,指向两个不同的文本。 但是输出如何表明它们都指向同一个char[]数组?!

one.value: 11108810
two.value: 8918249
one: 23583040
two: 23583040

我将答案留给你。 它会教您substring()工作原理,这种方法的优点是什么以及何时会导致大麻烦 。

得到教训

  • String对象本身相当便宜。 它指向的文本占用了大部分内存
  • String只是char[]的薄包装,以保持不变性
  • new String("abc")作为内部文本表示被重用是不是真的那么贵。 但是还是要避免这样的构造。
  • 从编译时已知的常量值连接String时,连接由编译器而不是由JVM完成
  • substring()有点棘手,但最重要的是,就使用的内存和运行时间而言,它都很便宜(在两种情况下均保持不变)

参考:来自Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz的字符串内存内部结构 。


翻译自: https://www.javacodegeeks.com/2012/07/string-memory-internals.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/371708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于inline-block 元素之间为何会产生间隔

关于inline-block 元素之间为何会产生间隔 现象&#xff1a; <body><input type"text"><input type"text"> </body> 在浏览器中的表现&#xff1a; 实时上不仅仅是 inline-block 会导致这种现象。 inline 也会导致。 那问题来了&a…

java 入参 是 枚举_java 枚举 参数传递

展开全部这样做是不行的&#xff0c;原因是&#xff1a;Java中的对象实例化都是在堆中&#xff0c;如果是普通的类实例变量&#xff0c;比如在方法636f707962616964757a686964616f313333376166371中定义的普通类实例变量&#xff0c;传到了方法2中&#xff0c;由于方法1和方法2…

loadView的使用总结

一、loadView 1. loadView什么时候被调用&#xff1f; 每次访问UIViewController的view&#xff08;如 controller.view、self.view&#xff09;并且view为nil&#xff0c;loadView方法就会被调用 2. 有什么作用 loadView 方法是用来负责创建UIViewController的view 3. 默认实…

数据库备份 java jar_Java实现数据库备份并利用ant导入SQL脚本

​数据备份对于经常在运维部署方面的工作者来说&#xff0c;是一件相对简单的事情&#xff0c;都可以通过某一个SQL工具进行备份&#xff0c;但是如果在项目运行当中&#xff0c;我们需要对数据进行实时&#xff0c;或者是每隔一星期&#xff0c;一个月&#xff0c;等等进行数据…

JSF简单Ajax示例

今天&#xff0c;我们将看到一些使用JSF的Ajax简单样本。 如果要查看有关JSF / Web应用程序的其他文章&#xff0c;请单击以下链接&#xff1a; 重定向后的JSF持久化对象和消息 &#xff0c; 使用JAAS和JSF进行用户登录验证 &#xff0c; JSF&#xff1a;Converter and Bean Au…

常用的好用的window工具

1. FastStone Capture截图录屏软件 百度软件中心&#xff1a;http://rj.baidu.com/soft/detail/13504.html?ald 注册企业版&#xff1a; 用户名&#xff1a;c1ikm 注册码&#xff1a;AXMQX-RMMMJ-DBHHF-WIHTV 中文输入乱码解决方法&#xff1a; 2. Notepad文本编辑器&#xff…

表分区

http://www.cnblogs.com/leestar54/p/6225821.html转载于:https://www.cnblogs.com/jouny/p/6262850.html

java飞鸽传书_feige 飞鸽传书源代码java 实现不错的联系网络编程的资料飞鸽传书的GUI(java实现) - 下载 - 搜珍网...

我的飞鸽传书/FileFilter.java我的飞鸽传书/FileNameExtensionFilter.java我的飞鸽传书/飞鸽传书/classes/feige/About.class我的飞鸽传书/飞鸽传书/classes/feige/ConnectOthers$ReadMessageThread.class我的飞鸽传书/飞鸽传书/classes/feige/ConnectOthers.class我的飞鸽传书…

JAXB和根元素

XmlRootElement是人们习惯于与JAXB&#xff08;JSR-222&#xff09;一起使用的注释。 目的是将根元素与类唯一关联。 由于JAXB类映射到复杂类型&#xff0c;因此一个类有可能对应于多个根元素。 在这种情况下&#xff0c;无法使用XmlRootElement &#xff0c;人们开始感到有些困…

python socket模块实现udp通信_Python基于socket模块实现UDP通信功能示例

Python基于socket模块实现UDP通信功能示例本文实例讲述了Python基于socket模块实现UDP通信功能。分享给大家供大家参考&#xff0c;具体如下&#xff1a;一 代码1、接收端import socket#使用IPV4协议&#xff0c;使用UDP协议传输数据ssocket.socket(socket.AF_INET, socket.SOC…

Hibernate缓存基础知识

最近&#xff0c;我尝试了休眠缓存。 在这篇文章中&#xff0c;我想分享我的经验&#xff0c;并指出Hibernate Second Level Cache的一些细节。 在此过程中&#xff0c;我将指导您阅读一些有助于实现缓存的文章。 让我们从地面开始。 在休眠状态下缓存 缓存功能旨在减少必要的…

TP3.2之WHERE组合条件处理

1、条件都是int类型&#xff1a; $User->where(type1 AND status1)->select(); 2、条件包含字符串类型&#xff1a; 使用3.1以上版本的话&#xff0c;使用字符串条件的时候&#xff0c;建议配合预处理机制&#xff0c;确保更加安全&#xff0c; $Model->where("i…

linux-ssh远程后台执行脚本-放置后台执行问题(转)

写了一个监控负载的小脚本&#xff08;死循环&#xff0c;测试结束后再kill对应进程&#xff09;&#xff0c;因需要监控多台服务器&#xff0c;所以在一台服务器上使用ssh统一执行脚本遇到问题&#xff1a;使用ssh root172.16.146.20 /usr/local/luly/loadavg.sh 2 2 &执行…

python2.7输入函数_Python2.7的用户输入函数有问题,无法让这些输入与程序一起工作...

我对python世界还是个新手&#xff0c;虽然我已经用php做了很多工作。。。这是我的案子。。。在我正在用python2.7为我的小程序编写一些代码。在在那个程序中&#xff0c;我需要2个用户输入&#xff0c;它们都是数字。在第一个数字不得大于11&#xff0c;也不得小于0。在第二个…

创建Java动态代理

Java动态代理机制提供了一种有趣的方式来创建代理实例。 不过&#xff0c;创建动态代理的步骤有些繁琐&#xff0c;请考虑将代理用于审核服务实例的方法调用所花费的时间– public interface InventoryService {public Inventory create(Inventory inventory);public List<I…

html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?...

新特性&#xff1a; 1. 拖拽释放(Drag and drop) API 2. 语义化更好的内容标签&#xff08;header,nav,footer,aside,article,section&#xff09; 3. 音频、视频API(audio,video) 4. 画布(Canvas) API 5. 地理(Geolocation) API 6. 本地离线存储 localStorage 长期存储数据&am…

Substring with Concatenation of All Words 题解

题意 You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters. For example, give…

java udp丢包_linux 系统 UDP 丢包问题分析思路

最近工作中遇到某个服务器应用程序 UDP 丢包&#xff0c;在排查过程中查阅了很多资料&#xff0c;总结出来这篇文章&#xff0c;供更多人参考。在开始之前&#xff0c;我们先用一张图解释 linux 系统接收网络报文的过程。● 首先网络报文通过物理网线发送到网卡● 网络驱动程…

【SQL】分组数据,过滤分组-group by , having

学习笔记&#xff0c;原文来自http://blog.csdn.net/robinjwong/article/details/24845125 创建分组 - GROUP BY 分组是在SELECT语句的GROUP BY子句中建立的。它的作用是通过一定的规则将一个数据集划分成若干个小的区域&#xff0c;然后针对若干个小区域进行数据处理。SELECT子…