Tome un segmento de una matriz en Java sin crear una nueva matriz en el montón

181

Estoy buscando un método en Java que devolverá un segmento de una matriz. Un ejemplo sería obtener la matriz de bytes que contiene los bytes 4 y 5 de una matriz de bytes. No quiero tener que crear una nueva matriz de bytes en la memoria de almacenamiento dinámico solo para hacerlo. En este momento tengo el siguiente código:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

Me gustaría saber si había una manera de hacer simplemente doSomething(bigArray.getSubArray(4, 2))donde 4 es el desplazamiento y 2 es la longitud, por ejemplo.

jbu
fuente
1
¿Qué hay de hacer algo de magia JNI en C ++? ¿Podría ser un desastre del POV de GC?
AlikElzin-kilaka
¿Tiene que ser una matriz de bytes primitivos?
MP Korstanje

Respuestas:

185

Descargo de responsabilidad: esta respuesta no se ajusta a las restricciones de la pregunta:

No quiero tener que crear una nueva matriz de bytes en la memoria de almacenamiento dinámico solo para hacerlo.

( Honestamente, siento que mi respuesta es digna de borrarse. La respuesta de @ unique72 es correcta. Imma deja que esta edición se asiente por un momento y luego borraré esta respuesta ) .


No conozco una manera de hacer esto directamente con matrices sin asignación de montón adicional, pero las otras respuestas que usan un contenedor de sublista tienen asignación adicional solo para el contenedor, pero no la matriz, lo que sería útil en el caso de Una gran variedad.

Dicho esto, si uno busca la brevedad, el método de utilidad Arrays.copyOfRange()se introdujo en Java 6 (¿a finales de 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
fuente
10
esto todavía asigna dinámicamente un nuevo segmento de memoria y copia el rango en ese.
Dan
44
Gracias Dan: descuidé que OP no quería crear una nueva matriz y no miré la implementación de copyOfRange. Si fuera de código cerrado, tal vez podría haber pasado. :)
David J. Liszewski
77
Creo que mucha gente quiere crear una submatriz a partir de una matriz y no les preocupa que use más memoria. Se encuentran con esta pregunta y obtienen la respuesta que desean, así que no la eliminen, ya que es útil. Creo que está bien.
The Lonely Coder
2
En realidad, copyOfRange todavía asigna un nuevo segmento de memoria
Kevingo Tsai
167

Arrays.asList(myArray)delega a new ArrayList(myArray), que no copia la matriz sino que solo almacena la referencia. Usar List.subList(start, end)después de eso hace SubListque solo haga referencia a la lista original (que todavía solo hace referencia a la matriz). No se copia la matriz ni su contenido, solo se crea el contenedor, y todas las listas involucradas están respaldadas por la matriz original. (Pensé que sería más pesado).

único72
fuente
9
Para aclarar, delega a una clase privada en Arraysuna llamada confusa ArrayList, pero que realmente es una Listmatriz, a diferencia de la java.util.ArrayListque haría una copia. No hay nuevas asignaciones (del contenido de la lista) ni dependencias de terceros. Esta es, creo, la respuesta más correcta.
dimo414
28
En realidad, esto no funcionará para matrices de tipos primitivos como lo quería el OP ( byte[]en su caso). Todo lo que obtendrás sería List<byte[]>. Y cambiar byte[] bigArraya Byte[] bigArraypodría imponer una sobrecarga de memoria significativa.
Dmitry Avtonomov
2
La única forma de lograr realmente lo que se desea es a través de la sun.misc.Unsafeclase.
Dmitry Avtonomov
39

Si está buscando un enfoque de alias de estilo de puntero, de modo que ni siquiera necesite asignar espacio y copiar los datos, entonces creo que no tiene suerte.

System.arraycopy() copiará desde su origen hasta su destino, y se reclama la eficiencia de esta utilidad. Es necesario asignar la matriz de destino.

djna
fuente
3
Sí, esperaba algún tipo de método de puntero ya que no quiero asignar memoria dinámicamente. pero parece que eso es lo que voy a tener que hacer.
jbu
1
Como sugiere @ unique72, parece haber formas de hacer lo que quiere explotando sutilezas en la implementación de varios tipos de lista / matriz de Java. Esto parece ser posible, pero no de manera explícita y eso me hace dudar de confiar demasiado en él ...
Andrew
¿Por qué debería array*copy*()reutilizar la misma memoria? ¿No es exactamente lo contrario de lo que esperaría una persona que llama?
Patrick Favre el
23

Una forma es envolver la matriz java.nio.ByteBuffer, usar las funciones absolutas put / get y cortar el búfer para trabajar en una submatriz.

Por ejemplo:

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

Tenga en cuenta que debe llamar a ambos wrap()y slice(), dado que wrap()solo afecta a las funciones put / get relativas, no a las absolutas.

ByteBuffer puede ser un poco difícil de entender, pero lo más probable es que se implemente de manera eficiente y valga la pena aprenderlo.

Alma de hombre
fuente
1
También vale la pena señalar que los objetos ByteBuffer se pueden decodificar con bastante facilidad:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl
@Soulman, gracias por la explicación, pero una pregunta es más eficiente que usar Arrays.copyOfRange?
ucMedia
1
@ucMedia para una matriz de dos bytes, Arrays.copyOfRangees probablemente más eficiente. En general, tendría que medir para su caso de uso específico.
Soulman
20

Utilice java.nio.Buffer's. Es un envoltorio liviano para buffers de varios tipos primitivos y ayuda a administrar el corte, la posición, la conversión, el pedido de bytes, etc.

Si sus bytes se originan en un Stream, los NIO Buffers pueden usar el "modo directo" que crea un buffer respaldado por recursos nativos. Esto puede mejorar el rendimiento en muchos casos.

James Schek
fuente
14

Puede usar ArrayUtils.subarray en apache commons. No es perfecto, pero es un poco más intuitivo que System.arraycopy. El inconveniente es que introduce otra dependencia en su código.

seth
fuente
23
Es lo mismo que Arrays.copyOfRange () en Java 1.6
newacct
10

Veo que la respuesta subList ya está aquí, pero aquí hay un código que demuestra que es una verdadera sublista, no una copia:

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

Sin embargo, no creo que haya una buena manera de hacer esto directamente con las matrices.

Carl Manaster
fuente
9
List.subList(int startIndex, int endIndex)
Manuel Selva
fuente
9
Primero necesitaría ajustar la matriz como una lista: Arrays.asList (...). Sublist (...);
camickr
7

Los Lists le permiten usar y trabajar con subListalgo de forma transparente. Las matrices primitivas requieren que realice un seguimiento de algún tipo de límite de desplazamiento. ByteBuffers tienen opciones similares a las que escuché.

Editar: si está a cargo del método útil, simplemente puede definirlo con límites (como se hace en muchos métodos relacionados con la matriz en Java):

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

Sin embargo, no está claro si trabaja en los elementos de la matriz, por ejemplo, si calcula algo y escribe el resultado.

akarnokd
fuente
6

Una opción sería pasar toda la matriz y los índices de inicio y fin, e iterar entre ellos en lugar de iterar sobre toda la matriz pasada.

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

Las referencias de Java siempre apuntan a un objeto. El objeto tiene un encabezado que, entre otras cosas, identifica el tipo concreto (por lo que los lanzamientos pueden fallar ClassCastException). Para las matrices, el inicio del objeto también incluye la longitud, los datos siguen inmediatamente después en la memoria (técnicamente, una implementación es libre de hacer lo que le plazca, pero sería una tontería hacer cualquier otra cosa). Por lo tanto, no puede; t tener una referencia que apunte en algún lugar de una matriz.

En C, los punteros apuntan a cualquier lugar y a cualquier cosa, y puede apuntar al centro de una matriz. Pero no puede emitir o averiguar con seguridad cuánto dura la matriz. En D, el puntero contiene un desplazamiento en el bloque de memoria y la longitud (o, de manera equivalente, un puntero al final, no recuerdo qué hace realmente la implementación). Esto permite que D corte las matrices. En C ++ tendrías dos iteradores apuntando al inicio y al final, pero C ++ es un poco extraño así.

Así que volviendo a Java, no, no puedes. Como se mencionó, NIO le ByteBufferpermite ajustar una matriz y luego dividirla, pero ofrece una interfaz incómoda. Por supuesto, puedes copiar, lo que probablemente sea mucho más rápido de lo que piensas. Puede introducir su propia Stringabstracción similar que le permite cortar una matriz (la implementación actual de Sun Stringtiene una char[]referencia más un desplazamiento inicial y una longitud, la implementación de mayor rendimiento solo tiene char[]). byte[]es de bajo nivel, pero cualquier abstracción basada en la clase que usted ponga hará un desastre horrible de la sintaxis, hasta JDK7 (tal vez).

Tom Hawtin - tackline
fuente
Gracias por explicar por qué sería imposible. Por cierto, String ahora copia substringen HotSpot (olvide qué compilación cambió esto). ¿Por qué dice que JDK7 permitiría una mejor sintaxis que ByteBuffer?
Aleksandr Dubinsky
@AleksandrDubinsky Al momento de escribir, parecía que Java SE 7 iba a permitir la []notación de matriz en tipos definidos por el usuario, como Listy ByteBuffer. Todavía
estoy
2

@ unique72 responda como una función o línea simple, es posible que deba reemplazar Object, con el tipo de clase respectivo que desea 'cortar'. Se ofrecen dos variantes para satisfacer diversas necesidades.

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

¿Qué tal un Listenvoltorio delgado ?

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

(No probado)

RoToRa
fuente
Esto incurrirá en boxing-unboxing de bytes. Puede ser lento
MP Korstanje
@mpkorstanje: En la biblioteca Orable Java, los Byteobjetos para todos los bytevalores se almacenan en caché. Por lo tanto, la sobrecarga del boxeo debería ser bastante lenta.
Lii
1

Necesitaba iterar hasta el final de una matriz y no quería copiar la matriz. Mi enfoque fue hacer un Iterable sobre la matriz.

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

Esto es un poco más liviano que Arrays.copyOfRange: sin rango o negativo

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