Öbek üzerinde yeni bir dizi oluşturmadan Java'da bir dizinin bir bölümünü alın


181

Java bir dizi bir segment döndürecek bir yöntem arıyorum. Bir örnek, bir bayt dizisinin 4. ve 5. baytlarını içeren bayt dizisini elde etmektir. Sadece bunu yapmak için yığın bellekte yeni bir bayt dizisi oluşturmak istemiyorum. Şu anda aşağıdaki kod var:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

doSomething(bigArray.getSubArray(4, 2))Örneğin, 4'ün ofset ve 2'nin uzunluk olduğu yerde yapmanın bir yolu olup olmadığını bilmek istiyorum .


1
C ++ 'da JNI büyüsü yapmaya ne dersiniz? GC POV'dan bir felaket olabilir mi?
AlikElzin-kilaka

Bir dizi ilkel bayt olmalı mı?
MP Korstanje

Yanıtlar:


185

Feragatname: Bu cevap sorunun sınırlamalarına uymamaktadır:

Sadece bunu yapmak için yığın bellekte yeni bir bayt dizisi oluşturmak istemiyorum.

( Dürüst olmak gerekirse, cevabımın silinmeye layık olduğunu hissediyorum. @ Unique72'nin cevabı doğru. Imma bu düzenlemenin biraz oturmasına izin verin ve sonra bu cevabı sileceğim. )


Ek yığın ayırma olmadan doğrudan diziler ile bunu yapmanın bir yolunu bilmiyorum, ancak bir alt liste sarıcı kullanarak diğer yanıtlar yalnızca sarıcı için ek ayırma var - ancak dizi - hangi durumda yararlı olacaktır büyük bir dizi.

Bununla birlikte, eğer kısalık aranıyorsa, yarar yöntemi Arrays.copyOfRange()Java 6'da (2006'nın sonlarında?) Tanıtıldı:

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
bu yine de dinamik olarak yeni bir bellek segmenti ayırır ve aralığı buna kopyalar.
Dan

4
Teşekkürler Dan - OP yeni dizi oluşturmak istemediğini ihmal ettim ve uygulanmasına bakmadım copyOfRange. Eğer kapalı kaynak olsaydı belki geçebilirdi. :)
David J. Liszewski

7
Bence bir çok insan bir diziden bir alt dizi oluşturmak istiyor ve biraz daha bellek kullandığından endişelenmiyor. Bu soruya rastlıyorlar ve istedikleri cevabı alıyorlar - bu yüzden lütfen yararlı olduğu için silmeyin - bence sorun yok.
Lonely Coder

2
Aslında, copyOfRange hala yeni bellek segmenti tahsis ediyor
Kevingo Tsai

167

Arrays.asList(myArray)yeniye delegeler ArrayList(myArray)dizi kopyalamak ama sadece referans saklar vermez. Bundan List.subList(start, end)sonra kullanmak , SubListsadece orijinal listeye (ki yine de diziye referans verir) atıfta bulunur. Dizinin veya içeriğinin kopyalanması yok, sadece sarıcı oluşturma ve ilgili tüm listeler orijinal dizi tarafından destekleniyor. (Daha ağır olacağını düşündüm.)


9
Açıklığa kavuşturmak için, Arrayskafa karıştırıcı bir şekilde özel bir sınıfaArrayList , ancak gerçekten Listbir dizi oluşturacak, aksine java.util.ArrayListbir kopya oluşturacaktır. Yeni bir ayırma (listenin içeriğinin) ve üçüncü taraf bağımlılıkları yok. Bence en doğru cevap bu.
dimo414

28
Aslında, bu OP'nin istediği ilkel tip diziler için işe yaramaz ( byte[]durumunda). Tek alacağın List<byte[]>. Ve değişen byte[] bigArrayiçinByte[] bigArray önemli bir bellek yükü getirebilir.
Dmitry Avtonomov

2
İstenen şeyi gerçekten başarmanın tek yolu sun.misc.Unsafesınıftır.
Dmitry Avtonomov

39

İşaretçi stili takma adlandırma yaklaşımı arıyorsanız, alan ayırmanıza ve verileri kopyalamanıza bile gerek kalmazsa, şansın olmadığını düşünüyorum.

System.arraycopy() kaynağınızdan hedefe kopyalanır ve bu yardımcı program için verimlilik talep edilir. Hedef diziyi ayırmanız gerekir.


3
Evet, dinamik olarak bellek ayırmak istemediğim için bir tür işaretçi yöntemi umuyordum. ama öyle yapmam gerekecek gibi görünüyor.
jbu

1
@ Unique72'nin de belirttiği gibi, çeşitli java listesi / dizi türlerinin uygulanmasında inceliklerden yararlanarak istediğinizi yapmanın yolları var gibi görünüyor. Bu mümkün görünüyor, sadece açık bir şekilde değil ve bu bana çok fazla güvenmekten çekiniyor ...
Andrew

Neden array*copy*()aynı hafızayı tekrar kullanmalıyım? Arayanın bekleyebileceği tam tersi değil mi?
Patrick Favre

23

Bunun bir yolu diziyi java.nio.ByteBuffer mutlak put / get işlevlerini kullanmak ve bir alt çalışmak için arabelleği dilimlemektir.

Örneğin:

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

Not Her iki aramak zorunda olduğunu wrap()ve slice()bu yana, wrap()sadece göreli put etkiler tek başına / fonksiyonları, mutlak olanları olsun.

ByteBuffer anlaşılması biraz zor olabilir, ancak büyük olasılıkla etkili bir şekilde uygulanır ve öğrenmeye değer.


1
Ayrıca, ByteBuffer nesnelerinin oldukça kolay bir şekilde deşifre edilebileceğini belirtmek gerekir:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl

@Soulman açıklama için teşekkürler, ama bir soru kullanmaktan daha verimli Arrays.copyOfRangemi?
ucMedia

1
@ucMedia iki baytlık bir dizi için, Arrays.copyOfRangemuhtemelen daha verimlidir. Genel olarak, özel kullanım durumunuz için ölçüm yapmanız gerekir.
Soulman

20

Java.nio.Buffer kullanın. Çeşitli ilkel tipte tamponlar için hafif bir sargıdır ve dilimleme, konum, dönüştürme, bayt sıralaması vb.

Baytlarınız bir Akıştan kaynaklanıyorsa, NIO Tamponları yerel kaynaklarla desteklenen bir tampon oluşturan "doğrudan mod" kullanabilir. Bu, birçok durumda performansı artırabilir.


14

Sen kullanabilirsiniz ArrayUtils.subarray apache commons içinde. Mükemmel değil ama biraz daha sezgisel System.arraycopy. dezavantajı, kodunuza başka bir bağımlılık getirmesidir.


23
Java 1.6'daki Arrays.copyOfRange () ile aynı
newacct

10

Alt liste cevabının zaten burada olduğunu görüyorum, ancak bunun bir kopya değil, gerçek bir alt liste olduğunu gösteren kod:

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

Ancak bunu doğrudan dizilerle yapmanın iyi bir yolu olduğuna inanmıyorum.


9
List.subList(int startIndex, int endIndex)

9
Önce Diziyi bir Liste olarak sarmanız gerekir: Arrays.asList (...). Sublist (...);
camickr

7

ListS ile ve çalışmayı kullanma izin subListşeffaf bir şeyin. İlkel diziler bir tür ofset sınırını izlemenizi gerektirir. ByteBufferduyduğum gibi seçenekler var.

Düzenleme: Yararlı yöntemden sorumlu iseniz, sadece sınırları ile tanımlayabilirsiniz (java kendisi birçok diziyle ilgili yöntemlerde olduğu gibi:

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

Bununla birlikte, dizi öğelerinin kendileri üzerinde çalışıyorsanız, örneğin bir şey hesaplayıp sonucu yazdığınızda net değil mi?


6

Bir seçenek, tüm diziyi ve başlangıç ​​ve bitiş indekslerini iletmek ve geçirilen tüm dizi üzerinde yineleme yapmak yerine yineleme yapmak olacaktır.

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 başvuruları her zaman bir nesneyi gösterir. Nesnenin, diğer şeylerin yanı sıra beton tipini tanımladığı bir başlığı vardır (böylece dökümler başarısız olabilirClassCastException ). Diziler için, nesnenin başlangıcı da uzunluğu içerir, daha sonra veriler bellekte hemen sonra gelir (teknik olarak bir uygulama, istediği şeyi yapmakta özgürdür, ancak başka bir şey yapmak zor olacaktır). Yani, t bir yerde bir dizi işaret eden bir referans var.

C işaretçileri herhangi bir yeri ve her şeyi gösterir ve bir dizinin ortasını gösterebilirsiniz. Ancak dizinin ne kadar sürdüğünü güvenli bir şekilde yayınlayamaz veya bulamazsınız. D işaretçi bellek bloğu ve uzunluğu bir ofset içerir (veya eşdeğer sonuna bir işaretçi, ben uygulamanın gerçekte ne yaptığını hatırlayamıyorum). Bu, D'nin dizileri dilimlemesine izin verir. C ++ 'da başlangıç ​​ve bitiş gösteren iki yineleyiciniz olacaktır, ancak C ++ bunun gibi biraz tuhaftır.

Java'ya dönersek, hayır. Belirtildiği gibi, NIO ByteBufferbir diziyi sarmanıza ve sonra dilimlemenize izin verir, ancak garip bir arayüz sağlar. Elbette kopyalayabiliyorsunuz, ki bu muhtemelen düşündüğünüzden çok daha hızlı. StringBir diziyi dilimlemenize izin veren kendi benzeri soyutlamalarınızı tanıtabilirsiniz (mevcut Sun uygulamasının Stringbir char[]referansı artı bir başlangıç ​​ofseti ve uzunluğu vardır, daha yüksek performans uygulaması sadece char[]). byte[]düşük seviyededir, ancak JDK7'ye (belki de) kadar sözdiziminde kötü bir karışıklık yaratacaktır.


Neden imkansız olacağını açıkladığınız için teşekkürler. Btw, String şimdi HotSpot'ta kopyalanıyor substring(bunu hangi yapının değiştirdiğini unut). Neden JDK7'nin ByteBuffer'dan daha iyi sözdizimine izin vereceğini söylüyorsunuz?
Aleksandr Dubinsky

Bunu yazma zamanda @AleksandrDubinsky Java SE 7 dizi izin gidiyordu benziyordu []gibi kullanıcı tanımlı tipler, üzerinde gösterim Listve ByteBuffer. Hala bekliyor ...
Tom Hawtin - tackline

2

@ unique72 cevabı basit bir fonksiyon veya çizgi olarak, Object'i dilimlemek istediğiniz sınıf tipiyle değiştirmeniz gerekebilir. Çeşitli ihtiyaçlara uygun iki değişken verilmiştir.

/// 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

İnce bir Listsargıya ne dersiniz ?

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

(Test edilmemiş)


Bu, baytların boks-kutudan çıkarılmasına neden olacaktır. Yavaş olabilir.
MP Korstanje

@mpkorstanje: Orable Java kitaplığında Bytetüm bytedeğerler için nesneler önbelleğe alınır. Yani boks yükü oldukça yavaş olmalı.
Lii

1

Bir dizinin sonuna kadar yinelemek gerekiyordu ve dizi kopyalamak istemiyordu. Benim yaklaşımım dizi üzerinde tekrarlanabilir yapmaktı.

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

Bu, Arrays.copyOfRange'den biraz daha hafif - aralık veya negatif yok

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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.