Pegue um segmento de uma matriz em Java sem criar uma nova matriz na pilha

181

Estou procurando um método em Java que retornará um segmento de uma matriz. Um exemplo seria obter a matriz de bytes que contém os 4º e 5º bytes de uma matriz de bytes. Não quero ter que criar uma nova matriz de bytes na memória heap apenas para fazer isso. No momento, tenho o seguinte código:

doSomethingWithTwoBytes(byte[] twoByteArray);

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

Eu gostaria de saber se havia uma maneira de fazer apenas doSomething(bigArray.getSubArray(4, 2))onde 4 é o deslocamento e 2 é o comprimento, por exemplo.

jbu
fonte
1
Que tal fazer alguma mágica JNI em C ++? Pode ser um desastre do ponto de vista da GC?
AlikElzin-Kilaka
Tem que ser uma matriz de bytes primitivos?
MP Korstanje

Respostas:

185

Isenção de responsabilidade: Esta resposta não está de acordo com as restrições da pergunta:

Não quero ter que criar uma nova matriz de bytes na memória heap apenas para fazer isso.

( Sinceramente, acho que minha resposta merece ser excluída. A resposta de @ unique72 está correta. Vou deixar essa edição descansar um pouco e depois excluirei essa resposta. )


Não conheço uma maneira de fazer isso diretamente com matrizes sem alocação de heap adicional, mas as outras respostas usando um wrapper de sub-lista têm alocação adicional apenas para o wrapper - mas não o array - o que seria útil no caso de uma grande variedade.

Dito isto, se alguém procura concisão, o método utilitário Arrays.copyOfRange()foi introduzido no Java 6 (final 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
fonte
10
isso ainda aloca dinamicamente um novo segmento de memória e copia o intervalo para ele.
Dan
4
Obrigado Dan - Neguei que o OP não queria criar nova matriz e não olhei para a implementação de copyOfRange. Se fosse de código fechado, talvez pudesse ter passado. :)
David J. Liszewski
7
Eu acho que muitas pessoas querem criar uma sub-matriz a partir de uma matriz e não estão preocupadas com o uso de mais memória. Eles se deparam com essa pergunta e obtêm a resposta que desejam - portanto, não exclua, pois é útil - acho que tudo bem.
The Lonely Coder
2
na verdade, copyOfRange ainda alocar novo segmento de memória
Kevingo Tsai
167

Arrays.asList(myArray)delega para new ArrayList(myArray), que não copia a matriz, mas apenas armazena a referência. Usar List.subList(start, end)depois disso cria um SubListque apenas faz referência à lista original (que ainda apenas faz referência à matriz). Nenhuma cópia da matriz ou seu conteúdo, apenas a criação do wrapper e todas as listas envolvidas são apoiadas pela matriz original. (Eu pensei que seria mais pesado.)

unique72
fonte
9
Para esclarecer, ele delega para uma classe privada Arrayschamada confusa ArrayList, mas que é realmente Listuma matriz, ao contrário da java.util.ArrayListqual faria uma cópia. Nenhuma nova alocação (do conteúdo da lista) e nenhuma dependência de terceiros. Esta é, acredito, a resposta mais correta.
precisa saber é o seguinte
28
Na verdade, isso não funcionará para matrizes de tipo primitivo, como o OP queria ( byte[]no caso dele). Tudo o que você conseguiria seria List<byte[]>. E mudar byte[] bigArraypara Byte[] bigArraypode impor uma sobrecarga significativa de memória.
Dmitry Avtonomov 02/04
2
A única maneira de realmente alcançar o que é desejado é através da sun.misc.Unsafeclasse.
Dmitry Avtonomov
39

Se você está procurando uma abordagem de alias no estilo de ponteiro, para que nem precise alocar espaço e copiar os dados, acredito que não tenha sorte.

System.arraycopy() será copiado da sua origem para o destino e a eficiência é reivindicada para este utilitário. Você precisa alocar a matriz de destino.

djna
fonte
3
Sim, eu esperava algum tipo de método de ponteiro, pois não quero alocar dinamicamente a memória. mas parece que é isso que eu vou ter que fazer.
JBU
1
Como @ unique72 sugere, parece haver maneiras de fazer o que você deseja, explorando sutilezas na implementação de vários tipos de lista / array java. Este parece ser possível, não apenas de forma explícita e que me faz hesitar em contar com ele muito ...
Andrew
Por que array*copy*()reutilizar a mesma memória? Não é exatamente o oposto do que um chamador esperaria?
Patrick Favre
23

Uma maneira é envolver a matriz em java.nio.ByteBuffer , usar as funções absolutas de put / get e dividir o buffer para trabalhar em um subarray.

Por exemplo:

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

Observe que você precisa chamar os dois wrap()e slice(), uma vez que, wrap()por si só, afeta as funções put / get relativas, não as absolutas.

ByteBuffer pode ser um pouco complicado de entender, mas provavelmente é implementado com eficiência e vale a pena aprender.

Soulman
fonte
1
É importante notar também que objetos ByteBuffer pode ser facilmente decodificados:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl
@ Soulman obrigado pela explicação, mas uma pergunta é mais eficiente do que usar Arrays.copyOfRange?
ucMedia 14/02
1
@ucMedia para uma matriz de dois bytes, Arrays.copyOfRangeé provavelmente mais eficiente. Geralmente, você precisaria medir para o seu caso de uso específico.
Soulman 16/02
20

Use java.nio.Buffer. É um invólucro leve para buffers de vários tipos primitivos e ajuda a gerenciar fatias, posições, conversões, pedidos de bytes, etc.

Se seus bytes forem originários de um fluxo, os NIO Buffers poderão usar o "modo direto", que cria um buffer suportado por recursos nativos. Isso pode melhorar o desempenho em muitos casos.

James Schek
fonte
14

Você pode usar o ArrayUtils.subarray no apache commons. Não é perfeito, mas um pouco mais intuitivo do que System.arraycopy. o lado negativo é que ele introduz outra dependência no seu código.

seth
fonte
23
É o mesmo que Arrays.copyOfRange () em Java 1.6
newacct
10

Vejo que a resposta subList já está aqui, mas aqui está o código que demonstra que é uma verdadeira sublist, não uma cópia:

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

Porém, não acredito que exista uma boa maneira de fazer isso diretamente com matrizes.

Carl Manaster
fonte
9
List.subList(int startIndex, int endIndex)
Manuel Selva
fonte
9
Você primeiro precisa agrupar a matriz como uma lista: Arrays.asList (...). Sublist (...);
camickr
7

Eles Listpermitem que você use e trabalhe com subListalgo de forma transparente. Matrizes primitivas exigiriam que você acompanhasse algum tipo de limite de deslocamento. ByteBuffers têm opções semelhantes às que ouvi.

Edit: Se você é responsável pelo método útil, você pode defini-lo com limites (como feito em muitos métodos relacionados à matriz no próprio java:

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

Não está claro, no entanto, se você trabalha com os próprios elementos da matriz, por exemplo, calcula algo e escreve o resultado?

akarnokd
fonte
6

Uma opção seria passar toda a matriz e os índices de início e fim e iterar entre eles, em vez de iterar sobre toda a matriz passada.

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

As referências Java sempre apontam para um objeto. O objeto possui um cabeçalho que, entre outras coisas, identifica o tipo concreto (para que as transmissões possam falhar comClassCastException ). Para matrizes, o início do objeto também inclui o comprimento, os dados seguem imediatamente depois na memória (tecnicamente, uma implementação é livre para fazer o que bem entender, mas seria imprudente fazer qualquer outra coisa). Portanto, você não pode ter uma referência que aponte em algum lugar de uma matriz.

Em ponteiros C, aponte para qualquer lugar e para qualquer coisa, e você pode apontar para o meio de uma matriz. Mas você não pode transmitir ou descobrir com segurança quanto tempo a matriz é. Em D, o ponteiro contém um deslocamento no bloco e no comprimento da memória (ou um ponteiro equivalente ao final, não me lembro o que a implementação realmente faz). Isso permite que D divida matrizes. No C ++, você teria dois iteradores apontando para o início e o fim, mas o C ++ é um pouco estranho assim.

Então, voltando ao Java, não, você não pode. Como mencionado, o NIO ByteBufferpermite agrupar uma matriz e, em seguida, cortá-la, mas fornece uma interface estranha. É claro que você pode copiar, o que provavelmente é muito mais rápido do que você imagina. Você pode apresentar sua própria Stringabstração que permite dividir uma matriz (a implementação atual da Sun Stringtem uma char[]referência mais um deslocamento inicial e comprimento, a implementação de desempenho mais alto apenas possui a char[]). byte[]é de baixo nível, mas qualquer abstração baseada em classe que você colocar fará uma bagunça terrível da sintaxe, até o JDK7 (talvez).

Tom Hawtin - linha de orientação
fonte
Obrigado por explicar por que seria impossível. Btw, String agora copia substringno HotSpot (esqueça qual build mudou isso). Por que você diz que o JDK7 permitiria uma sintaxe melhor que o ByteBuffer?
Aleksandr Dubinsky
@AleksandrDubinsky No momento da redação, parecia que o Java SE 7 permitiria a []notação de matriz em tipos definidos pelo usuário, como Liste ByteBuffer. Ainda esperando ...
Tom Hawtin - tackline
2

@ unique72 responda como uma função ou linha simples, pode ser necessário substituir Object pelo tipo de classe que você deseja 'cortar'. Duas variantes são fornecidas para atender a várias necessidades.

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

Que tal um Listinvólucro fino ?

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

(Não testado)

RoToRa
fonte
Isso resultará em box-unboxing de bytes. Pode ser lento.
MP Korstanje
@mpkorstanje: Na biblioteca Java Orable, os Byteobjetos para todos os bytevalores são armazenados em cache. Portanto, a sobrecarga do boxe deve ser bastante lenta.
Lii 31/01
1

Eu precisava percorrer o final de uma matriz e não queria copiar a matriz. Minha abordagem foi tornar um Iterable sobre a 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
fonte
-1

Isso é um pouco mais leve que Arrays.copyOfRange - sem intervalo ou 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;
}
vinculo
fonte