.toArray (nova MyClass [0]) ou .toArray (nova MyClass [myList.size ()])?


176

Supondo que eu tenho um ArrayList

ArrayList<MyClass> myList;

E eu quero ligar para oArray, existe uma razão de desempenho para usar

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

sobre

MyClass[] arr = myList.toArray(new MyClass[0]);

?

Eu prefiro o segundo estilo, já que é menos detalhado, e presumi que o compilador garantirá que a matriz vazia não seja realmente criada, mas estou pensando se isso é verdade.

Obviamente, em 99% dos casos, não faz diferença de uma maneira ou de outra, mas eu gostaria de manter um estilo consistente entre meu código normal e meus loops internos otimizados ...


6
Parece que a questão foi resolvida em um novo post de Aleksey Shipilёv, Arrays of Wisdom of the Ancients !
— MLDs

6
Na postagem do blog: "Conclusão: toArray (novo T [0]) parece mais rápido, mais seguro e mais limpo contratualmente e, portanto, deve ser a opção padrão agora."
— 24916 DavidS

Respostas:


109

Por outro lado, a versão mais rápida, no Hotspot 8, é:

MyClass[] arr = myList.toArray(new MyClass[0]);

Eu executei um micro benchmark usando jmh, os resultados e o código estão abaixo, mostrando que a versão com uma matriz vazia supera consistentemente a versão com uma matriz presized. Observe que, se você puder reutilizar uma matriz existente do tamanho correto, o resultado poderá ser diferente.

Resultados de benchmark (pontuação em microssegundos, menor = melhor):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025 ▒ 0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155 ▒ 0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512 ▒ 0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884 ▒ 0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147 ▒ 0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977 ▒ 5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019 ▒ 0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133 ▒ 0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075 ▒ 0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318 ▒ 0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652 ▒ 0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692 ▒ 8.957  us/op

Para referência, o código:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}

Você pode encontrar resultados semelhantes, análise completa e discussão na publicação do blog Matrizes de Sabedoria dos Antigos . Para resumir: o compilador JVM e JIT contém várias otimizações que permitem criar e inicializar de maneira barata uma nova matriz de tamanho correto e essas otimizações não podem ser usadas se você mesmo criar a matriz.


2
Comentário muito interessante. Estou surpreso que ninguém tenha comentado sobre isso. Eu acho que é porque contradiz as outras respostas aqui, no que diz respeito à velocidade. Também é interessante notar que a reputação desses caras é quase maior do que todas as outras respostas juntas.
— Pimp Trizkit

Eu discordo. Eu também gostaria de ver benchmarks para MyClass[] arr = myList.stream().toArray(MyClass[]::new);.. que eu acho que seriam mais lentos. Além disso, gostaria de ver referências para a diferença com a declaração de matriz. Como na diferença entre: MyClass[] arr = new MyClass[myList.size()]; arr = myList.toArray(arr);e MyClass[] arr = myList.toArray(new MyClass[myList.size()]);... ou não deve haver nenhuma diferença? Eu acho que esses dois são um problema que está fora dos toArrayacontecimentos das funções. Mas ei! Não achei que aprenderia sobre outras diferenças complexas.
— Pimp Trizkit

1
@PimpTrizkit Apenas verificado: o uso de uma variável extra não faz diferença conforme o esperado. O uso de um fluxo leva entre 60% e 100% mais tempo para chamar toArraydiretamente (quanto menor o tamanho, maior a sobrecarga relativa)
— assylias

Uau, essa foi uma resposta rápida! Obrigado! Sim, eu suspeitava disso. A conversão para um fluxo não parecia eficiente. Mas você nunca sabe!
— Pimp Trizkit

2
Esta mesma conclusão foi encontrada aqui: shipilev.net/blog/2016/arrays-wisdom-ancients
— user167019

122

A partir do ArrayList no Java 5 , o array já estará preenchido se tiver o tamanho certo (ou for maior). Consequentemente

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

criará um objeto de matriz, preencha-o e retorne-o para "arr". Por outro lado

MyClass[] arr = myList.toArray(new MyClass[0]);

criará duas matrizes. O segundo é uma matriz de MyClass com comprimento 0. Portanto, há uma criação de objeto para um objeto que será descartado imediatamente. Na medida em que o código-fonte sugere, o compilador / JIT não pode otimizar este para que ele não seja criado. Além disso, o uso do objeto de comprimento zero resulta em conversão (ões) dentro do método toArray () -.

Veja a fonte de ArrayList.toArray ():

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Use o primeiro método para que apenas um objeto seja criado e evite vazamentos (implícitos, porém caros).


1
Dois comentários podem ser do interesse de alguém: 1) LinkedList.toArray (T [] a) é ainda mais lento (usa reflexão: Array.newInstance) e mais complexo; 2) Por outro lado, na liberação JDK7, eu estava muito surpreso ao descobrir, que realiza Array.newInstance normalmente dolorosamente-lentas quase tão rápido como a criação habitual panóplia!
— Java.is.for.desktop 30/07

1
@ktaria size é um membro privado de ArrayList, especificando **** surpresa **** o tamanho. Veja ArrayList SourceCode
— MyPasswordIsLasercats

3
Adivinhar o desempenho sem parâmetros de referência funciona apenas em casos triviais. Na verdade, new Myclass[0]é mais rápido: shipilev.net/blog/2016/arrays-wisdom-ancients
— Karol S

Essa resposta não é mais válida a partir de JDK6 +
— Антон Антонов

28

Na inspeção JetBrains Intellij Idea:

Existem dois estilos para converter uma coleção em uma matriz: usando uma matriz pré-dimensionada (como c.toArray (nova String [c.size ()]) ) ou usando uma matriz vazia (como c.toArray (nova String [ 0]) .

Nas versões Java mais antigas, era recomendável usar matriz pré-dimensionada, pois a chamada de reflexão necessária para criar uma matriz com tamanho adequado era bastante lenta. No entanto, desde as atualizações tardias do OpenJDK 6, essa chamada foi intrinsecificada, tornando o desempenho da versão do array vazio o mesmo e, às vezes, ainda melhor, comparado à versão pré-dimensionada. A passagem de matriz pré-dimensionada também é perigosa para uma coleção simultânea ou sincronizada, pois é possível uma corrida de dados entre o tamanho e a chamada toArray, o que pode resultar em nulos extras no final da matriz, se a coleção tiver sido reduzida ao mesmo tempo durante a operação.

Essa inspeção permite seguir o estilo uniforme: usando uma matriz vazia (recomendada em Java moderno) ou usando uma matriz pré-dimensionada (que pode ser mais rápida em versões Java mais antigas ou JVMs não baseadas em HotSpot).


Se tudo isso for copiado / citado, podemos formatá-lo adequadamente e também fornecer um link para a fonte? Na verdade, eu vim aqui por causa da inspeção do IntelliJ e estou muito interessado no link para procurar todas as inspeções e o raciocínio por trás delas.
— Tim Büthe 30/08/18



17

As JVMs modernas otimizam a construção de matriz reflexiva nesse caso, portanto a diferença de desempenho é pequena. Nomear a coleção duas vezes nesse código padrão não é uma boa idéia, portanto, evitaria o primeiro método. Outra vantagem do segundo é que ele trabalha com coleções sincronizadas e simultâneas. Se você quiser otimizar, reutilize a matriz vazia (matrizes vazias são imutáveis ​​e podem ser compartilhadas) ou use um criador de perfil (!).


2
A votação inicial 'reutiliza a matriz vazia', porque é um compromisso entre legibilidade e desempenho potencial que é digno de consideração. Passando um argumento declarou private static final MyClass[] EMPTY_MY_CLASS_ARRAY = new MyClass[0]não impede a matriz retornada de ser construído por reflexão, mas não impedir que um conjunto adicional a ser construído cada cada vez.
— Michael Scheper

Machael está certo, se você usar uma matriz de comprimento zero, não há maneira de contornar: (T []) java.lang.reflect.Array.newInstance (a.getClass (). GetComponentType (), size); o que seria supérfluo se o tamanho seria> = ActualSize (JDK7)
— Alex

Se você puder citar as "JVMs modernas que otimizam a construção de matrizes refletivas neste caso", eu votarei de bom grado nesta resposta.
— Tom Panning

Eu estou aprendendo aqui. Se, em vez disso, eu usar: MyClass[] arr = myList.stream().toArray(MyClass[]::new);Ajudaria ou prejudicaria as coleções sincronizadas e simultâneas. E porque? Por favor.
— Pimp Trizkit

3

O toArray verifica se a matriz passada é do tamanho certo (ou seja, grande o suficiente para caber nos elementos da sua lista) e, se sim, usa isso. Consequentemente, se o tamanho da matriz for menor que o necessário, uma nova matriz será criada reflexivamente.

No seu caso, uma matriz de tamanho zero é imutável; portanto, ela pode ser elevada com segurança a uma variável final estática, o que pode tornar seu código um pouco mais limpo, o que evita a criação da matriz em cada chamada. Uma nova matriz será criada dentro do método de qualquer maneira, portanto é uma otimização de legibilidade.

Indiscutivelmente, a versão mais rápida é passar a matriz com um tamanho correto, mas, a menos que você possa provar que esse código é um gargalo de desempenho, prefira a legibilidade ao tempo de execução, até que seja provado o contrário.


2

O primeiro caso é mais eficiente.

Isso ocorre porque no segundo caso:

MyClass[] arr = myList.toArray(new MyClass[0]);

o tempo de execução realmente cria uma matriz vazia (com tamanho zero) e, em seguida, dentro do método toArray cria outra matriz para ajustar os dados reais. Essa criação é feita usando reflexão usando o seguinte código (extraído de jdk1.5.0_10):

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Ao usar o primeiro formulário, você evita a criação de uma segunda matriz e também o código de reflexão.


toArray () não usa reflexão. Pelo menos desde que você não conte "casting" para reflexão, de qualquer maneira ;-).
— Georgi

toArray (T []) faz. Ele precisa criar uma matriz do tipo apropriado. As JVMs modernas otimizam esse tipo de reflexão para ter a mesma velocidade da versão não reflexiva.
— Tom Hawtin - tackline

Eu acho que ele usa reflexão. O JDK 1.5.0_10 faz com certeza e a reflexão é a única maneira que conheço para criar uma matriz de um tipo que você não conhece em tempo de compilação.
— Panagiotis Korros 06/10/08

Em seguida, um dos exemplos de código fonte dela (o acima ou o meu) está desatualizado. Infelizmente, não encontrei um número de subversão correto para o meu.
— Georgi

1
Georgi, seu código é do JDK 1.6 e se você vir a implementação do método Arrays.copyTo, verá que a implementação usa reflexão.
— Panagiotis Korros 06/10/08

-1

O segundo é marginalmente mais legível, mas há tão pouca melhoria que não vale a pena. O primeiro método é mais rápido, sem desvantagens no tempo de execução, e é isso que eu uso. Mas eu escrevo da segunda maneira, porque é mais rápido digitar. Em seguida, meu IDE o sinaliza como um aviso e se oferece para corrigi-lo. Com um único toque de tecla, ele converte o código do segundo tipo para o primeiro.


-2

Usar 'toArray' com a matriz do tamanho correto terá um desempenho melhor, pois a alternativa criará primeiro a matriz com tamanho zero e depois a matriz com o tamanho correto. No entanto, como você diz, a diferença provavelmente será insignificante.

Além disso, observe que o compilador javac não executa nenhuma otimização. Atualmente, todas as otimizações são executadas pelos compiladores JIT / HotSpot em tempo de execução. Não conheço nenhuma otimização em torno de 'toArray' em nenhuma JVMs.

A resposta para sua pergunta, portanto, é basicamente uma questão de estilo, mas por uma questão de consistência deve fazer parte de todos os padrões de codificação aos quais você adere (documentado ou não).


OTOH, se o padrão é usar uma matriz de comprimento zero, os casos que divergem implicam que o desempenho é uma preocupação.
— Michael Scheper

-5

código de amostra para inteiro:

Integer[] arr = myList.toArray(new integer[0]);
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.