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

itsadok
fonte
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.

assylias
fonte
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).

Georgi
fonte
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).

Антон Антонов
fonte
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
3
Aqui você pode conferir os textos das inspeções: github.com/JetBrains/intellij-community/tree/master/plugins/…
Антон Антонов
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 (!).

Tom Hawtin - linha de orientação
fonte
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.

Dave Cheney
fonte
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.

Panagiotis Korros
fonte
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.

MiguelMunoz
fonte
-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).

Matthew Murdoch
fonte
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]);
Rasol
fonte