4

我正在优化文本处理软件,其中经常使用以下类:

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 的后备数组是私有的是有原因的。这种方法有哪些潜在问题?

4

5 回答 5

4

将会发生的一件事是,您正在致力于 JDK 的一种特定实现。例如,Java 7 Update 6 完全修改了对char[]. 这就是为什么只有当你的代码是非常短暂的,基本上是一次性的代码时才应该容忍这种方法。

如果您只是阅读char[],并且正在为 OpenJDK Java 7 Update 6 编写代码,则不会引入任何错误。

另一方面,全世界 95% 的 Java 程序员可能会对反映String内部结构的代码摇头表示怀疑,所以要小心 :)

于 2012-12-18T13:54:39.747 回答
3

根据java.lang.String(Java 7 Update 5 和更早版本)的版本,它使用一个后备数组,以及该count数组中实际字符串的开始索引和长度 ( )。在 Java 的这些实现中,后备数组可以(基本上)比实际字符串长,并且字符串不一定从数组的开头开始。

例如,当您使用 时substring,后备数组可能与原始字符串的后备数组相同,只是开始索引和字符数不同。因此,使用反射返回的后备数组String并非在所有情况下都有效(或者:它会导致不正确/意外的行为)。

参见例如http://www.docjar.com/html/api/java/lang/String.java.html String substring(int beginIndex, int endIndex)第 1950 行(及以下),它调用第String(int offset, int count, char value[])645 行(及以下)的构造函数。这里的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 所指出的,Java 7 的更新版本不再是这种情况。您不应该依赖 Java 的实现细节(特别是因为它可以在版本之间发生重大变化 - 如所示)。

于 2012-12-18T13:57:18.057 回答
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() : "";
    }
}

它应该给你一个答案,为什么公开类的内部私有值可能是一个坏主意。

于 2012-12-18T14:08:53.820 回答
1

如果您希望它更快,请使用String.charAt(i)它将被内联并避免任何与更改 inetrnals 相关的问题。如果您想避免从 StringBuilder 创建 String,您可以使用 CharSequence,因为它们都支持此接口。

于 2012-12-18T13:59:51.127 回答
0

getArray您可以按如下方式更改实现:

public char[] getArray() 
{
    return this.textString.toCharArray();
} 
于 2012-12-18T13:58:02.457 回答