Java String.intern()与手动字符串到标识符映射?

Java String.intern()与手动字符串到标识符映射?,java,string,string-interning,Java,String,String Interning,我记得看到过一些字符串密集型程序,它们执行大量字符串比较,但相对较少的字符串操作,并且使用单独的表将字符串映射到标识符,以实现高效的相等性和较低的内存占用,例如: public class Name { public static Map<String, Name> names = new SomeMap<String, Name>(); public static Name from(String s) { Name n = names.

我记得看到过一些字符串密集型程序,它们执行大量字符串比较,但相对较少的字符串操作,并且使用单独的表将字符串映射到标识符,以实现高效的相等性和较低的内存占用,例如:

public class Name {
    public static Map<String, Name> names = new SomeMap<String, Name>();
    public static Name from(String s) {
        Name n = names.get(s);
        if (n == null) {
            n = new Name(s);
            names.put(s, n);
        }
        return n;
    }
    private final String str;
    private Name(String str) { this.str = str; }
    @Override public String toString() { return str; }
    // equals() and hashCode() are not overridden!
}
我想我理解这一点——特别是当周围有很多相同的字符串和很多比较时——但是仅仅使用常规字符串和
intern
ing它们难道不能实现同样的效果吗?报告明确指出:


调用intern方法时,如果池中已经包含一个字符串,该字符串等于equals(object)方法确定的该字符串对象,则返回池中的字符串。否则,此字符串对象将添加到池中,并返回对此字符串对象的引用

因此,对于任意两个字符串s和t,s.intern()==t.intern()为真当且仅当s.equals(t)为真时。

那么,与使用
intern()
相比,手动管理类似
名称的类有哪些优点和缺点

到目前为止,我想到的是:

  • 手动管理映射意味着使用常规堆,
    intern()
    使用permgen
  • 手动管理地图时,您喜欢类型检查,可以验证某个内容是否为
    名称
    ,而内部字符串和非内部字符串共享相同的类型,因此在某些地方可能会忘记内部字符串
  • 依靠
    intern()
    意味着在不编写任何额外类的情况下重用现有的、经过优化、尝试和测试的机制
  • 手动管理地图会导致新用户对代码更加困惑,并且strign操作变得更加繁琐
。。。但我觉得我错过了其他的东西

与使用intern()相比,手动管理类这样的名称有哪些优点和缺点

类型检查是一个主要问题,但不变保持也是一个重要问题

名称
构造函数添加简单检查

Name(String s) {
  if (!isValidName(s)) { throw new IllegalArgumentException(s); }
  ...
}
可以确保*不存在与无效名称(如
“12#blue,,“
)对应的
名称
实例,这意味着采用
名称
作为参数并使用其他方法返回的
名称
的方法不需要担心无效的
名称
可能会爬到哪里

为了概括这个论点,假设您的代码是一座城堡,其墙壁旨在保护它免受无效输入的影响。你想让一些输入通过,所以你安装了带有防护装置的门,在输入通过时检查它们。
Name
构造函数就是一个保护的例子

String
Name
之间的区别在于
String
s无法防范。任何一段代码,无论是恶意的还是幼稚的,在外围内部还是外部,都可以创建任何字符串值。Buggy
String
操纵代码类似于城堡内的僵尸爆发。守卫无法保护不变量,因为僵尸不需要通过它们。僵尸们只是在行进中传播和破坏数据

值“是”
字符串
满足的有用不变量少于值“是”
名称

请参阅,以获取查看同一主题的另一种方式


*-通常的警告是重新反序列化
Serializable
,允许绕过构造函数。

我总是使用映射,因为
intern()
在内部字符串的字符串池中执行(可能是线性的)搜索。如果你经常这样做,它的效率就不如地图-地图是为快速搜索而制作的

那么,手动管理一个项目的优点和缺点是什么 使用intern()命名类vs

一个优点是:

因此,对于任意两个字符串s和t,s.intern()==t.intern() 当且仅当s.equals(t)为真时为真

在一个必须经常比较许多小字符串的程序中,这可能会得到回报。
而且,它最终节省了空间。考虑一个源程序,它经常使用诸如<代码> ActudioTythRealTeNoDeMeFielySerialSerial之类的名称。对于intern(),此字符串将存储一次,就是这样。如果只是引用它,那么其他所有内容都会引用,但您仍然拥有引用。

不幸的是,
String.intern()
可能比简单的同步HashMap慢。它不需要这么慢,但到今天为止,在Oracle的JDK中,它是慢的(可能是由于JNI)

另一件需要考虑的事情是:您正在编写解析器;您在一个
char[]
中收集了一些字符,需要将它们生成一个字符串。由于字符串可能是公共的,并且可以共享,因此我们希望使用池

String.intern()
使用这样的池;但要查找,首先需要一个字符串。所以我们首先需要
新字符串(char[],offset,length)

我们可以在自定义池中避免这种开销,在自定义池中可以直接基于
char[],offset,length
进行查找。例如,池是一个trie。字符串很可能在池中,因此我们将在不分配任何内存的情况下获取字符串

如果我们不想编写自己的池,但使用好的旧HashMap,我们仍然需要创建一个key对象,它包装
char[],offset,length
(类似于CharSequence)。这仍然比新字符串便宜,因为我们不复制字符。

Java 5.0和6中的String.intern()使用的perm gen空间通常具有较小的最大大小。这可能意味着即使有大量的空闲堆,也会耗尽空间

Java 7使用其在常规堆中的作用来存储插入的字符串

字符串比较非常快,我不认为在考虑开销时比较比较时间有很多优势。

这可能是另一个原因
Name(String s) {
  if (!isValidName(s)) { throw new IllegalArgumentException(s); }
  ...
}
private static final int MAX_SIZE = 10000;
private static final Map<String, String> STRING_CACHE = new LinkedHashMap<String, String>(MAX_SIZE*10/7, 0.70f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
        return size() > 10000;
    }
};

public static String intern(String s) {
    // s2 is a String equals to s, or null if its not there.
    String s2 = STRING_CACHE.get(s);
    if (s2 == null) {
        // put the string in the map if its not there already.
        s2 = s;
        STRING_CACHE.put(s2,s2);
    }
    return s2;
}
public static void main(String... args) {
    String lo = "lo";
    for (int i = 0; i < 10; i++) {
        String a = "hel" + lo + " " + (i & 1);
        String b = intern(a);
        System.out.println("String \"" + a + "\" has an id of "
                + Integer.toHexString(System.identityHashCode(a))
                + " after interning is has an id of "
                + Integer.toHexString(System.identityHashCode(b))
        );
    }
    System.out.println("The cache contains "+STRING_CACHE);
}
String "hello 0" has an id of 237360be after interning is has an id of 237360be
String "hello 1" has an id of 5736ab79 after interning is has an id of 5736ab79
String "hello 0" has an id of 38b72ce1 after interning is has an id of 237360be
String "hello 1" has an id of 64a06824 after interning is has an id of 5736ab79
String "hello 0" has an id of 115d533d after interning is has an id of 237360be
String "hello 1" has an id of 603d2b3 after interning is has an id of 5736ab79
String "hello 0" has an id of 64fde8da after interning is has an id of 237360be
String "hello 1" has an id of 59c27402 after interning is has an id of 5736ab79
String "hello 0" has an id of 6d4e5d57 after interning is has an id of 237360be
String "hello 1" has an id of 2a36bb87 after interning is has an id of 5736ab79
The cache contains {hello 0=hello 0, hello 1=hello 1}
private static final int MAX_SIZE = 10191;
private static final String[] STRING_CACHE = new String[MAX_SIZE];

public static String intern(String s) {
    int hash = (s.hashCode() & 0x7FFFFFFF) % MAX_SIZE;
    String s2 = STRING_CACHE[hash];
    if (!s.equals(s2))
        STRING_CACHE[hash] = s2 = s;
    return s2;
}
System.out.println("The cache contains "+ new HashSet<String>(Arrays.asList(STRING_CACHE)));
The cache contains [null, hello 1, hello 0]