Raih segmen array di Java tanpa membuat array baru di heap

181

Saya mencari metode di Jawa yang akan mengembalikan segmen array. Contohnya adalah untuk mendapatkan array byte yang berisi byte 4 dan 5 byte array. Saya tidak ingin harus membuat array byte baru di memori tumpukan hanya untuk melakukan itu. Saat ini saya memiliki kode berikut:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

Saya ingin tahu apakah ada cara untuk melakukan di doSomething(bigArray.getSubArray(4, 2))mana 4 adalah offset dan 2 adalah panjang, misalnya.

jbu
sumber
1
Bagaimana dengan melakukan sihir JNI di C ++? Mungkinkah bencana dari POV GC?
AlikElzin-kilaka
Apakah harus berupa array byte primitif?
MP Korstanje

Jawaban:

185

Penafian: Jawaban ini tidak sesuai dengan batasan pertanyaan:

Saya tidak ingin harus membuat array byte baru di memori tumpukan hanya untuk melakukan itu.

( Jujur, saya merasa jawaban saya layak dihapus. Jawaban oleh @ unique72 sudah benar. Imma membiarkan edit ini berlangsung sebentar dan kemudian saya akan menghapus jawaban ini. )


Saya tidak tahu cara untuk melakukan ini secara langsung dengan array tanpa alokasi tumpukan tambahan, tetapi jawaban lain menggunakan pembungkus sub-daftar memiliki alokasi tambahan hanya untuk pembungkus - tetapi tidak array - yang akan berguna dalam kasus array yang besar.

Yang mengatakan, jika seseorang mencari singkatnya, metode utilitas Arrays.copyOfRange()diperkenalkan di Java 6 (akhir 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);
David J. Liszewski
sumber
10
ini masih mengalokasikan secara dinamis segmen memori baru dan menyalin kisaran ke dalamnya.
Dan
4
Terima kasih Dan - Saya lupa bahwa OP tidak ingin membuat array baru dan saya tidak melihat implementasi copyOfRange. Jika itu sumber tertutup, mungkin itu bisa lewat. :)
David J. Liszewski
7
Saya pikir banyak orang ingin membuat sub array dari array dan tidak perlu khawatir menggunakan lebih banyak memori. Mereka menemukan pertanyaan ini dan mendapatkan jawaban yang mereka inginkan - jadi tolong jangan hapus karena ini berguna - saya pikir tidak apa-apa.
The Lonely Coder
2
sebenarnya, copyOfRange masih mengalokasikan segmen memori baru
Kevingo Tsai
167

Arrays.asList(myArray)delegasi ke yang baru ArrayList(myArray), yang tidak menyalin array tetapi hanya menyimpan referensi. Menggunakan List.subList(start, end)setelah itu membuat SubListyang hanya mereferensikan daftar asli (yang masih hanya referensi array). Tidak ada salinan array atau isinya, hanya pembuatan wrapper, dan semua daftar yang terlibat didukung oleh array asli. (Saya pikir itu akan lebih berat.)

unik72
sumber
9
Untuk memperjelas, itu mendelegasikan ke kelas privat dalam Arrayskebingungan dipanggil ArrayList, tetapi yang benar-benar Listsekitar array, sebagai lawan java.util.ArrayListyang akan membuat salinan. Tidak ada alokasi baru (dari konten daftar), dan tidak ada ketergantungan pihak ketiga. Saya yakin ini adalah jawaban yang paling benar.
dimo414
28
Sebenarnya, ini tidak akan berfungsi untuk array tipe primitif seperti yang diinginkan OP ( byte[]dalam kasusnya). Yang akan Anda dapatkan hanyalah List<byte[]>. Dan mengubah byte[] bigArrayke Byte[] bigArraymungkin memaksakan overhead memori yang signifikan.
Dmitry Avtonomov
2
Satu-satunya cara untuk benar-benar mencapai apa yang diinginkan adalah melalui sun.misc.Unsafekelas.
Dmitry Avtonomov
39

Jika Anda mencari pendekatan aliasing gaya pointer, sehingga Anda bahkan tidak perlu mengalokasikan ruang dan menyalin data maka saya yakin Anda kurang beruntung.

System.arraycopy() akan menyalin dari sumber Anda ke tujuan, dan efisiensi diklaim untuk utilitas ini. Anda perlu mengalokasikan array tujuan.

djna
sumber
3
ya, saya berharap untuk beberapa metode pointer karena saya tidak ingin mengalokasikan memori secara dinamis. tetapi sepertinya itulah yang harus saya lakukan.
jbu
1
Seperti yang disarankan @ unique72, tampaknya ada cara untuk melakukan apa yang Anda inginkan dengan mengeksploitasi seluk-beluk dalam penerapan berbagai tipe daftar / array java. Ini tampaknya mungkin, hanya saja tidak secara eksplisit dan itu membuat saya ragu untuk mengandalkannya terlalu banyak ...
Andrew
Mengapa harus array*copy*()menggunakan kembali memori yang sama? Bukankah itu kebalikan dari apa yang diharapkan seorang penelepon?
Patrick Favre
23

Salah satu caranya adalah dengan membungkus array java.nio.ByteBuffer, menggunakan fungsi put / get absolut, dan mengiris buffer untuk bekerja pada subarray.

Misalnya:

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

Perhatikan bahwa Anda harus memanggil keduanya wrap()dan slice(), karena wrap()dengan sendirinya hanya memengaruhi fungsi put / get relatif, bukan yang absolut.

ByteBuffer bisa agak sulit untuk dipahami, tetapi kemungkinan besar dilaksanakan secara efisien, dan layak untuk dipelajari.

Soulman
sumber
1
Perlu juga dicatat bahwa objek ByteBuffer dapat dengan mudah diterjemahkan:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl
@Soulman terima kasih atas penjelasannya, tetapi satu pertanyaan apakah lebih efisien daripada menggunakan Arrays.copyOfRange?
ucMedia
1
@ucMedia untuk array dua byte, Arrays.copyOfRangemungkin lebih efisien. Secara umum, Anda harus mengukur untuk kasus penggunaan spesifik Anda.
Soulman
20

Gunakan java.nio.Buffer's. Ini adalah pembungkus ringan untuk buffer dari berbagai jenis primitif dan membantu mengelola pengirisan, posisi, konversi, pemesanan byte, dll.

Jika byte Anda berasal dari Stream, NIO Buffer dapat menggunakan "mode langsung" yang menciptakan buffer yang didukung oleh sumber daya asli. Ini dapat meningkatkan kinerja dalam banyak kasus.

James Schek
sumber
14

Anda bisa menggunakan ArrayUtils.subarray di apache commons. Tidak sempurna tetapi sedikit lebih intuitif daripada System.arraycopy. The downside adalah bahwa hal itu memperkenalkan ketergantungan lain ke dalam kode Anda.

seth
sumber
23
Ini sama dengan Arrays.copyOfRange () di Jawa 1,6
newacct
10

Saya melihat jawaban subList sudah ada di sini, tetapi di sini kode yang menunjukkan bahwa itu adalah sublist yang benar, bukan salinan:

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

Saya tidak percaya ada cara yang baik untuk melakukan ini secara langsung dengan array.

Carl Manaster
sumber
9
List.subList(int startIndex, int endIndex)
Manuel Selva
sumber
9
Pertama-tama Anda harus membungkus Array sebagai Daftar: Arrays.asList (...). Sublist (...);
camickr
7

The Lists memungkinkan Anda untuk menggunakan dan bekerja dengan subListsesuatu transparan. Array primitif akan mengharuskan Anda untuk melacak semacam batas offset. ByteBuffers memiliki opsi yang sama seperti yang saya dengar.

Sunting: Jika Anda bertanggung jawab atas metode yang berguna, Anda bisa mendefinisikannya dengan batas (seperti yang dilakukan dalam banyak metode terkait array di java itu sendiri:

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

Namun, tidak jelas apakah Anda mengerjakan sendiri elemen array, mis. Anda menghitung sesuatu dan menulis kembali hasilnya?

akarnokd
sumber
6

Salah satu opsi adalah untuk melewatkan seluruh array dan indeks awal dan akhir, dan beralih di antara mereka bukan iterasi atas seluruh array yang dilewati.

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

Referensi Java selalu menunjuk ke suatu objek. Objek memiliki header yang antara lain mengidentifikasi jenis beton (sehingga gips dapat gagalClassCastException ). Untuk array, awal objek juga termasuk panjang, data kemudian mengikuti segera setelah dalam memori (secara teknis implementasi bebas untuk melakukan apa yang diinginkan, tetapi akan sangat bodoh untuk melakukan hal lain). Jadi, Anda tidak bisa; t memiliki referensi yang menunjuk suatu tempat ke dalam array.

Dalam C pointer arahkan ke mana saja dan ke apa saja, dan Anda bisa menunjuk ke tengah array. Tetapi Anda tidak dapat dengan aman membuang atau mencari tahu berapa lama array itu. Dalam D pointer berisi offset ke blok memori dan panjang (atau setara dengan pointer ke ujung, saya tidak ingat apa implementasi sebenarnya). Ini memungkinkan D untuk mengiris array. Dalam C ++ Anda akan memiliki dua iterator yang menunjuk ke awal dan akhir, tetapi C ++ agak aneh seperti itu.

Jadi kembali ke Jawa, tidak, kamu tidak bisa. Seperti yang disebutkan, NIO ByteBuffermemungkinkan Anda untuk membungkus array dan kemudian mengirisnya, tetapi memberikan antarmuka yang canggung. Tentu saja Anda bisa menyalin, yang mungkin jauh lebih cepat daripada yang Anda bayangkan. Anda dapat memperkenalkan Stringabstraksi Anda sendiri- seperti yang memungkinkan Anda untuk mengiris array (implementasi Sun saat ini Stringmemiliki char[]referensi plus offset dan panjang mulai, implementasi kinerja yang lebih tinggi hanya memiliki char[]). byte[]tingkat rendah, tetapi abstraksi berbasis kelas apa pun yang Anda pakai yang akan membuat berantakan sintaks yang mengerikan, sampai JDK7 (mungkin).

Tom Hawtin - tackline
sumber
Terima kasih telah menjelaskan mengapa itu tidak mungkin. Btw, String sekarang menyalin di substringdalam HotSpot (lupa yang membangun mengubah ini). Mengapa Anda mengatakan bahwa JDK7 akan memungkinkan sintaks yang lebih baik daripada ByteBuffer?
Aleksandr Dubinsky
@AleksandrDubinsky Pada saat penulisan ini sepertinya Java SE 7 akan memungkinkan []notasi array pada tipe yang ditentukan pengguna, seperti Listdan ByteBuffer. Masih menunggu ...
Tom Hawtin - tackline
2

@ unique72 menjawab sebagai fungsi atau garis sederhana, Anda mungkin perlu mengganti Object, dengan tipe kelas yang ingin Anda 'slice'. Dua varian diberikan sesuai dengan berbagai kebutuhan.

/// 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();
}
PicoCreator
sumber
1

Bagaimana dengan Listbungkus tipis ?

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

(Belum dicoba)

RoToRa
sumber
Ini akan menimbulkan tinju-unboxing dari byte. Mungkin lambat.
MP Korstanje
@mpkorstanje: Dalam Byteobjek perpustakaan Java Orable untuk semua bytenilai di-cache. Jadi overhead tinju harus agak lambat.
Lii
1

Saya perlu mengulang melalui akhir array dan tidak ingin menyalin array. Pendekatan saya adalah membuat Iterable di atas array.

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");
        }
      };
    }
  };
}
Owen O'Malley
sumber
-1

Ini sedikit lebih ringan daripada Array.copyOfRange - tidak ada rentang atau negatif

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;
}
obligasi
sumber