Diretório do artigo
- 1 Análise de código-fonte ArrayList e idéias de design
- 2 Análise de código-fonte LinkedList
- 3 Lista de perguntas da entrevista
- 3.1 Fale sobre o entendimento de ArrayList e LinkedList
- 3.2 Problemas de expansão
- 3.3 Problemas de exclusão
- 3.4 Problemas de contraste
- 3.5 Outros tipos de tópicos
- 4 Análise do código fonte do HashMap
1 Análise de código-fonte ArrayList e idéias de design
1.1 Estrutura geral
ArrayList é uma estrutura de matriz.
- Permitir colocar valor nulo, ele será expandido automaticamente;
- A complexidade de tempo de métodos como tamanho, isEmpty, get, set e add são todos O (1);
- Não é thread-safe.No caso de multi-threading, é recomendável usar a classe thread-safe: Collections # synchronizedList;
- Aprimore o loop for ou use um iterador para iterar, se o tamanho da matriz for alterado, ele falhará rapidamente e lançará uma exceção.
1.2 Inicialização
Três métodos: inicialização direta sem parâmetros, inicialização de tamanho especificado, inicialização de dados iniciais especificados.
// 无参数直接初始化
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 指定大小初始化
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
- Inicialização direta sem parâmetros, o tamanho padrão é uma matriz vazia, não 10. 10 é o valor da primeira expansão.
1.3 Implementação nova e ampliada
- 1,5 vezes a capacidade original após a expansão;
- A expansão máxima é Integer.MAX_VALUE;
- Ao adicionar, não há uma verificação estrita do valor, portanto, ArrayList permite valores nulos.
- Para expandir a essência , crie uma nova matriz e copie a matriz antiga. Consome mais desempenho; portanto, especifique o tamanho dos dados o máximo possível para evitar a expansão frequente.
1.4 Excluir
Exclua um elemento indexado por índice, os elementos atrás avançarão um pouco.
1.5 Iterador
int cursor;// 迭代过程中,下一个元素的位置,默认从 0 开始。
int lastRet = -1; // 新增场景:表示上一次迭代过程中,索引的位置;删除场景:为 -1。
int expectedModCount = modCount;// expectedModCount 表示迭代过程中,期望的版本号;modCount 表示数组实际的版本号。
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
2 Análise de código-fonte LinkedList
2.1 Estrutura geral
Código do nó
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.2 Adicionar, excluir
Pode ser adicionado do início ao fim, alguns podem ser excluídos do início
2.3 Consulta do nó
Se o índice estiver no primeiro semestre, inicie a pesquisa desde o início, caso contrário, inicie a pesquisa pela parte de trás, o que melhora a eficiência. size >> 1 significa deslocar para a direita 1 bit, o que equivale a 2.
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
2.4 Iterador
Implemente a interface ListIterator e ofereça suporte à iteração bidirecional, ou seja, é possível iterar do início ou do fim.
3 Lista de perguntas da entrevista
3.1 Fale sobre o entendimento de ArrayList e LinkedList
Você pode responder primeiro à arquitetura geral e, em seguida, analisar alguns detalhes. Por exemplo, a parte inferior do ArrayList é uma matriz, e sua API é para encapsular a matriz inferior e assim por diante.
3.2 Problemas de expansão
1. ArrayList é construído sem o construtor de parâmetros.Agora, adicione um valor.No momento, qual é o tamanho da matriz e qual é o tamanho máximo disponível antes da próxima expansão?
No momento, o tamanho da matriz é 1 e o tamanho máximo disponível antes da próxima expansão é 10. O valor padrão da primeira expansão é 10.
2. Se adicionar novos valores à lista continuamente, quando eu aumentar para a 11ª, o tamanho da matriz será Quanto?
oldCapacity + (oldCapacity >> 1), a próxima expansão é 1,5 vezes. Isso é de 10 a 15.
3. Depois que a matriz é inicializada e um valor é adicionado, se eu usar o método addAll e adicionar 15 valores de uma vez, qual é o tamanho da matriz final?
Após a inicialização, adicione um valor. No momento, a expansão da capacidade padrão é 10 e, em seguida, adicione 15 valores de uma vez. A expansão da capacidade é de 1,5 vezes, ou seja, 15, ainda não pode ser satisfeita. Existe uma estratégia no momento:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // 直接扩容到我们需要的大小
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
Expansão direta para 16.
4. Agora eu tenho uma matriz grande para copiar, o tamanho original da matriz é 5k, como posso copiar rapidamente?
5k é um valor relativamente grande e a capacidade especificada é 5k quando a matriz é inicializada. Se a inicialização não especificar um tamanho, ela será freqüentemente expandida, com um grande número de cópias, afetando o desempenho.
5. Por que a expansão da capacidade consome desempenho?
A camada inferior usa System.arraycopy para copiar a matriz original para a nova matriz, portanto, consome desempenho.
6. Há algo que valha a pena aprender com o processo de expansão do código-fonte?
- Expansão automática, os usuários não precisam se preocupar com alterações na estrutura de dados subjacente. A expansão da capacidade é baseada em um crescimento de 1,5 vezes. O crescimento no estágio inicial é relativamente lento e o crescimento no estágio posterior é relativamente grande. O tamanho dos dados usados na maioria dos trabalhos não é grande. Existe utilização para melhorar o desempenho.
- Durante o processo de expansão, preste atenção ao estouro da matriz, que não é menor que 0 e não é maior que o valor máximo de Inteiro.
3.3 Problemas de exclusão
1. Existe um ArrayList, os dados são 2, 3, 3, 3, 4 e existem três 3s no meio.Agora eu uso para (int i = 0; i <list.size (); i ++), desejo alterar o valor para 3 Elementos excluídos, posso excluí-los corretamente? Qual é o resultado da exclusão final e por quê? O código de exclusão é o seguinte:
List<String> list = new ArrayList<String>() {{
add("2");
add("3");
add("3");
add("3");
add("4");
}};
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("3")) {
list.remove(i);
}
}
Não pode.
2. Ainda a matriz ArrayList acima, podemos excluir aprimorando o loop for?
Não, ele reportará um erro. Como o processo aprimorado para loop realmente chama o método next () do iterador. Quando você chama o método list # remove () para excluir, o valor de modCount será +1, mas o valor de esperadoModCount no iterador não foi alterado. , Causando a próxima vez que o iterador executar o método next (), o esperadoModCount! = ModCount relatará o erro ConcurrentModificationException.
Veja: Por que o Alibaba proíbe a operação de remoção / adição de elementos no loop foreach
3. A matriz acima, se pode ser excluída usando o método Iterator.remove () ao excluir, por que?
Sim, porque o método Iterator.remove () atribuirá o modCount mais recente ao esperadoModCount durante a execução, para que durante o próximo ciclo, tanto o modCount quanto o esperadoModCount sejam iguais.
4. As três perguntas acima são iguais para o LinkedList?
Sim, embora a estrutura subjacente do LinkedList seja uma lista duplamente vinculada, mas para os três problemas acima, os resultados são consistentes com o ArrayList.
3.4 Problemas de contraste
1. Qual é a diferença entre ArrayList e LinkedList?
A lógica subjacente dos dois é diferente: ArrayList é uma matriz e LinkedList é uma lista duplamente vinculada. As APIs implementadas na parte inferior também são diferentes, bla, bla, etc.
2. Quais são os diferentes cenários de aplicativos de ArrayList e LinkedList?
ArrayList é adequado para o cenário de pesquisa rápida sem adição e exclusão frequentes, enquanto o LinkedList é adequado para o cenário de adição e exclusão freqüente e poucas consultas.
3. ArrayList e LinkedList têm capacidade máxima? O
primeiro tem o maior número inteiro, o último é teoricamente sem fio, mas o tamanho real é int size, portanto, existem apenas números inteiros.
4. Como ArrayList e LinkedList lidam com valores nulos?
Ambos executam a adição e exclusão de valores nulos, mas ArrayList exclui valores nulos desde o início.
5. ArrayList e LinedList são seguros para threads, por que?
Não. Em um ambiente multithread, operações como adição e exclusão não são sincronizadas.
6. Como resolver o problema de segurança da rosca?
Existem listas seguras de encadeamento em Collections, Collections.synchronizedList () ou CopyOnWriteArrayList.
3.5 Outros tipos de tópicos
1. Descreva a lista duplamente vinculada?
Ligeiramente.
2. Descreva a adição e exclusão de listas duplamente vinculadas
.