字符内存转成字符串_字符串内存内部
字符内存转成字符串
String
类如何存储文本,内部存储和常量池如何工作。
这里要理解的要点是String
Java对象与其内容– private value
字段下的char[]
之间的区别。 String
基本上是char[]
数组的包装器,将其封装并使其无法修改,因此String
可以保持不变。 另外, String
类还记住该数组的实际部分(请参阅下文)。 这一切都意味着您可以有两个不同的String
对象(相当轻量)指向相同的char[]
。
我会告诉你一些例子,连同hashCode()
的每个String
和hashCode()
内部的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()
测试将通过。 但更重要的是, one
和two
是完全相同的引用! 因此, one == two
也是正确的。 显然,如果one
和two
指向同一个对象,则one.value
和two.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[] value
。 String
是不可变的,不需要复制已知永远不会修改的数据结构。 而且,由于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();
在打印哈希码之前。 one
和two
不仅指向同一文本,而且它们是相同的参考!
one.value: 11108810
two.value: 11108810
one: 15184449
two: 15184449
这意味着one.equals(two)
和one == two
测试都将通过。 我们还节省了一些内存,因为"abc"
文本在内存中仅出现一次(第二个副本将被垃圾回收)。
第二个练习略有不同,请查看以下内容:
String one = "abc";
String two = "abc".substring(1);
显然one
和two
是两个不同的对象,指向两个不同的文本。 但是输出如何表明它们都指向同一个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
字符内存转成字符串
字符内存转成字符串_字符串内存内部相关推荐
- linux挂载硬盘_Linux把内存挂载成硬盘提高读写速度-内存虚拟盘
Linux把内存挂载成硬盘提高读写速度-内存虚拟盘 tmpfs是一种虚拟内存文件系统正如这个定义它最大的特点就是它的存储空间在VM里面,这里提一下VM(virtual memory),VM是由linu ...
- 二维字符数组按长度排序_字符串长度 字符数组长度
1.不带转义字符的字符 如:"abc!x=/",其长度为 7 2.带转义字符的字符串 (1) 字符串"abcn":其中的'n'为转义字符(换行符),计算字符串长 ...
- 翁恺老师C语言学习笔记(十一)字符串_字符串常量
字符串常量 char* s = "Hello,world!" 我们先看一段代码 #include<stdio.h>int main(void) {char *s = & ...
- android 数组赋值字符串_字符串数组的赋值
例如: main() { chars[30]; strcpy(s,"Good News!"); /*给数组赋字符串*/ } 上面程序在编译时,遇到chars[30]这条语句时,编译 ...
- 判断字符串是否为回文字符串_字符串文字
判断字符串是否为回文字符串 String objects hold ordered sequences of bytes, typically characters, usually to form ...
- 16g电脑内存有什么好处_电脑内存8G和16G区别大吗?
展开全部 1.对于普通家庭用32313133353236313431303231363533e78988e69d8331333431363630户,4GB的内存基本上够,8G是足够的,再加到16G几乎 ...
- js怎么把函数字符串转成函数_字符串处理函数
整理一下日常用到的c++中的字符串处理函数 1.strpbrk和strspn 最近看了一个解析http请求的方法,里面用到了这俩函数,在这里记录一下.首先,http的请求报文的格式如下: strpbr ...
- 字符串_字符串数组_字符_字符数组
1.字符串 由String类型定义的变量,一个变量代表一个字符串 String str="12345679"; 做题时,遇到输入为一字符串,需要对其进行分割操作时,将字符串各个字符 ...
- python用什么方式可以打印换行字符串_字符串是一个连续的字符序列,用________方式打印出可以换行的字符串。...
[多选题]关于赋值语句的作用,错误的描述是( ) [多选题]Python中布尔变量的值为( ) [多选题]关于 Python 语言的注释,以下选项中描述正确的是( ) [其它]根据CAD原文件绘制别墅 ...
最新文章
- oracle表增加自增主键,Oracle中给已存在的表增加自增主键
- vs2012 编译 zlib 1.2.8
- 管人、管团队、管项目,如何让团队管理者成为技术团队中的发动机?
- XStream生成的XML中带class属性,去掉class属性
- P4718 【模板】Pollard-Rho算法
- 实验2-3-3 信号处理总结以及Linux下支持的信号列表
- java 弹幕游戏_JAVA 弹幕小游戏 1.0版本
- acs880变频器静态辨识_(完整版)ABB-ACS880变频器调试参数
- cadence SPB17.4 allegro + CAM350 10.5 / 10.7 / 14.6 出拼板
- 在IDEA中调试JavaScript代码
- “差不多先生”姚劲波和不再神奇的58同城
- python基本快捷键
- 极兔速递电子面单API接口-快递鸟
- 10年涨7倍!纵观南京房价变迁史,看哭多少买房人!
- 苹果大中华区营收同比增48% iPhone销量翻番
- MAK代理激活的使用方法和注意事项
- 谓词函数和谓词函数对象_称为形容词的函数
- Delaunay三角网之逐点插入法
- P1359 租用游艇【Floyd】
- python数据分析用到的库_用python进行数据分析的五个最常用库