String.substring在Java中到底做什么?

String.substring在Java中到底做什么?,java,string,Java,String,我一直在想,如果我做了String s=“Hello World”。子字符串(0,5),那么我只会得到一个新字符串s=“Hello”。这也记录在JavaAPI文档中:“返回一个新字符串,该字符串是该字符串的子字符串” 但当我看到以下两个链接时,我开始怀疑 基本上,他们说如果我使用String s=“helloworld”。子字符串(0,5),我仍然会得到一个包含“helloworld”字符数组的字符串 为什么??Java真的是这样实现子字符串的吗?为什么会这样?为什么不返回一个全新的较短的子

我一直在想,如果我做了
String s=“Hello World”。子字符串(0,5)
,那么我只会得到一个新字符串
s=“Hello”
。这也记录在JavaAPI文档中:“返回一个新字符串,该字符串是该字符串的子字符串”

但当我看到以下两个链接时,我开始怀疑

基本上,他们说如果我使用
String s=“helloworld”。子字符串(0,5)
,我仍然会得到一个包含“helloworld”字符数组的字符串


为什么??Java真的是这样实现子字符串的吗?为什么会这样?为什么不返回一个全新的较短的子字符串?

如果不需要,为什么要分配一个新的
char[]
?这是一个有效的实现,因为
String
是不可变的。它在聚合中保存分配和内存。

因为字符串无论如何都是不可变的。因此,完全创建一个新对象没有多大意义

它应该是一种效率度量。i、 e.在获取子字符串时,不会创建新的字符数组,而只是在现有字符数组上创建一个窗口

这值得吗?大概缺点是它会造成一些混乱(例如,请参见),而且每个
字符串
对象都需要将偏移量信息带入数组,即使它没有被使用

编辑:从Java7开始,这种行为现在已经改变了。有关更多信息,请参阅链接答案

Java真的是这样实现子字符串的吗

看看代码(JDK 7)(我已经简化了),是的:

为什么会这样?为什么不返回一个全新的较短的子字符串


该评论似乎暗示速度是原因

请记住字符串是不可变的,它们会占用内存,如果每一个子字符串都创建了一个新字符串,请设想对一个字符串执行多个子字符串操作!相反,只需创建一个新的字符串对象,该对象指向相同的不可变字符串,但具有不同的偏移量和计数属性。现在,不管您对原始字符串或该字符串的子字符串执行多少子字符串,内存中只有该字符串本身的一个副本。效率更高


另外,在执行
String s=“Hello,World”子字符串(0,5)时考虑操作顺序。首先,将在堆上创建字符串“Hello,World”,一个全新的字符串对象将指向它。然后,substring方法将在新的String对象和另一个由
s
实例创建并指向的新String对象上调用。因此,
s
指向堆上的字符串“Hello,World”,其偏移量
为0,计数
为5。

尽管使用
子字符串()
创建的
字符串具有相同的备份
字符[]
(可能是为了节省复制空间和时间),自从Java7更新6以来,这种情况就不再存在了,因为这种共享
char[]
的方式有其内存开销。这种开销尤其存在于加载(大)字符串、获取小子字符串并丢弃大字符串的情况下。如果小字符串保留很长时间,可能会导致大量不必要的内存使用

在任何情况下,在当前版本(Java 7 Update 21)中,
subString()
调用构造函数
字符串(char value[],int offset,int count)
使用原始字符串的
char[]
,然后构造函数从char数组复制指定范围:

public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count < 0) {
        throw new StringIndexOutOfBoundsException(count);
    }
    // Note: offset or count might be near -1>>>1.
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}
公共字符串(字符值[],整数偏移量,整数计数){
如果(偏移量<0){
抛出新StringIndexOutOfBoundsException(偏移量);
}
如果(计数<0){
抛出新StringIndexOutOfBoundsException(计数);
}
//注意:偏移量或计数可能接近-1>>>1。
如果(偏移量>value.length-计数){
抛出新StringIndexOutOfBoundsException(偏移量+计数);
}
this.value=Arrays.copyOfRange(值、偏移量、偏移量+计数);
}

但无论如何,新的子字符串都应该被视为新字符串,对吗?“你好”是“你好世界”的一部分,但它本身是一个全新的字符串。如果按照你的理念,Java应该做所有事情,比如我创建“hello”
,然后我创建“hello world”
,那么第一个“hello”
应该从内存中删除,并使用“hello world”
的内存。你将如何从第一个“hello”中创建“hello world”?但无论如何,新的子字符串应该被视为新字符串,对吗?“你好”是“你好世界”的一部分,但它本身是一个全新的字符串。如果按照你的理念,Java应该做所有的事情,比如如果我创建
“hello”
,然后我创建
“hello world”
,那么第一个
“hello”
应该从内存中删除,并使用
“hello world”
的内存。这个特定的实现并不意味着在同一JVM中包含相同字符序列的每个字符串都应该指向相同的字符数组。它所做的只是优化使用子字符串派生的字符串,而不是相反。在Java 7中,它不再是真的:它复制特定字符串的字符数组片段。您能否更新有关较新版本Java的答案?换句话说,返回全新的较短子字符串需要线性额外内存和时间。将视图返回到相同的
char[]
上只需要恒定的内存和时间。(此外,您可以从子字符串视图中获得真正的副本,但不能从另一个方向获得,因此此选项通常更灵活。)您需要查看字符串构造函数,自Java7以来,它不再使用相同的字符数组:构造函数只复制beginIndex和endIndex@MarkRotteveel实际上是从Java7U6IIRC开始的。这些特定于实现的答案很快就会过时!是的,我在回答和评论这个问题后发现
public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count < 0) {
        throw new StringIndexOutOfBoundsException(count);
    }
    // Note: offset or count might be near -1>>>1.
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}