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 フィールドを取り除くことで、このクラスのメモリ フットプリントを削減したいと考えています。

問題が 1 つあります。このクラスはコードベース全体で広く使用されているため、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);
  } 
}

実行可能な解決策のように思えますが、文字列のバッキング配列が何らかの理由でプライベートであると思われます。このアプローチの潜在的な問題は何ですか?

4

5 に答える 5

4

発生することの 1 つは、JDK の 1 つの特定の実装にコミットすることです。たとえば、Java 7 Update 6 では、char[]. これが、コードが非常に一時的で、基本的に使い捨てのコードである場合にのみ、このようなアプローチを許容する必要がある理由です。

を読んでいるだけで、char[]OpenJDK Java 7、Update 6 用にコーディングしている場合は、バグが発生することはありません。

一方、世界中の Java プログラマーの 95% は、おそらくString内部を反映するコードを信じられずに首を横に振るので、注意してください :)

于 2012-12-18T13:54:39.747 に答える
3

(Java 7 Update 5 以前)のバージョンに応じて、バッキング配列と、その配列内の実際の文字列java.lang.Stringの開始インデックスと長さ ( ) を使用します。countJava のこれらの実装では、バッキング配列は実際の文字列よりも (実質的に) 長くなる可能性があり、文字列は必ずしも配列の先頭から始まるとは限りません。

たとえば、 を使用する場合substring、バッキング配列は元の String のバッキング配列と同一である可能性がありますが、開始インデックスと文字数が異なるだけです。そのため、リフレクションを使用して のバッキング配列を返すことは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

より高速にする場合String.charAt(i)は、インライン化され、inetrnalへの変更に関する問題を回避するものを使用します。どちらもこのインターフェイスをサポートしているため、StringBuilderからStringを作成しないようにする場合は、CharSequenceを使用できます。

于 2012-12-18T13:59:51.127 に答える
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 に答える
0

getArray次のように実装を変更できます。

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