在某些情况下直接访问String的后备数组是否合理?

时间:2012-12-18 13:52:51

标签: java arrays string reflection

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

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

5 个答案:

答案 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();
}