我正在努力优化文本处理软件,其中使用了以下类:
class Sentence {
private final char[] textArray;
private final String textString;
public Sentence(String text) {
this.textArray = text.toCharArray();
this.textString = text;
}
public String getString() {
return textString;
}
public char[] getArray() {
return textArray;
}
}
正如您所看到的,存在一些冗余:textString的后备数组始终等于textArray,但两者都存储。
我希望通过删除textArray字段来减少这个类的内存占用。
有一个问题:这个类被广泛用于代码库,因此我无法摆脱getArray()方法。我的解决方案是摆脱textArray字段,并让 getArray()方法通过反射返回textSting的后备数组。
结果如下:
class Sentence {
private final String textString;
public Sentence(String text) {
this.textString = text;
}
public String getString() {
return textString;
}
public char[] getArray() {
return getBackingArrayUsingReflection(textString);
}
}
这似乎是一个可行的解决方案,但我怀疑String的支持数组是私有的。这种方法有哪些潜在的问题?
答案 0 :(得分:4)
将要发生的一件事是您正在致力于JDK的一个特定实现。例如,Java 7 Update 6完全改进了对char[]
的使用。这就是为什么只有当你的代码非常短暂,基本上是丢弃代码时才应该容忍这种方法。
如果您只是阅读char[]
,并且您正在为OpenJDK Java 7,Update 6编写代码,那么您将不会引入任何错误。
另一方面,世界上95%的Java程序员可能会对反映在String
内部的代码感到难以置信,所以要小心:)
答案 1 :(得分:3)
根据java.lang.String
(Java 7 Update 5及更早版本)的版本,它使用支持数组,以及该数组中实际字符串的开始索引和长度(count
)。在Java的这些实现中,后备数组可以(基本上)比实际字符串长,并且字符串不一定从数组的开头开始。
例如,当您使用substring
时,后备数组可能与原始String的后备数组相同,但只是具有不同的起始索引和字符数。因此,使用反射返回String
的后备数组在所有情况下都不起作用(或者:它将导致错误/意外行为)。
参见1950行(及以下)的http://www.docjar.com/html/api/java/lang/String.java.html String substring(int beginIndex, int endIndex)
示例,它在第645行(及以下)调用构造函数String(int offset, int count, char value[])
。这里char[]
直接用作后备数组,offset和count用作数组的偏移量和字符串的长度:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
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;
}
正如Marko Topolnik所指出的,更多recent versions of Java 7已不再是这种情况。您不应该依赖Java的实现细节(特别是因为它可以在版本之间发生重大变化 - 如图所示)。
答案 2 :(得分:1)
如果您的速度更快,请使用String.charAt(i)
内联,并避免更改inetrnals的任何问题。如果你想避免从StringBuilder创建String,你可以使用CharSequence,因为它们都支持这个接口。
答案 3 :(得分:1)
对于有趣和游戏,请运行以下单元测试:
public class StringTest {
private String text;
public StringTest() {
super();
}
public char[] getBackingArray() {
if (text == null) {
return null;
}
try {
final Field valueField = text.getClass().getDeclaredField("value");
valueField.setAccessible(true);
final char[] data = (char[]) valueField.get(text);
return data;
} catch (final Exception e) {
e.printStackTrace();
}
return null;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Test
public void testStringFunManipulation() {
final StringTest test = new StringTest();
test.setText("Hello World");
Assert.assertNotNull(test);
System.out.println("Original String: " + test);
System.out
.println("Original String Hash: " + test.getText().hashCode());
char[] data = test.getBackingArray();
Assert.assertNotNull(data);
System.out.println("Backing Array: " + data);
data[0] = 'J';
System.out.println("Modified String: " + test);
System.out
.println("Modified String Hash: " + test.getText().hashCode());
System.out.println("Modified String Hash Should be: "
+ "Jello World".hashCode());
}
@Override
public String toString() {
return text != null ? text.toString() : "";
}
}
它应该为您解释为什么暴露类 的内部私有值 是个坏主意。
答案 4 :(得分:0)
您可以按如下方式更改getArray
实施:
public char[] getArray()
{
return this.textString.toCharArray();
}