web-dev-qa-db-ja.com

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

配列のセグメントを返す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が長さです。

180
jbu

免責事項:この回答は、質問の制約に準拠していません。

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

正直に、私の答えは削除する価値があると感じています。@ 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);
183

Arrays.asList(myArray)は新しいArrayList(myArray)に委任します。これは配列をコピーせず、参照を保存するだけです。その後、List.subList(start, end)を使用すると、元のリストを参照するだけのSubListが作成されます(配列はまだ参照されます)。配列またはその内容のコピーはなく、ラッパーの作成だけで、関係するすべてのリストは元の配列によってバックアップされます。 (もっと重いと思いました。)

165
unique72

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

System.arraycopy()はソースから宛先にコピーし、このユーティリティの効率が主張されます。宛先配列を割り当てる必要があります。

39
djna

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()自体は相対put/get関数にのみ影響し、絶対関数には影響しないため、slice()wrap()の両方を呼び出す必要があることに注意してください。

ByteBufferを理解するのは少し難しいかもしれませんが、おそらく効率的に実装されており、学ぶ価値があります。

21
Soulman

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

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

20
James Schek

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

14
seth

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));
    }
}

ただし、これを配列で直接行う良い方法はないと思います。

10
Carl Manaster
List.subList(int startIndex, int endIndex)
9
Manuel Selva

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

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

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

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

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

Listsを使用すると、何かのsubListを透過的に使用および操作できます。プリミティブ配列では、何らかのオフセット-制限を追跡する必要があります。 ByteBuffersには、私が聞いたのと同様のオプションがあります。

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

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

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

6
akarnokd

@ unique72は単純な関数または行として答えます。Objectを、「スライス」したいそれぞれのクラスタイプに置き換える必要がある場合があります。さまざまなニーズに合わせて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();
}
2
PicoCreator

配列の最後まで繰り返す必要があり、配列をコピーしたくありませんでした。私のアプローチは、配列上で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
Owen O'Malley

薄い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;
      }
   };
}

(未テスト)

1
RoToRa