我知道对于Oracle Java 1.7 Update 6和更高版本,当使用String.substring
时,
将复制String的内部字符数组,对于较旧的版本,将共享它。
但是我发现没有官方的API可以告诉我当前的行为。
用例
我的用例是:
在解析器中,我想检测String.substring
是复制还是共享基础字符数组。
问题是,如果字符数组是共享的,则我的解析器需要使用new String(s)
显式地“取消共享”,以避免
内存问题。但是,如果String.substring
无论如何都复制数据,则没有必要,并且可以避免在解析器中显式复制数据。用例:
// possibly the query is very very large
String query = "select * from test ...";
// the identifier is used outside of the parser
String identifier = query.substring(14, 18);
// avoid if possible for speed,
// but needed if identifier internally
// references the large query char array
identifier = new String(identifier);
我需要的
基本上,我希望有一个静态方法
boolean isSubstringCopyingForSure()
来检测是否不需要new String(..)
。如果存在SecurityManager
,则检测不起作用,我可以。基本上,检测应该是保守的(为了避免内存问题,即使没有必要,我宁愿使用new String(..)
)。选项
我有几种选择,但是我不确定它们是否可靠,特别是对于非Oracle JVM:
检查String.offset字段
/**
* @return true if substring is copying, false if not or if it is not clear
*/
static boolean isSubstringCopyingForSure() {
if (System.getSecurityManager() != null) {
// we can not reliably check it
return false;
}
try {
for (Field f : String.class.getDeclaredFields()) {
if ("offset".equals(f.getName())) {
return false;
}
}
return true;
} catch (Exception e) {
// weird, we do have a security manager?
}
return false;
}
检查JVM版本
static boolean isSubstringCopyingForSure() {
// but what about non-Oracle JREs?
return System.getProperty("java.vendor").startsWith("Oracle") &&
System.getProperty("java.version").compareTo("1.7.0_45") >= 0;
}
检查行为
有两种选择,两者都很复杂。一种是使用自定义字符集创建一个字符串,然后使用子字符串创建一个新的字符串b,然后修改原始字符串并检查b是否也被更改。第二个选项是创建巨大的字符串,然后创建几个子字符串,并检查内存使用情况。
最佳答案
是的,确实在7u6中进行了此更改。对此没有API更改,因为此更改严格来说是实现更改,而不是API更改,也没有API来检测正在运行的JDK的行为。但是,由于更改,应用程序肯定有可能注意到性能或内存利用率的差异。实际上,编写在7u4中可以运行但在7u6中可以运行的程序并不困难,反之亦然。我们希望这种权衡对大多数应用程序都是有利的,但是毫无疑问,某些应用程序会受到这种更改的影响。
有趣的是,您担心共享字符串值(在7u6之前)的情况。从我那里听到的大多数人都有相反的担忧,他们喜欢共享,而将7u6更改为未共享的值会给他们带来麻烦(或者,他们担心会引起问题)。
无论如何,要做的是测量,而不是猜测!
首先,比较有无更改的类似JDK之间的应用程序性能,例如7u4和7u6。可能您应该查看GC日志或其他内存监视工具。如果差异可以接受,那么您就完成了!
假设7u6之前的共享字符串值引起问题,下一步是尝试new String(s.substring(...))
的简单变通方法以强制取消共享字符串值。然后测量。同样,如果两个JDK的性能都可以接受,那么您就完成了!
如果事实证明在不共享的情况下,对new String()
的额外调用是 Not Acceptable ,那么检测这种情况并使“取消共享”调用成为条件的最佳方法可能是考虑String的value
字段,该字段是char[]
,并且得到它的长度:
int getValueLength(String s) throws Exception {
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
return ((char[])field.get(s)).length;
}
考虑调用
substring()
产生的字符串,该字符串返回比原始字符串短的字符串。在共享情况下,子字符串的length()
将与如上所述检索的value
数组的长度不同。在非共享情况下,它们将是相同的。例如:String s = "abcdefghij".substring(2, 5);
int logicalLength = s.length();
int valueLength = getValueLength(s);
System.out.printf("%d %d ", logicalLength, valueLength);
if (logicalLength != valueLength) {
System.out.println("shared");
else
System.out.println("unshared");
在早于7u6的JDK上,该值的长度将为10,而在7u6或更高版本上,该值的长度将为3。当然,在两种情况下,逻辑长度均为3。