ヒープに新しい配列を作成せずに、Javaで配列のセグメントを取得する


181

配列のセグメントを返すJavaのメソッドを探しています。例は、バイト配列の4番目と5番目のバイトを含むバイト配列を取得することです。そのためだけに、ヒープメモリに新しいバイト配列を作成する必要はありません。現在、私は次のコードを持っています:

doSomethingWithTwoBytes(byte[] twoByteArray);

void someMethod(byte[] bigArray)
{
      byte[] x = {bigArray[4], bigArray[5]};
      doSomethingWithTwoBytes(x);
}

doSomething(bigArray.getSubArray(4, 2))たとえば、4がオフセットで、2が長さであるだけの方法があるかどうか知りたいのですが。


1
C ++でJNIマジックを実行するのはどうですか?GC POVからの災害である可能性がありますか?
AlikElzin-kilaka

プリミティブバイトの配列である必要がありますか?
コルスタンジェ議員、2015

回答:


185

免責事項:この回答は質問の制約に適合していません:

そのためだけに、ヒープメモリに新しいバイト配列を作成する必要はありません。

正直に言って、私の答えは削除に値するものだと思います。@ unique72の答えは正しいです。Immaはこの編集を少し待ってから、この答えを削除します。


追加のヒープ割り当てなしで配列でこれを直接行う方法はわかりませんが、サブリストラッパーを使用する他の回答には、ラッパーのみに追加の割り当てがありますが、配列はありません。これは、大きな配列。

とはいえ、簡潔さを求めている場合、ユーティリティメソッドArrays.copyOfRange()はJava 6(2006年後半?)で導入されました。

byte [] a = new byte [] {0, 1, 2, 3, 4, 5, 6, 7};

// get a[4], a[5]

byte [] subArray = Arrays.copyOfRange(a, 4, 6);

10
これはまだ動的に新しいメモリセグメントを割り当て、範囲をその中にコピーします。
Dan

4
Danに感謝します。OPが新しい配列を作成したくなかったことを無視し、の実装についても検討しませんでしたcopyOfRange。もしそれがクローズドソースだったのなら、おそらく合格したかもしれません。:)
David J. Liszewski

7
多くの人が配列からサブ配列を作成することを望んでおり、さらに多くのメモリを使用することを心配していません。彼らはこの質問に出くわして、彼らが望む答えを得る-それはそれが役に立つので削除しないでください-それは大丈夫だと思います。
Lonely Coder

2
実際には、copyOfRangeはまだ新しいメモリセグメントを割り当てています
Kevingo Tsai

167

Arrays.asList(myArray)ArrayList(myArray)配列をコピーせず、参照のみを保存するnew にデリゲートします。List.subList(start, end)その後で使用すると、SubListは元のリストを参照するだけになります(それでも配列を参照するだけです)。配列またはその内容のコピーは行われず、ラッパーの作成のみが行われ、関係するすべてのリストは元の配列に基づいています。(もっと重くなると思いました。)


9
明確にArrays言うとArrayList、混乱を招くように呼び出されたプライベートクラスに委譲されますが、コピーを作成するのではListなく、実際には配列の周りにありjava.util.ArrayListます。(リストのコンテンツの)新しい割り当て、およびサードパーティの依存関係はありません。これが最も正しい答えだと思います。
dimo414

28
実際、これは、OPが望んだとおりのプリミティブ型配列では機能しません(byte[]彼の場合)。あなたが得るすべてはでしょうList<byte[]>。また、に変更byte[] bigArrayするとByte[] bigArray、かなりのメモリオーバーヘッドが発生する可能性があります。
ドミトリー・アヴトノモフ2014

2
真に望まれることを達成する唯一の方法は、sun.misc.Unsafeクラスを通してです。
ドミトリーアヴトノモフ2014

39

ポインタスタイルのエイリアシングアプローチを探しているので、スペースを割り当ててデータをコピーする必要さえない場合、運が悪いと思います。

System.arraycopy() コピー元からコピー先にコピーされ、このユーティリティの効率性が主張されています。宛先アレイを割り当てる必要があります。


3
はい、私は動的にメモリを割り当てたくないので、ある種のポインタメソッドを望んでいました。しかし、それが私がしなければならないことのようです。
jbu 2009

1
@ unique72が示唆しているように、さまざまなJavaリスト/配列型の実装で微妙な点を利用することで、希望どおりの方法を実行する方法があるようです。これは可能であるように見えますが、明示的な方法ではありません。そのため、依存しすぎることをためらっています...
Andrew

なぜarray*copy*()同じメモリを再利用する必要があるのですか?それは発呼者が期待するものと正反対ではありませんか?
Patrick Favre 2017年

23

1つの方法は、配列をでラップしjava.nio.ByteBuffer、絶対put / get関数を使用して、バッファをスライスしてサブ配列を処理することです。

例えば:

doSomething(ByteBuffer twoBytes) {
    byte b1 = twoBytes.get(0);
    byte b2 = twoBytes.get(1);
    ...
}

void someMethod(byte[] bigArray) {
      int offset = 4;
      int length = 2;
      doSomething(ByteBuffer.wrap(bigArray, offset, length).slice());
}

あなたは、両方を呼び出すために持っていることを注意wrap()してslice()いるので、wrap()それ自体でのみ相対プット/取得機能ではなく、絶対的なものに影響を与えます。

ByteBuffer 理解するのは少し難しいかもしれませんが、効率的に実装されている可能性が高く、学習する価値があります。


1
:それはまたのByteBufferオブジェクトがかなり容易に復号することができることは注目に値しますStandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl

@Soulmanは説明に感謝しますが、1つの質問はそれを使用するよりも効率的 Arrays.copyOfRangeですか?
ucMedia

1
2バイト配列の@ucMedia Arrays.copyOfRangeはおそらくより効率的です。一般的には、特定のユースケースを測定する必要があります。
ソウルマン

20

java.nio.Bufferを使用します。これは、さまざまなプリミティブ型のバッファの軽量ラッパーであり、スライス、位置、変換、バイト順序などの管理に役立ちます。

バイトがストリームから発生した場合、NIOバッファーは「ダイレクトモード」を使用して、ネイティブリソースによってサポートされるバッファーを作成できます。これにより、多くの場合にパフォーマンスが向上します。


14

Apache CommonsでArrayUtils.subarrayを使用できます。完璧ではありませんが、より直感的ですSystem.arraycopy. 欠点は、コードに別の依存関係を導入することです。


23
これは、Java 1.6で)Arrays.copyOfRange(と同じだ
newacct

10

subListの回答はすでにここにありますが、これはコピーではなく、真のサブリストであることを示すコードです。

public class SubListTest extends TestCase {
    public void testSubarray() throws Exception {
        Integer[] array = {1, 2, 3, 4, 5};
        List<Integer> list = Arrays.asList(array);
        List<Integer> subList = list.subList(2, 4);
        assertEquals(2, subList.size());
        assertEquals((Integer) 3, subList.get(0));
        list.set(2, 7);
        assertEquals((Integer) 7, subList.get(0));
    }
}

ただし、これを配列で直接行う良い方法があるとは思いません。



7

Listsがあなたが使用して作業できるようにするsubList透過的に何かの。プリミティブ配列では、ある種のオフセット(制限)を追跡する必要があります。ByteBuffer私が聞いたのと同様のオプションがあります。

編集: 便利なメソッドを担当している場合は、(Java自体の多くの配列関連のメソッドで行われているように)境界でそれを定義できます。

doUseful(byte[] arr, int start, int len) {
    // implementation here
}
doUseful(byte[] arr) {
    doUseful(arr, 0, arr.length);
}

ただし、配列要素自体に取り組んでいるかどうかは不明です。たとえば、何かを計算して結果を書き戻しますか?


6

1つのオプションは、配列全体と開始インデックスと終了インデックスを渡し、渡された配列全体を繰り返すのではなく、それらの間で反復することです。

void method1(byte[] array) {
    method2(array,4,5);
}
void method2(byte[] smallarray,int start,int end) {
    for ( int i = start; i <= end; i++ ) {
        ....
    }
}

6

Java参照は常にオブジェクトを指します。オブジェクトには、特に具象タイプを識別するヘッダーがあります(したがって、キャストはで失敗する可能性がありますClassCastException)。配列の場合、オブジェクトの先頭には長さも含まれ、データはメモリ内の直後に続きます(技術的には、実装は自由に実行できますが、他のことは何でもできません)。したがって、配列内のどこかを指す参照はありません。

Cでは、ポインターはどこでも何でも指し示し、配列の中央を指すことができます。しかし、安全にキャストしたり、配列の長さを調べたりすることはできません。Dでは、ポインターにはメモリブロックへのオフセットと長さが含まれます(または、末尾へのポインターと同じように、実装が実際に何を行っているか思い出せません)。これにより、Dは配列をスライスできます。C ++では、開始と終了を指す2つのイテレータがありますが、C ++は少し奇妙です。

したがって、Javaに戻ると、できません。前述のように、NIOをByteBuffer使用すると、配列をラップしてからスライスすることができますが、インターフェイスは扱いにくいものになります。もちろん、コピーすることもできます。これは、思ったよりもはるかに高速です。String配列をスライスできる独自の抽象化を導入できます(現在のSunの実装にStringは、char[]参照と開始オフセットおよび長さがありchar[]ます。より高いパフォーマンスの実装にはだけがあります)。byte[]低レベルですが、クラスベースの抽象化を行うと、JDK7(おそらく)まで、構文がひどく混乱します。


なぜそれが不可能なのか説明してくれてありがとう。ところで、String substringはHotSpotにコピーされます(どのビルドがこれを変更したかは忘れてください)。JDK7がByteBufferよりも優れた構文を許可すると言うのはなぜですか?
Aleksandr Dubinsky

@AleksandrDubinskyこれを書いている時点では、Java SE 7 []ではListandのようなユーザー定義型の配列表記が許可されていたようByteBufferです。まだ待っています...
トムホーティン-タックライン

2

@ unique72は単純な関数または行として答えます。オブジェクトを「スライス」するそれぞれのクラスタイプに置き換える必要がある場合があります。さまざまなニーズに合わせて2つのバリエーションが用意されています。

/// Extract out array from starting position onwards
public static Object[] sliceArray( Object[] inArr, int startPos ) {
    return Arrays.asList(inArr).subList(startPos, inArr.length).toArray();
}

/// Extract out array from starting position to ending position
public static Object[] sliceArray( Object[] inArr, int startPos, int endPos ) {
    return Arrays.asList(inArr).subList(startPos, endPos).toArray();
}

1

薄いListラッパーはどうですか?

List<Byte> getSubArrayList(byte[] array, int offset, int size) {
   return new AbstractList<Byte>() {
      Byte get(int index) {
         if (index < 0 || index >= size) 
           throw new IndexOutOfBoundsException();
         return array[offset+index];
      }
      int size() {
         return size;
      }
   };
}

(未テスト)


これにより、バイトのボックス化/ボックス化解除が発生します。遅いかもしれません。
コルスタンジェ議員2015

@mpkorstanje:Orable JavaライブラリではByte、すべてのbyte値のオブジェクトがキャッシュされます。したがって、ボクシングのオーバーヘッドはかなり遅くなるはずです。
Lii

1

配列の最後まで反復処理する必要があり、配列をコピーしたくありませんでした。私のアプローチは、配列に対してIterableを作成することでした。

public static Iterable<String> sliceArray(final String[] array, 
                                          final int start) {
  return new Iterable<String>() {
    String[] values = array;
    int posn = start;

    @Override
    public Iterator<String> iterator() {
      return new Iterator<String>() {
        @Override
        public boolean hasNext() {
          return posn < values.length;
        }

        @Override
        public String next() {
          return values[posn++];
        }

        @Override
        public void remove() {
          throw new UnsupportedOperationException("No remove");
        }
      };
    }
  };
}

-1

これはArrays.copyOfRangeよりも少し軽量です-範囲がないか、負の値です

public static final byte[] copy(byte[] data, int pos, int length )
{
    byte[] transplant = new byte[length];

    System.arraycopy(data, pos, transplant, 0, length);

    return transplant;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.