Java 如何存储字符串以及子字符串如何在内部工作? [关闭]

Posted

技术标签:

【中文标题】Java 如何存储字符串以及子字符串如何在内部工作? [关闭]【英文标题】:How does Java store Strings and how does substring work internally? [closed] 【发布时间】:2012-12-21 01:00:20 【问题描述】:
class StringTesting 
    public static void main(String args[])
    
        String str = "abcd";
        String str1 = new String("abcd");
        String str2 = str.substring(0,2);
        String str3 = str.substring(0,2);
        String str4 = str.substring(0,str.length());
        String str5 = str1.substring(0,2);
        String str6 = str1.substring(0,2);
        String str7 = str1.substring(0,str1.length());

        System.out.println(str2 == str3);
        System.out.println(str == str4);
        System.out.println(str5 == str6);
        System.out.println(str1 == str7);
    

这是我在 java 1.6.0_27 上得到的输出:

false
true
false
true

有人可以解释一下输出吗?我知道Java区分存储在堆中的字符串和存储在字符串“公共池”(可以被实习)中的字符串。在内部,它们的表示方式有何不同。它如何改变子字符串算法。 请在适当的地方引用书籍/文章/博客等。

【问题讨论】:

这些问题很多。对于新旧 JDK,它甚至不会是相同的答案。 如果您真的想知道它是如何工作的,请阅读源代码。你不会得到更好的参考。注意:这在 Java 7 更新 6 中已更改。;) @dystroy:这就是为什么我在问题中给出了我的 Java 版本。我不介意旧的或最近的 JDK 的答案。 它在你的 JDK 中 src.zip 下(或者你可以用谷歌搜索它),如果你有一个不错的 IDE,你只需点击 String 的源代码就可以看到它。 这是一些相关答案的链接。可能是this one、this one 和this one。我删除了我的评论,因为我觉得没有它很容易找到这些问题,而且我不想淹没你的问题。 【参考方案1】:

查看 cmets:

    String str = "abcd";  // new String LITERAL which is interned in the pool
    String str1 = new String("abcd"); // new String, not interned: str1 != str
    String str2 = str.substring(0,2); // new String which is a view on str
    String str3 = str.substring(0,2); // same: str3 != str2
    String str7 = str1.substring(0,str1.length()); // special case: str1 is returned

注意事项:

从 Java 7u6 开始,子字符串返回一个新字符串,而不是原始字符串的视图(但这对于该示例没有影响)

调用str1.substring(0,str1.length());时的特殊情况-见代码:

public String substring(int beginIndex, int endIndex) 
    //some exception checking then
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);

编辑

什么是视图?

在 Java 7u6 之前,String 基本上是一个 char[],它包含带有偏移量和计数的字符串字符(即字符串由从 @987654331 中的 offset 位置开始的 count 字符组成@)。

调用 substring 时,会使用相同的 char[] 创建一个新字符串,但偏移量/计数不同,以有效地在原始字符串上创建视图。 (如上所述,当 count = length 和 offset = 0 时除外)。

从java 7u6开始,每次都会创建一个新的char[],因为字符串类中没有countoffset字段。

公共池具体存储在哪里?

这是特定于实现的。在最近的版本中,池的位置实际上已经移动。在较新的版本中,它存储在堆上。

池是如何管理的?

主要特点:

字符串字面量存储在池中 内部字符串存储在池中 (new String("abc").intern();) 当一个字符串S被保留时(因为它是一个字面量或者因为intern()被调用了),如果池中存在一个从equals到@987654340的字符串的引用,JVM将返回一个引用@(因此 "abc" == "abc" 应该始终返回 true)。 可以对池中的字符串进行垃圾回收(这意味着如果已满,则可能会在某个阶段从池中删除一个留存的字符串)

【讨论】:

什么是视图,公共池具体存储在哪里以及如何管理? @Bruce 什么是视图? => 它是特定于实现的,因此您必须查看 Java 6 的代码并与 Java 7 进行比较(找不到更新 6 或更新的链接)。你也可以看到this discussion。 @Bruce String pool => oracle.com/technetwork/java/javase/…(搜索实习生):在 JDK 7 中,在 Java 的永久代中不再分配实习字符串堆,而是分配在 Java 堆的主要部分(称为年轻代和年老代),以及应用程序创建的其他对象。 @Bruce "如何在运行时访问字符串池" => docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.4 非常感谢您的链接!【参考方案2】:

String 是不可变对象。

String#subString - 创建一个新的 String 。 Source

在代码中是 [open jdk 6] -

 public String substring(int beginIndex, int endIndex) 
    if (beginIndex < 0) 
        throw new StringIndexOutOfBoundsException(beginIndex);
    
    if (endIndex > value.length) 
        throw new StringIndexOutOfBoundsException(endIndex);
    
    int subLen = endIndex - beginIndex;
    if (subLen < 0) 
        throw new StringIndexOutOfBoundsException(subLen);
    
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);

【讨论】:

substring 永远不会在字符串字面量池中创建新对象,但它可能会返回已在字符串字面量池中的相同对象。 来源 - 我检查了来源link @Quoi 源码显示新建了一个字符串,字符串池中没有... @assylias - 在 java 7 中,我签入了 openjdk 6。:) @Quoi 在 Java 6 中也创建了一个新字符串 - 只是底层的 char[] 是共享的(在最近的 JDK 中不是这种情况)。

以上是关于Java 如何存储字符串以及子字符串如何在内部工作? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

贪婪/懒惰(非贪婪)/占有量词如何在内部工作? [复制]

如何在内部存储android java中下载和保存媒体文件

在 Java 中,整数如何在内部以位级别表示?

分段错误如何在内部(内核/硬件)工作?

ConcurrentHashMap 如何在内部工作?

Javascript子字符串是虚拟的吗?