No Java 8, por que a capacidade padrão de ArrayList agora é zero?

93

Pelo que me lembro, antes do Java 8, a capacidade padrão ArrayListera de 10.

Surpreendentemente, o comentário sobre o construtor padrão (vazio) ainda diz: Constructs an empty list with an initial capacity of ten.

De ArrayList.java:

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Kevinarpe
fonte

Respostas:

105

Tecnicamente, não é 10zero, se você admitir uma inicialização preguiçosa da matriz de apoio. Vejo:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

Onde

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

Você está se referindo apenas ao objeto de array inicial de tamanho zero que é compartilhado entre todos os ArrayListobjetos inicialmente vazios . Ou seja, a capacidade de 10é garantida preguiçosamente , otimização que está presente também no Java 7.

É certo que o contrato do construtor não é totalmente preciso. Talvez esta seja a fonte de confusão aqui.

fundo

Aqui está um e-mail de Mike Duigou

Publiquei uma versão atualizada do patch vazio de ArrayList e HashMap.

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/

Esta implementação revisada não introduz novos campos para nenhuma das classes. Para ArrayList, a alocação lenta da matriz de apoio ocorre apenas se a lista for criada no tamanho padrão. De acordo com nossa equipe de análise de desempenho, aproximadamente 85% das instâncias de ArrayList são criadas no tamanho padrão, portanto, essa otimização será válida para a grande maioria dos casos.

Para HashMap, o uso criativo é feito do campo de limite para rastrear o tamanho inicial solicitado até que a matriz de intervalo seja necessária. No lado da leitura, a caixa vazia do mapa é testada com isEmpty (). No tamanho de gravação, uma comparação de (tabela == EMPTY_TABLE) é usada para detectar a necessidade de aumentar a matriz do balde. Em readObject há um pouco mais de trabalho para tentar escolher uma capacidade inicial eficiente.

De: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html

Lukas Eder
fonte
4
De acordo com bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928, isso reduz o uso de heap e melhora os tempos de resposta (os números para dois aplicativos são mostrados)
Thomas Kläger
3
@khelwood: ArrayList não "relata" realmente sua capacidade, a não ser por meio deste Javadoc: não há getCapacity()método ou algo parecido. (Dito isso, algo como ensureCapacity(7)é um no-op para um ArrayList inicializado por padrão, então eu acho que realmente devemos agir como se sua capacidade inicial fosse realmente 10 ...)
ruakh
10
Boa escavação. A capacidade inicial padrão de fato não é zero, mas 10, com o caso padrão sendo alocado vagarosamente como um caso especial. Você pode observar isso se adicionar repetidamente elementos a um ArrayListcriado com o construtor no-arg em vez de passar zero para o intconstrutor e se observar o tamanho do array interno de forma reflexiva ou em um depurador. No caso padrão, a matriz salta do comprimento 0 para 10 e depois para 15, 22, seguindo a taxa de crescimento de 1,5x. Passar zero como capacidade inicial resulta em crescimento de 0 a 1, 2, 3, 4, 6, 9, 13, 19 ....
Stuart Marks
13
Sou Mike Duigou, autor da mudança e do e-mail citado e aprovo esta mensagem. 🙂 Como Stuart diz, a motivação era principalmente sobre economia de espaço em vez de desempenho, embora também haja um pequeno benefício de desempenho devido ao fato de frequentemente evitar a criação da matriz de apoio.
Mike Duigou
4
@assylias:; ^) não, ele ainda tem seu lugar como um singleton e emptyList()ainda consome menos memória do que várias ArrayListinstâncias vazias . É apenas menos importante agora e, portanto, não é necessário em todos os locais, especialmente em locais com maior probabilidade de adicionar elementos posteriormente. Lembre-se também de que às vezes você deseja uma lista vazia imutável e esse emptyList()é o caminho a seguir.
Holger
24

Em java 8, a capacidade padrão de ArrayList é 0 até que adicionemos pelo menos um objeto ao objeto ArrayList (você pode chamá-lo de inicialização lenta).

Agora a pergunta é por que essa mudança foi feita em JAVA 8?

A resposta é economizar o consumo de memória. Milhões de objetos de lista de array são criados em aplicativos Java em tempo real. O tamanho padrão de 10 objetos significa que alocamos 10 ponteiros (40 ou 80 bytes) para o array subjacente na criação e os preenchemos com nulos. Uma matriz vazia (preenchida com nulos) ocupa muita memória.

A inicialização lenta adia esse consumo de memória até o momento em que você realmente usará a lista de array.

Por favor, veja o código abaixo para obter ajuda.

ArrayList al = new ArrayList();          //Size:  0, Capacity:  0
ArrayList al = new ArrayList(5);         //Size:  0, Capacity:  5
ArrayList al = new ArrayList(new ArrayList(5)); //Size:  0, Capacity:  0
al.add( "shailesh" );                    //Size:  1, Capacity: 10

public static void main( String[] args )
        throws Exception
    {
        ArrayList al = new ArrayList();
        getCapacity( al );
        al.add( "shailesh" );
        getCapacity( al );
    }

    static void getCapacity( ArrayList<?> l )
        throws Exception
    {
        Field dataField = ArrayList.class.getDeclaredField( "elementData" );
        dataField.setAccessible( true );
        System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}

Response: - 
Size:  0, Capacity:  0
Size:  1, Capacity: 10

O artigo Capacidade padrão de ArrayList em Java 8 explica isso em detalhes.

Shailesh Vikram Singh
fonte
7

Se a primeira operação feita com um ArrayList é passar addAlluma coleção que tem mais de dez elementos, então qualquer esforço colocado na criação de um array inicial de dez elementos para conter o conteúdo do ArrayList seria jogado fora da janela. Sempre que algo é adicionado a um ArrayList, é necessário testar se o tamanho da lista resultante excederá o tamanho do armazenamento de apoio; permitir que o armazenamento de apoio inicial tenha tamanho zero em vez de dez fará com que este teste falhe uma vez extra no tempo de vida de uma lista cuja primeira operação é um "adicionar" que exigiria a criação da matriz inicial de dez itens, mas esse custo é menos do que o custo de criação de uma matriz de dez itens que nunca acaba sendo usada.

Dito isso, poderia ter sido possível melhorar ainda mais o desempenho em alguns contextos se houvesse uma sobrecarga de "addAll", que especificava quantos itens (se houver) provavelmente seriam adicionados à lista após a atual, e quais poderiam use isso para influenciar seu comportamento de alocação. Em alguns casos, o código que adiciona os últimos itens a uma lista terá uma boa ideia de que a lista nunca precisará de nenhum espaço além desse. Existem muitas situações em que uma lista será preenchida uma vez e nunca mais modificada depois disso. Se no ponto o código sabe que o tamanho final de uma lista será de 170 elementos, ele terá 150 elementos e um armazenamento de apoio de tamanho 160,

supergato
fonte
Muito bons pontos sobre addAll(). Essa é mais uma oportunidade para melhorar a eficiência em torno do primeiro malloc.
kevinarpe
@kevinarpe: Eu gostaria que a biblioteca Java tivesse sido projetada de mais algumas maneiras para que os programas indiquem como as coisas provavelmente seriam usadas. O estilo antigo de substring, por exemplo, era péssimo para alguns casos de uso, mas excelente para outros. Se houvesse funções separadas para "substring que provavelmente durará mais que o original" e "substring que provavelmente não durará mais que o original", e o código tivesse usado a correta 90% do tempo, eu pensaria que essas poderiam ter superado muito o implementação de string antiga ou nova.
supercat
3

A questão é 'por quê?'.

As inspeções de perfil de memória (por exemplo ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) mostram que arrays vazios (preenchidos com nulos) ocupam toneladas de memória.

O tamanho padrão de 10 objetos significa que alocamos 10 ponteiros (40 ou 80 bytes) para o array subjacente na criação e os preenchemos com nulos. Aplicativos Java reais criam milhões de listas de array.

A modificação introduzida remove ^ W adia este consumo de memória até o momento em que você realmente usará a lista de array.

ya_pulser
fonte
Corrija "consumir" com "desperdício". O link que você fornece não implica que eles comecem a devorar memória em todos os lugares, apenas que os arrays com elementos nulos desperdiçam a memória alocada para eles, de forma desproporcional. "Consumir" implica que eles usam magicamente a memória além de sua alocação, o que não é o caso.
mechalynx
1

Após a pergunta acima, eu examinei o documento ArrayList do Java 8. Eu descobri que o tamanho padrão ainda é apenas 10.

Por favor veja abaixo

Rahul Maurya
fonte
0

O tamanho padrão de ArrayList em JAVA 8 é ainda 10. A única alteração feita em JAVA 8 é que, se um codificador adicionar elementos menores que 10, os espaços em branco de arraylist restantes não são especificados como nulos. Dizer isso porque eu mesmo passei por essa situação e o eclipse me fez olhar para essa mudança de JAVA 8.

Você pode justificar essa mudança olhando a captura de tela abaixo. Nele você pode ver que o tamanho de ArrayList é especificado como 10 em Object [10], mas o número de elementos exibidos é apenas 7. Restos elementos de valor nulo não são exibidos aqui. Em JAVA 7 abaixo, a captura de tela é a mesma com apenas uma única mudança, que é que os elementos de valor nulo também são exibidos para os quais o codificador precisa escrever código para lidar com valores nulos se ele estiver iterando a lista completa de matrizes, enquanto em JAVA 8 essa carga é removida o chefe do codificador / desenvolvedor.

Link da captura de tela.

TechTeddy
fonte