Holen Sie sich ein Segment eines Arrays in Java, ohne ein neues Array auf dem Heap zu erstellen

181

Ich suche nach einer Methode in Java, die ein Segment eines Arrays zurückgibt. Ein Beispiel wäre, das Byte-Array zu erhalten, das das 4. und 5. Byte eines Byte-Arrays enthält. Ich möchte dafür kein neues Byte-Array im Heap-Speicher erstellen müssen. Im Moment habe ich folgenden Code:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

Ich würde gerne wissen, ob es eine Möglichkeit gibt, beispielsweise zu tun, doSomething(bigArray.getSubArray(4, 2))wo 4 der Versatz und 2 die Länge ist.

jbu
quelle
1
Was ist mit JNI-Magie in C ++? Könnte eine Katastrophe von der GC POV sein?
AlikElzin-Kilaka
Muss es ein Array von primitiven Bytes sein?
MP Korstanje

Antworten:

185

Haftungsausschluss: Diese Antwort entspricht nicht den Einschränkungen der Frage:

Ich möchte dafür kein neues Byte-Array im Heap-Speicher erstellen müssen.

( Ehrlich gesagt, ich denke, meine Antwort ist es wert, gelöscht zu werden. Die Antwort von @ unique72 ist korrekt. Imma, lass diese Bearbeitung ein bisschen stehen und dann werde ich diese Antwort löschen. )


Ich kenne keine Möglichkeit, dies direkt mit Arrays ohne zusätzliche Heap-Zuordnung zu tun, aber die anderen Antworten, die einen Unterlisten-Wrapper verwenden, haben nur eine zusätzliche Zuordnung für den Wrapper - aber nicht für das Array -, was im Fall von nützlich wäre ein großes Array.

Wenn man jedoch nach Kürze sucht, wurde die Dienstprogrammmethode Arrays.copyOfRange()in Java 6 eingeführt (Ende 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
quelle
10
Dadurch wird immer noch dynamisch ein neues Speichersegment zugewiesen und der Bereich in dieses kopiert.
Dan
4
Danke Dan - ich habe vernachlässigt, dass OP kein neues Array erstellen wollte und ich habe mir die Implementierung von nicht angesehen copyOfRange. Wenn es Closed-Source wäre, hätte es vielleicht vorbei sein können. :)
David J. Liszewski
7
Ich denke, viele Leute möchten ein Sub-Array aus einem Array erstellen und sind nicht besorgt, dass es mehr Speicher benötigt. Sie stoßen auf diese Frage und erhalten die gewünschte Antwort. Bitte nicht löschen, da dies nützlich ist. Ich denke, das ist in Ordnung.
Der einsame Kodierer
2
Tatsächlich weist copyOfRange immer noch neues Speichersegment zu
Kevingo Tsai
167

Arrays.asList(myArray)delegiert an new ArrayList(myArray), wodurch das Array nicht kopiert, sondern nur die Referenz gespeichert wird. Wenn Sie List.subList(start, end)danach verwenden, wird a erstellt, SubListdas nur auf die ursprüngliche Liste verweist (die immer noch nur auf das Array verweist). Kein Kopieren des Arrays oder seines Inhalts, nur das Erstellen eines Wrappers, und alle beteiligten Listen werden vom ursprünglichen Array unterstützt. (Ich dachte, es wäre schwerer.)

einzigartig72
quelle
9
Zur Verdeutlichung wird es an eine Privatklasse delegiert, die Arraysverwirrend aufgerufen wird ArrayList, aber tatsächlich Listein Array umgibt, im Gegensatz dazu java.util.ArrayListeine Kopie erstellen würde. Keine neuen Zuordnungen (des Inhalts der Liste) und keine Abhängigkeiten von Dritten. Dies ist meines Erachtens die richtigste Antwort.
dimo414
28
Tatsächlich funktioniert dies nicht für Arrays vom primitiven Typ, wie es das OP wollte ( byte[]in seinem Fall). Alles was du bekommst wäre List<byte[]>. Ein Wechsel byte[] bigArrayzu kann Byte[] bigArraymöglicherweise einen erheblichen Speicheraufwand verursachen.
Dmitry Avtonomov
2
Der einzige Weg, um wirklich das zu erreichen, was gewünscht wird, ist durch die sun.misc.UnsafeKlasse.
Dmitry Avtonomov
39

Wenn Sie einen Aliasing-Ansatz im Zeigerstil suchen, damit Sie nicht einmal Speicherplatz zuweisen und die Daten kopieren müssen, haben Sie meines Erachtens kein Glück.

System.arraycopy() wird von Ihrer Quelle zum Ziel kopiert, und für dieses Dienstprogramm wird Effizienz beansprucht. Sie müssen das Zielarray zuweisen.

djna
quelle
3
Ja, ich hatte auf eine Art Zeigermethode gehofft, da ich keinen Speicher dynamisch zuweisen möchte. aber es sieht so aus, als müsste ich das tun.
jbu
1
Wie @ unique72 vorschlägt, scheint es Möglichkeiten zu geben, das zu tun, was Sie wollen, indem Sie Feinheiten bei der Implementierung verschiedener Java-Listen- / Array-Typen ausnutzen. Dies scheint möglich zu sein, nur nicht explizit, und das lässt mich zögern, mich zu sehr darauf zu verlassen ...
Andrew
Warum sollte array*copy*()derselbe Speicher wiederverwendet werden? Ist das nicht genau das Gegenteil von dem, was ein Anrufer erwarten würde?
Patrick Favre
23

Eine Möglichkeit besteht darin, das Array einzuschließen java.nio.ByteBuffer einzuschließen, die absoluten Put / Get-Funktionen zu verwenden und den Puffer in Scheiben zu schneiden, um an einem Subarray zu arbeiten.

Zum Beispiel:

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

Beachten Sie, dass Sie beide aufrufen müssen wrap()und slice(), da sie wrap()nur die relativen Put / Get-Funktionen betreffen, nicht die absoluten.

ByteBuffer kann etwas schwierig zu verstehen sein, ist aber höchstwahrscheinlich effizient implementiert und es lohnt sich zu lernen.

Soulman
quelle
1
Es ist auch erwähnenswert, dass ByteBuffer-Objekte ziemlich einfach dekodiert werden können:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
Skeryl
@Soulman danke für die Erklärung, aber eine Frage ist es effizienter als zu verwenden Arrays.copyOfRange?
ucMedia
1
@ucMedia für ein Zwei-Byte-Array Arrays.copyOfRangeist wahrscheinlich effizienter. Im Allgemeinen müssten Sie für Ihren spezifischen Anwendungsfall messen.
Soulman
20

Verwenden Sie java.nio.Buffer's. Es ist ein leichter Wrapper für Puffer verschiedener primitiver Typen und hilft bei der Verwaltung von Slicing, Position, Konvertierung, Bytereihenfolge usw.

Wenn Ihre Bytes aus einem Stream stammen, können die NIO-Puffer den "Direktmodus" verwenden, der einen Puffer erstellt, der von nativen Ressourcen unterstützt wird. Dies kann in vielen Fällen die Leistung verbessern.

James Schek
quelle
14

Sie können ArrayUtils.subarray in Apache Commons verwenden. Nicht perfekt, aber etwas intuitiver als System.arraycopy. Der Nachteil ist, dass es eine weitere Abhängigkeit in Ihren Code einführt.

Seth
quelle
23
Es ist das gleiche wie Arrays.copyOfRange () in Java 1.6
newacct
10

Ich sehe, dass die Antwort auf die Unterliste bereits hier ist, aber hier ist der Code, der zeigt, dass es sich um eine echte Unterliste handelt, nicht um eine Kopie:

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

Ich glaube jedoch nicht, dass es eine gute Möglichkeit gibt, dies direkt mit Arrays zu tun.

Carl Manaster
quelle
9
List.subList(int startIndex, int endIndex)
Manuel Selva
quelle
9
Sie müssten das Array zuerst als Liste umschließen: Arrays.asList (...). Sublist (...);
Camickr
6

Eine Möglichkeit wäre, das gesamte Array sowie die Start- und Endindizes zu übergeben und zwischen diesen zu iterieren, anstatt über das gesamte übergebene Array zu iterieren.

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
quelle
6

Mit den Lists können Sie subListetwas transparent verwenden und damit arbeiten . Bei primitiven Arrays müssen Sie eine Art Offset-Limit verfolgen. ByteBuffers haben ähnliche Optionen wie ich gehört habe.

Bearbeiten: Wenn Sie für die nützliche Methode verantwortlich sind, können Sie sie einfach mit Grenzen definieren (wie bei vielen Array-bezogenen Methoden in Java selbst:

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

Es ist jedoch nicht klar, ob Sie an den Array-Elementen selbst arbeiten, z. B. etwas berechnen und das Ergebnis zurückschreiben?

akarnokd
quelle
6

Java-Referenzen verweisen immer auf ein Objekt. Das Objekt hat eine Kopfzeile, die unter anderem den konkreten Typ identifiziert (so dass Casts mit fehlschlagen könnenClassCastException ). Bei Arrays enthält der Start des Objekts auch die Länge, die Daten folgen unmittelbar danach im Speicher (technisch gesehen kann eine Implementierung frei tun, was sie will, aber es wäre dumm, etwas anderes zu tun). Sie können also keine Referenz haben, die irgendwo in ein Array zeigt.

In C zeigen Zeiger überall und auf alles, und Sie können auf die Mitte eines Arrays zeigen. Sie können jedoch nicht sicher umwandeln oder herausfinden, wie lang das Array ist. In D enthält der Zeiger einen Versatz in den Speicherblock und die Länge (oder gleichwertig einen Zeiger auf das Ende, ich kann mich nicht erinnern, was die Implementierung tatsächlich tut). Dies ermöglicht es D, Arrays zu schneiden. In C ++ hätten Sie zwei Iteratoren, die auf den Anfang und das Ende zeigen, aber C ++ ist so etwas seltsam.

Zurück zu Java, nein, das kannst du nicht. Wie bereits erwähnt, ByteBufferkönnen Sie mit NIO ein Array umbrechen und dann in Scheiben schneiden, erhalten jedoch eine umständliche Oberfläche. Sie können natürlich kopieren, was wahrscheinlich sehr viel schneller ist, als Sie denken würden. Sie können Ihre eigene Stringähnliche Abstraktion einführen , mit der Sie ein Array in Scheiben schneiden können (die aktuelle Sun-Implementierung von Stringhat eine char[]Referenz plus Startversatz und Länge, eine leistungsstärkere Implementierung hat nur die char[]). byte[]ist auf niedrigem Niveau, aber jede klassenbasierte Abstraktion, die Sie anwenden, wird die Syntax bis JDK7 (vielleicht) schrecklich durcheinander bringen.

Tom Hawtin - Tackline
quelle
Vielen Dank für die Erklärung, warum dies unmöglich wäre. Übrigens kopiert String jetzt substringin HotSpot weiter (vergessen Sie, welcher Build dies geändert hat). Warum sollte JDK7 eine bessere Syntax als ByteBuffer zulassen?
Aleksandr Dubinsky
@AleksandrDubinsky Zum Zeitpunkt des Schreibens sah es so aus, als würde Java SE 7 die Array- []Notation für benutzerdefinierte Typen wie Listund zulassen ByteBuffer.
Ich
2

@ unique72 Antwort als einfache Funktion oder Zeile müssen Sie möglicherweise Object durch den entsprechenden Klassentyp ersetzen, den Sie in Scheiben schneiden möchten. Es werden zwei Varianten angegeben, um unterschiedlichen Anforderungen gerecht zu werden.

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

Wie wäre es mit einer dünnen ListHülle?

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

(Ungetestet)

RoToRa
quelle
Dies führt zu einem Boxing-Unboxing von Bytes. Könnte langsam sein.
MP Korstanje
@mpkorstanje: In der Orable Java-Bibliothek werden ByteObjekte für alle byteWerte zwischengespeichert. Der Boxaufwand sollte also eher langsam sein.
Lii
1

Ich musste das Ende eines Arrays durchlaufen und wollte das Array nicht kopieren. Mein Ansatz war es, ein Iterable über das Array zu erstellen.

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
quelle
-1

Dies ist etwas leichter als Arrays.copyOfRange - kein Bereich oder negativ

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