原文:使用“”还是构造函数创建Java字符串?作者:X Wang 在 Java 中,可以使用两种方法创建字符串:
String x = "abc";
String y = new String("abc");
使用双引号和使用构造函数有什么区别?
1. 双引号与双引号 构造函数
这个问题可以通过两个简单的例子来回答。示例1:String a = "abcd";
String b = "abcd";
System.out.println(a == b); // True
System.out.println(a.equals(b)); // True
a==b
true 是因为它们a
都b
引用同一个对象 - 在方法区域中声明为文字(下面的字符串文字)的字符串(我们建议读者参考我们资源中的源代码:了解 Java 的 Top 8 图,图 8)。当相同的字符串文字被创建多次时,内存中仅存储该字符串的一份副本,即它的一个实例(在我们的例子中为“abcd”)。这称为“字符串实习”。所有在编译时处理的字符串常量都会自动驻留在 Java 中。示例2:
String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d); // False
System.out.println(c.equals(d)); // True
c==d
false,因为c
它们d
引用内存中(堆上)的两个不同对象。不同的对象总是有不同的引用。该图说明了上述两种情况:
2. 程序执行阶段的interintering字符串
作者感谢 LukasEder(下面的评论是他的): 字符串驻留也可能在程序执行期间发生,即使使用构造函数创建两个字符串:String c = new String("abcd").intern();
String d = new String("abcd").intern();
System.out.println(c == d); // Now true
System.out.println(c.equals(d)); // True
3.什么时候使用双引号,什么时候使用构造函数
由于文字“abcd”始终是 String 类型,因此使用构造函数将创建额外的不必要的对象。因此,如果您只需要创建一个字符串,则应使用双引号。如果您确实需要在堆上创建一个新对象,则应该使用构造函数。此处显示了用例(原始) 。(我在下面提供了翻译后的文本。但我仍然强烈建议您熟悉此链接中评论者的代码。)JDK 6 和 JDK 7 中的 substring() 方法
JDK 6 和 JDK 7 中的 substring() 方法By X Wang JDK 6 和 JDK 7 中的方法substring(int beginIndex, int endIndex)
有所不同。了解这些差异可以帮助您更好地使用此方法。为了便于阅读,下面substring()
我们将指完整的语法,即 substring(int beginIndex, int endIndex)
。
1. substring() 的作用是什么?
该方法substring(int beginIndex, int endIndex)
返回一个以字符 number 开头beginIndex
并以字符 number 结尾的字符串endIndex-1
。
String x = "abcdef";
x = x.substring(1,3);
System.out.println(x);
输出:
bc
2. 调用 substring() 时会发生什么?
您可能知道,由于不变性x
,当将 的结果分配给 x 时x.substring(1,3)
,x
它指向一个全新的行(见图): 但是,该图并不完全正确;它没有展示堆中实际发生的情况。substring()
在 JDK 6 和 JDK 7 中, 调用时实际发生的情况有所不同。
3. JDK 6中的substring()
数组类型支持字符串类型char
。在 JDK 6 中,该类String
包含 3 个字段:char value[]
、int offset
、int count
。它们用于存储实际的字符数组、数组中第一个字符的索引、行中的字符数。当调用该方法时substring()
,它会创建一个新行,但变量的值仍然指向堆上的同一数组。两个字符串之间的区别在于它们的字符数和数组中起始字符的索引值。 下面的代码经过简化,仅包含演示问题的基础知识。
//JDK 6
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
4. JDK 6中substring()引起的问题
如果你有一个非常长的字符串,但你只需要它的一小部分,每次使用substring()
. 这将导致执行问题,因为您只需要一小部分,但仍然必须存储整个字符串。对于 JDK 6,解决方案是下面的代码,它将字符串转换为真正的子字符串:
x = x.substring(x, y) + ""
用户STepeR提出了一个问题(参见他的评论),似乎有必要添加第4点。“ substring()
JDK 6 中引起的问题”是一个更广泛的示例。我希望这将是答案,并将帮助其他人快速找出问题所在。这是代码:
String a = "aLongLongString";
String b = a.substring(1, 2);
String c = a.substring(2, 6);
因此,在 JDK 7 中b
,通过调用 aс
类型对象的方法创建的a 类型对象将引用堆中两个新创建的数组 - for 、for 。这两个新数组将与a 引用的原始数组一起存储在堆上。那些。原始数组不会在任何地方消失。现在让我们回到 JDK 6。在 JDK 6 中,堆包含单个数组。执行代码行后 String
substring()
String
L
b
ongL
c
aLongLongString
aLongLongString
String b = a.substring(1, 2);
String c = a.substring(2, 6);
对象b
引用c
堆中的同一个数组,对应于对象a
:b
- 从第一个索引到第二个索引的元素,c
- 从第二个索引到第六个索引的元素(编号从0开始,提醒)。那些。显然,b
在 JDK 6 中对变量或 c 的任何进一步访问实际上都会导致原始数组的所需元素被“计数”到堆中。在 JDK 7 中,对变量b
或 c 的任何进一步访问都将导致对已在堆中创建并“存活”的必要的较小数组的访问。那些。显然,在这种情况下,JDK 7 在物理上使用了更多内存。但让我们想象一个可能的选择:变量的某些子字符串被分配给变量b
,并且将来每个人都只使用它们 - 对象和。没有人再简单地访问变量 a;没有对它的引用(这就是本文作者的意思)。结果,在某个时间点垃圾收集器被触发,并且(当然,以最一般的形式)我们得到两种不同的情况: JDK 6:对象被销毁(垃圾收集),但是 - 原始的巨大数组堆里是活的;它被不断地使用并且。 JDK 7:对象 a 与堆中的原始数组一起被销毁。这就是 JDK 6 中可能导致内存泄漏的地方。 c
a
b
c
a
b
c
5. JDK 7 中的 substring()
该方法在JDK 7中得到了改进。在 JDK 7 中,substring()
它实际上在堆上创建了一个新数组。
//JDK 7
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}
GO TO FULL VERSION