【问题标题】:how the subString() function of string class worksstring 类的 subString() 函数是如何工作的
【发布时间】:2010-10-16 19:13:55
【问题描述】:

请看下面的代码。

String s = "Monday";
if(s.subString(0,3).equals("Mon"){}

String s2 = new String(s.subString(0,3));
String s3 = s.subString(0,3);  

我知道第 2 行仍将指向“星期一”,并有一个新的 String 对象,其偏移量和计数设置为 0,3。

第 4 行将在字符串池中创建一个新的字符串“Mon”并指向它。

但不确定第 5 行的行为会像第 2 行还是第 4 行。

如果我对第 2 行或第 4 行的错误也请更正..

【问题讨论】:

  • 我在想,我的答案在哪里丢失了。然后我意识到我在牧场回答了。 :)
  • String的那个方法全小写:substring()

标签: java string


【解决方案1】:

阅读此https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/String.html

“返回一个新字符串...”

【讨论】:

  • 我认为问题在于字符串何时共享底层 char 数组以及何时不共享。
【解决方案2】:

正如 Pete Kirkham 所指出的,这是特定于实现的。我的回答仅适用于 Sun JRE,并且仅在 Java 7 更新 6 之前。

您是对的,普通的substring 调用只是创建一个新字符串,该字符串引用与原始字符串相同的字符数组。这也是第 5 行发生的情况。新的字符串对象引用恰好被分配给变量这一事实不会改变方法的行为。

为了清楚起见,您说在第 2 行中,新字符串仍将指向“Monday”——字符串内的 char 数组引用将指向与“Monday”使用的相同的 char 数组。但是“星期一”本身就是一个字符串,而不是一个字符数组。换句话说,到第 2 行完成(并忽略 GC)时,有两个字符串对象,它们都引用同一个 char 数组。一个计数为 6,另一个计数为 3;两者的偏移量都是 0。

不过,第 4 行使用“字符串池”是错误的——那里没有进行池化。但是,它与其他线路不同。当您调用String(String) 构造函数时,新字符串将获取原始字符数据的副本,因此它是完全独立的。如果您只需要一个包含非常大原始字符串的一小部分的字符串,这将非常有用;它允许在您保留小部分的副本时对原始的大型 char 数组进行垃圾收集(假设没有其他需要)。根据我自己的经验,一个很好的例子是从一行中读取行。默认情况下,BufferedLineReader 将使用 80 个字符的缓冲区读取行,因此返回的每个字符串都将使用至少 80 个字符的 char 数组。如果您正在阅读大量非常短的行(单个单词),那么仅通过使用奇怪的外观就可以看出内存消耗方面的差异

line = new String(line);

可能非常重要。

这有帮助吗?

【讨论】:

【解决方案3】:

在第 5 行---->s3=Mon .

【讨论】:

    【解决方案4】:

    我知道第 2 行仍将指向“星期一”,并有一个新的 String 对象,其偏移量和计数设置为 0,3。

    目前对于 Sun JRE 实现来说也是如此。我似乎记得过去的 Sun 实现不是这样,JVM 的其他实现也不是这样。不要依赖未指定的行为。 GNU 类路径可能会复制数组(我不记得使用什么比率来决定何时复制,但如果副本是原始数据的足够小部分,它确实会复制,这将一个不错的 O(N) 算法转变为O(N^2))。

    第 4 行将在字符串池中创建一个新的字符串“Mon”并指向它。

    不,它会在堆中创建一个新的字符串对象,遵循与任何其他对象相同的垃圾收集规则。它是否共享相同的底层字符数组取决于实现。不要依赖未指定的行为。

    String(String) 构造函数说:

    初始化一个新创建的字符串对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。

    String(char[]) 构造函数说:

    分配一个新的字符串,以便它表示当前包含在字符数组参数中的字符序列。 字符数组的内容被复制;随后对字符数组的修改不会影响新创建的字符串。

    遵循良好的 OO 原则,String 的任何方法实际上都不需要使用字符数组来实现,因此String 的规范中没有任何部分要求对字符数组进行操作。将数组作为输入的那些操作指定将数组的 内容 复制到字符串中使用的任何内部存储。字符串可以在内部使用 UTF-8 或 LZ 压缩并符合 API。

    但是,如果您的 JVM 没有进行小比率子字符串优化,那么当您使用 new String(String) 时,它有可能只复制相关部分,所以这是一个尝试看看是否它改善了内存使用。并非所有影响 Java 运行时的东西都由 Java 定义。

    要获取字符串池中的字符串equal 到一个字符串,使用intern() 方法。如果具有该值的字符串已经被实习,这将从池中检索一个字符串,或者创建一个新字符串并将其放入池中。请注意,池化字符串具有不同的(同样取决于实现)垃圾回收行为。

    【讨论】:

    • 感谢皮特的精彩解释。 “新创建的字符串是参数字符串的副本。”那么参数字符串存储在哪里。
    • 像 Java 中的任何对象一样,它存储在堆中,直到不再被引用,然后在某个时候被垃圾收集。我见过的所有实现都使用 char 数组来存储字符串中的字符,有些实现在字符串之间共享这个数组有不同的规则...
    • ... 但我不记得 String(String) 最终的数组长度与新字符串的长度不同的地方。所以参数内部使用的数组可以被复制也可以被引用,但这取决于实现。
    【解决方案5】:

    在 Sun 的实现中,String 对象有一个private final char value[] 字段。通过调用 substring() 创建新 String 时,不会创建新的 char 数组,新实例使用原始对象的 value。第 2 行和第 5 行就是这种情况,新的 String 对象将使用 s 的 char 数组。

    如果字符串长度小于 char 数组的总长度value,构造函数 String(String) 会创建一个新的 char 数组。所以第 4 行创建的 String 将使用一个新的 char 数组。

    你应该看看构造函数public String(String original)的source code,真的很简单。

    【讨论】:

      【解决方案6】:

      注意:从 Sun/Oracle 的 Java 中的 Java 7 update 6 开始,由 String.substring 创建的 String 不再共享父级的 char 数组。决定这种优化很少有好处,并且不能证明offsetcount 字段的成本和复杂性是合理的。

      一些链接:

      【讨论】:

        【解决方案7】:

        “子字符串通过获取原始字符串的一部分从源字符串中创建一个新对象”。

        在Java 1.7之前,子字符串持有原始字符数组的引用,这意味着即使是5个字符长的子字符串,也可以通过持有强引用来防止1GB字符数组被垃圾回收。

        这个问题在 Java 1.7 中得到修复,其中不再引用原始字符数组,但这种变化也使得创建子字符串在时间上有点昂贵。早些时候它在 O(1) 的范围内,在 Java 7 上最坏的情况可能是 O(n)。

        【讨论】:

          猜你喜欢
          • 2010-10-27
          • 2020-12-25
          • 1970-01-01
          • 1970-01-01
          • 2011-04-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-28
          相关资源
          最近更新 更多