【问题标题】:What does String.substring exactly do in Java?String.substring 在 Java 中究竟做了什么?
【发布时间】:2012-05-31 08:32:42
【问题描述】:

我一直认为如果我这样做String s = "Hello World".substring(0, 5),那么我只会得到一个新字符串s = "Hello"。这也记录在 Java API 文档中:“返回一个新字符串,它是此字符串的子字符串”

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

What is the purpose of the expression "new String(...)" in Java?

String constructor considered useless turns out to be useful after all

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

为什么? Java真的以这种方式实现子字符串吗?为什么会这样?为什么不直接返回一个全新的更短的子字符串?

【问题讨论】:

    标签: java string


    【解决方案1】:

    反过来,为什么在不需要的时候分配一个新的char[]?这是一个有效的实现,因为String 是不可变的。它总体上节省了分配和内存。

    【讨论】:

    • 换句话说,返回一个全新的更短的子字符串需要线性的额外内存和时间。将视图返回到同一个 char[] 只需要恒定的内存和恒定的时间。 (此外,您可以从子字符串视图中获取真正的副本,但不能采用其他方式——因此该选项通常更灵活。)
    【解决方案2】:

    这应该是一种效率措施。即,当您使用子字符串时,您不会创建新的 char 数组,而只是在现有 char 数组上创建一个窗口。

    这值得吗?也许。缺点是它会引起一些混乱(例如,参见this SO question),而且每个String 对象都需要将偏移信息携带到数组中,即使它没有被使用。

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

    【讨论】:

    • 但是新的子字符串无论如何都应该被视为一个新的字符串,对吧? “Hello”是“Hello World”的一部分,但它本身是一个全新的字符串。如果在您的理念中,Java 应该做所有事情,例如如果我创建 "hello",然后我创建 "hello world",那么应该从内存中删除第一个 "hello",并改用 "hello world" 的内存。跨度>
    • 并非如此。这个特定的实现并不意味着在同一个 JVM 中包含相同字符序列的每个字符串都应该指向同一个 char 数组。它所做的只是优化从子字符串派生的字符串,而不是其他方式。
    • 在 Java 7 中不再正确:它为特定字符串复制 char 数组的切片。
    • 您能否将这个答案更新到 Java 的较新版本?
    【解决方案3】:

    Java真的是这样实现subString的吗

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

    public String substring(int beginIndex, int endIndex) {
        .......
        return new String(offset + beginIndex, endIndex - beginIndex, value);
    }
    
    // Package private constructor which shares value array for speed.
    String(int offset, int count, char value[]) {
        this.value = value;
        this.offset = offset;
        this.count = count;
    }
    

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

    评论似乎暗示速度是原因

    【讨论】:

    • 您需要查看 String 构造函数,因为 Java 7 它不再需要相同的字符数组:构造函数仅复制 beginIndex 和 endIndex 之间的数组切片
    • @MarkRotteveel 实际上从 Java 7u6 IIRC 开始。这些特定于实现的答案往往很快就会变得陈旧!
    • 是的,我在回答和评论后注意到这个问题已经有一年了......
    • @MarkRotteveel 我明白了:这个答案将它推到了队列的顶部:stackoverflow.com/a/16528206/829571 ;-)
    • @MarkRotteveel 确切地说,有一种情况(在 jdk7u6 中),构造函数不复制,而是采用相同的字符数组: if ((beginIndex == 0) && (endIndex = = value.length)).
    【解决方案4】:

    虽然过去使用subString() 创建的String 具有相同的支持char[](可能是为了节省复制空间和时间)是正确的,但自从Java 7 Update 6 以来不再正确,因为这char[] 的共享有其内存开销。如果加载(大)字符串,采用小子字符串并丢弃大字符串,则尤其存在这种开销。如果小字符串保留很长时间,这可能会导致大量不必要的内存使用。

    无论如何,在当前版本(Java 7 Update 21)中,subString() 使用原始字符串的 char[] 调用构造函数 String(char value[], int offset, int count),然后构造函数从 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);
    }
    

    【讨论】:

      【解决方案5】:

      因为 String 无论如何都是不可变的。所以完全创建一个新对象没有多大意义

      【讨论】:

      • 但是新的子字符串无论如何都应该被视为一个新的字符串,对吧? “Hello”是“Hello World”的一部分,但它本身是一个全新的字符串。如果在您的理念中,Java 应该做所有事情,例如如果我创建 "hello",然后我创建 "hello world",那么应该从内存中删除第一个 "hello",并改用 "hello world" 的内存。跨度>
      • 你将如何从第一个“hello”创建“hello world”?
      【解决方案6】:

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

      另外,在执行String s = "Hello, World".substring(0,5); 时,请考虑操作顺序。首先将在堆上创建字符串“Hello, World”,并且一个全新的 String 对象将指向它。然后将在新的 String 对象和 s 实例创建并指向的另一个新 String 对象上调用 substring 方法。因此,s 指向堆“Hello, World”上的字符串,offset 为 0,count 为 5。

      【讨论】:

      • 事情可以,必须而且总是会改变!正如你所说,一切都有取舍。
      猜你喜欢
      • 2010-10-27
      • 1970-01-01
      • 2011-06-18
      • 2012-07-23
      • 2016-09-10
      • 2023-03-15
      • 2012-10-17
      • 2021-06-04
      • 1970-01-01
      相关资源
      最近更新 更多