Collections Framework Series Mapa (doze): TreeMap (1,8)

anuário

Um perfil

dois Overview

análise de código fonte de três

 encontrar 3.1

 3.2 travessia

 3,3 inserção

 3.4 Excluir

 

 

 

 

 

 I. Introdução

TreeMapApareceu pela primeira vez JDK 1.2, a implementação do Java Collections Framework é o mais importante. Com base na TreeMap subjacente 红黑树implementada, pode garantir log(n)containsKey completa, get, put e operações de remoção dentro da complexidade de tempo, de alta eficiência. Por outro lado, devido à Based TreeMap vermelho-preto árvore, que é fundamental para manter a fundação TreeMap ordenada. No geral, o núcleo TreeMap é a árvore de vermelho-preto, a sua árvore de vermelho-preto é também um monte de maneiras de verificar uma operação de adições de base pacote e exclusões. Então, enquanto nós entendemos a árvore rubro-negro, TreeMap nada de secreto.

 Em segundo lugar, uma visão geral

TreeMapHerdado de AbstractMape implementos  NavigableMapa interface. interface de NavigableMap estende-se SortedMapda interface, em última análise, SortedMap herdada da Mapinterface, enquanto classe AbstractMap também implementa a interface do Mapa. Estes são o sistema de herança TreeMap, descrevendo-se um pouco caótico, como a Figura:

A figura representa a hierarquia de herança TreeMap diagrama, mais intuitivo. Aqui brevemente sobre a interface incomum hierarquia de herança NavigableMape SortedMapas duas interfaces ver o nome conhecido significado. Deixe-me falar de interfaces NavigableMap, interface de NavigableMap declara um método de série com capacidades de navegação, tais como:

/ **
 * Retorna a árvore de vermelho-preto correspondente à entrada de chave menor
 * /
Map.Entry <K, V> firstEntry ();

/ **
 * Retorna o maior maxKey chave, e só inferior ao maxKey chave parâmetro
 * /
K lowerKey (chave K);

/ **
 * Retorna a chave mínimo Minkey, e maior que a chave parâmetro só Minkey
 * /
K higherKey (chave K);

A navegação através destes métodos, podemos navegar rapidamente para a chave ou objetivos de entrada. Como interface de SortedMap que fornece alguma ordem com base em operações-chave, tais como

/ **
 * Retorna a chave contida no Mapa [gama Minkey, Tokey)
 * /
SortedMap <K, V> headMap (K Tokey); ();

/ **
 * Retorna a chave contido no intervalo Mapa [fromKey, Tokey)
 * /
SortedMap <K, V> submap (K fromKey, K Tokey);

Estes são a introdução de duas interfaces, muito simples. Como AbstractMap e Mapa não pode dizer aqui, estamos interessados ​​em ver Javadoc próprio direito. Sobre sistema de herança TreeMap aqui quando se trata de isso, então nós entrar em detalhes da análise.

 Em terceiro lugar, análise de código fonte

JDK 1.8O TreeMapcódigo fonte tem mais de dois mil linhas, ou mais. Este artigo não tem a intenção de analisar toda a frase fonte por sentença, mas a seleção de vários métodos comumente utilizados de análise. Essas funções são método de pesquisa, transversal, insert, delete, etc. implementado, outros métodos amiguinhos estão lata interessados análise própria. TreeMapA parte principal é implementado no 红黑树método -implemented de substancialmente a maior parte da árvore de vermelho-preto subjacente é adicionada, excluída, uma operação de verificação do pacote. Cerca de um disse, enquanto a árvore vermelha-preta para entender o princípio, TreeMap nada de secreto. No 红黑树princípio, por favor consulte o meu outro artigo - uma árvore de análise detalhada rubro-negro , este artigo não vai discutir isso.

 encontrar 3.1

TreeMapimplementação de árvore rubro-negro, enquanto a árvore rubro-negra é uma árvore de busca binária de auto-equilíbrio, assim que olhar procedimentos operacionais treemap e consistente com base em uma árvore de busca binária. Binary processo de pesquisa de árvore é assim, primeiro valor-alvo eo valor do nó raiz são comparados, se o valor-alvo é menor que o valor do nó raiz, e então o filho esquerdo do nó raiz para comparação. Se o valor alvo é maior do que o valor do nó raiz, ele continua a comparar e filho direito do nó raiz. No processo de descoberta, se um nó em uma árvore binária e o valor-alvo são iguais, ele retorna true, caso contrário false. Encontrar TreeMap e é semelhante, exceto no TreeMap, o nó (Entrada) é armazenado em pares chave-valor <k,v>. No processo de busca, mais é o tamanho da chave, retorna o valor, se não for encontrado, ele retorna null. método de busca TreeMap é get, na implementação específica getEntrydo processo, o código fonte relevante da seguinte forma:

V pública get (Object key) {
    Entrada <K, V> p = getEntry (chave);
    retorno (p == null null:? p.value);
}

Entrada final <K, V> getEntry (Object key) {
    // Offload versão baseada em comparação, por causa do desempenho
    if (comparador! = null)
        retorno getEntryUsingComparator (chave);
    se (null == key)
        throw new NullPointerException ();
    @SuppressWarnings ( "unchecked")
        Comparable <? Super K> chave k = (Comparable <Super K>?);
    Entrada <K, V> p = raiz;
    
    // Encontrar a operação lógica do núcleo no loop while
    while (p! = null) {
        int cmp = k.compareTo (p.key);
        if (CMP <0)
            p = p.left;
        else if (CMP> 0)
            p = p.right;
        outro
            regresso p;
    }
    return null;
}

Encontre a operação lógica do núcleo é o getEntryprocesso de whilecirculação, nós controlamos o acima referido processo, seu próprio olhar para ele, é relativamente simples, para não dizer.

 3.2 travessia

Toda a operação de transferência é o de uma maior frequência de operação, para TreeMap, utilização é geralmente como se segue:

for (Object key: map.keySet ()) {
    // faça alguma coisa
}

ou

for (Map.Entry entrada: map.entrySet ()) {
    // faça alguma coisa
}

Como pode ser visto a partir do fragmento de código de cima, que geralmente definido como uma chave ou um conjunto de TreeMap transversal de entrada. O fragmento de código de cima com foreach gerado através do método keySet recolha, em tempo de compilação é convertido num percurso iterativo, é equivalente a:

Conjunto de teclas = map.keySet ();
Repetidor ite = keys.iterator ();
enquanto (ite.hasNext ()) {
    chave objeto = ite.next ();
    // faça alguma coisa
}

Por outro lado, o TreeMap tem uma propriedade que pode garantir-chave ordenada, o padrão é a seqüência positiva. Assim, durante a travessia, você vai encontrar TreeMap será de pequeno a grande valor dos títulos de saída. Bem, então tem que analisar keySetmétodos, bem como ao atravessar método keySet conjunto gerado, TreeMap é como garantir chaves ordenadas. código relacionado é o seguinte:

público Set <K> keySet () {
    regresso navigableKeySet ();
}

pública NavigableSet <K> navigableKeySet () {
    KeySet <K> NKS = navigableKeySet;
    retorno (NKS! = null)? NKS: (navigableKeySet = new KeySet <> (this));
}

classe static final KeySet <E> estende AbstractSet <e> implementos NavigableSet <E> {
    última NavigableMap <? E,> m privado;
    KeySet (NavigableMap <E,?> Mapa) {m = Roteiro; }

    pública Iterator <E> iterator () {
        if (m instanceof TreeMap)
            return ((TreeMap) m <E,?>) .keyIterator ();
        outro
            return ((TreeMap.NavigableSubMap) m <E,?>) .keyIterator ();
    }

    // omitindo código não-crítica
}

Iterator <K> keyIterator () {
    retornar nova KeyIterator (getFirstEntry ());
}

classe KeyIterator última estende PrivateEntryIterator <K> {
    KeyIterator (Entry <K, V> primeiro) {
        Super (em primeiro lugar);
    }
    pública K next () {
        retorno nextEntry chave (.);
    }
}

classe abstrata PrivateEntryIterator <T> implementa Iterator <T> {
    Entrada <K, V> próxima;
    Entrada <K, V> lastReturned;
    int expectedModCount;

    PrivateEntryIterator (Entry <K, V> primeiro) {
        expectedModCount = modCount;
        lastReturned = nulo;
        próximo = primeiro;
    }

    hasNext boolean public final () {
        voltar no próximo! = null;
    }

    Final Entry <K, V> nextEntry () {
        Entrada <K, V> e = próxima;
        se (e == null)
            throw new NoSuchElementException ();
        if (modCount! = expectedModCount)
            throw new ConcurrentModificationException ();
        // encontrar um nó sucessor e
        próximo = sucessor (e);
        lastReturned = e;
        voltar e;
    }

    // Outros métodos omitidos
}

O código acima mais código keySet envolvidos, ou mais, podemos olhar de cima para baixo. Como pode ser visto a partir do acima fonte keySet método retorna a KeySetclasse objecto. Essa classe implementa a Iterableinterface, você pode retornar um iterador. A implementação específica é o iterador KeyIterator, e classe lógica do núcleo KeyIterator é PrivateEntryIteratorimplementado. O código acima são numerosas, mas o código do núcleo ou de classe e de classe KeySet PrivateEntryIterator  nextEntrymétodos. classe KeySet é uma coleção, não analisados aqui. O método nextEntry é mais importante, uma análise simples abaixo.

No KeyIterator inicialização, vai TreeMap entrada contém a menor chave passou PrivateEntryIterator. Quando você chamar o método nextEntry, chamando o método sucessor para encontrar o sucessor para a entrada atual e deixar que o próximo ponto a um sucessor, e, finalmente, voltar para a entrada atual. Retorna o valor da chave pode ser realizado em seqüência lógica positiva desta forma.

Bem, TreeMap travessia operar conversamos sobre isso. operação de travessia em si não é difícil, mas falar um pouco mais, um pouco prolixo, que se ofender.

 3,3 inserção

No que diz respeito às duas primeiras operações, a inserção é significativamente mais complicada. Quando colocado em TreeMap novos pares chave-valor poderia minar a natureza da árvore de vermelho-preto. Por conveniência da descrição aqui, a entrada denominados nós. E o nó recém-inserido é chamado N, N é o nó pai P. nó pai de P Ge P é o filho esquerdo de G. P é irmão U. Após a inserção do novo nó N em que as árvores vermelho-negro (novo nó vermelho), produz os seguintes cinco casos:

  1. N é o nó raiz
  2. Preto pai nó N
  3. nó pai de N é vermelho, vermelho é também o nó tio
  4. nó pai de N é vermelho, nó tio é negro, e N é o filho direito de P
  5. nó pai do N é vermelho, nó tio é negro, e N é o filho esquerdo de P

5 no caso acima, o caso 2 não destrói rubro-negro natureza da árvore, então nenhuma ação é necessária. 1. Propriedades de árvore de vermelho-preto destruir 2 (raiz negra), onde 3, 4 e 5 irá destruir a árvore natureza vermelho-preto 4 (Red cada nó tem dois nós filhos para ser preto). Esta necessidade de tempo para ser ajustado para fazer a árvore de vermelho-preto para recuperar o equilíbrio. Quanto à forma de ajustar, posso referir a outro artigo sobre a árvore de vermelho-preto ( análise detalhada de árvore vermelho-preto ), não serão repetidos aqui. Em seguida, analisar inserir itens relacionados:

V pública put (chave K, valor V) {
    Entrada <K, V> t = raiz;
    // 1. Se a raiz for nulo, o novo nó ao nó raiz
    Se (t == null) {
        comparar (chave, chave);
        root = nova entrada <> (chave, valor, null);
        tamanho = 1;
        modCount ++;
        return null;
    }
    cmp int;
    Entrada <K, V> pai;
    // comparador dividida e caminhos comparáveis
    Comparator <? Super K> cpr = comparador;
    if (cpr! = null) {
        // 2. chave para encontrar uma posição adequada na árvore de vermelho-preto
        Faz {
            pai = t;
            cmp = cpr.compare (chave, t.key);
            if (CMP <0)
                t = t.left;
            else if (CMP> 0)
                t = t.right;
            outro
                t.setValue retorno (valor);
        } While (t! = Null);
    } outro {
        // lógica de código é semelhante ao anterior é omitido
    }
    Entrada <K, V> e = nova entrada <> (chave, valor, pai);
    // 3. O novo nó na árvore de link em um vermelho-preto
    if (CMP <0)
        parent.left = e;
    outro
        parent.right = e;
    // 4. Insira o novo nó pode destruir propriedades de árvore vermelho-preto, a correção aqui
    fixAfterInsertion (e);
    tamanho ++;
    modCount ++;
    return null;
}

colocar o código do método acima, a lógica de busca árvore binária e nó lógica de inserção consistente. passos importantes que tenho escrito notas, não é difícil de entender. Inserção complexidade lógica em que uma operação de reparação após a inserção, uma método correspondente fixAfterInsertion, e uma descrição de fonte do método são como se segue:

Aqui, na inserção acabado. Em seguida, ele disse TreeMap parte mais complexa, que é uma operação de exclusão.

 3.4 Excluir

A eliminação é a parte mais complexa de uma árvore vermelha-preta, pois a operação pode destruir as propriedades de árvore vermelho-negra 5 (a partir de qualquer nó para todos os caminhos simples cada folha contém o mesmo número de nós pretos), a natureza da reparação a 5 reparação de outras propriedades (Propriedades 2 e 4 necessidade de reparação, não corrigir natureza. 1 e 3) complexa. Quando a operação de exclusão levou à destruição de propriedade 5, 8 acontece. Para a conveniência de descrição, aqui ou fazer algumas suposições. Nós 最终被删除substituir o nó nó é chamado X, X é chamado N. nó pai de N P, e N é o filho esquerdo de P. N nó irmão S, esquerda criança S é SL, a criança certa para o SR. Aqui X é especialmente enfatizado  最终被删除 nó, a situação é a razão pela qual uma árvore de busca binária que você deseja excluir um nó com duas crianças em situações de excluir apenas um nó filho, o nó é o desejo de ser excluído predecessor nó e sucessor.

Em seguida, uma simples lista de situações que podem ocorrer quando prestes a excluir um nó, a primeira listada situação relativamente simples:

  1. Eventualmente eliminado nó nó X é vermelho
  2. nó X é preto, mas o nó filho do nó é vermelho

situações mais complexas:

  1. Alternativamente, um novo nó de raiz N
  2. Preto N, N irmão nós S vermelho, os outros nós são negros.
  3. Preto de N, N nó pai P, S e nós filhos S irmão são pretos.
  4. N é preto, P é vermelho, S e S crianças são pretos.
  5. Preto N, P pode ser a preto vermelho, S é preto, S é SL vermelha a criança esquerdo, SR para o negro filho direito
  6. Preto de N, P pode ser a preto vermelho, S é preto, vermelho SR, SL pode ser preto vermelho

Oito situações listadas acima, os dois primeiros processo relativamente simples, no caso da situação Após 6 2,6 complicado. Em seguida, vou analisar a situação de desdobramento 2,6, exclua o código fonte relevante da seguinte forma:

V pública remove (Object key) {
    Entrada <K, V> p = getEntry (chave);
    se (P == nulo)
        return null;

    V = oldValue p.value;
    deleteEntry (p);
    voltar oldValue;
}

deleteEntry private void (Entry <K, V> p) {
    modCount ++;
    Tamanho--;

    / * 
     * 1. Se nós p têm dois filhos, em seguida, encontrar um nó sucessor,
     * Os valores de cópia e o nodo sucessor para o nó P, e P no seu nó sucessor
     * /
    if (p.left! = null && p.right! = null) {
        Entrada <K, V> S = sucessor (p);
        p.key = s.key;
        p.value = s.value;
        p = s;
    } // p tem 2 filhos

    // Iniciar correção no nó de substituição, se ele existir.
    Entrada <K, V> substituição = (p.left = null p.left:!? P.right);

    if (substituição! = null) {
        / *
         * 2. As referências pais substituição apontar para o novo nó pai,
         * Enquanto permitindo nova substituição apontando pai.
         * / 
        replacement.parent = p.parent;
        se (p.parent == nulo)
            root = substituição;
        else if (p == p.parent.left)
            p.parent.left = substituição;
        outro
            p.parent.right = substituição;

        // nulo para fora as ligações assim que são OK para usar por fixAfterDeletion.
        p.left = p.right = p.parent = nulo;

        // 3. Se você excluir um nó p é um nó preto, você precisa ajustar
        if (p.color == PRETO)
            fixAfterDeletion (substituição);
    } Else if (p.parent == null) {// nó raiz é excluído, e este é apenas um nó na árvore
        raiz = nulo;
    } Else {// excluir o nó não é um nó filho
        // p é preto, necessidade de ser ajustado
        if (p.color == PRETO)
            fixAfterDeletion (p);

        // P vai ser removida a partir da árvore
        if (p.parent! = null) {
            se (P == p.parent.left)
                p.parent.left = nulo;
            else if (p == p.parent.right)
                p.parent.right = nulo;
            p.parent = nulo;
        }
    }
}

Como pode ser visto a partir do código-fonte, removeo método é simplesmente uma garantia, na implementação do núcleo deleteEntryprocesso. deleteEntry principalmente a fazê-lo algumas coisas:

  1. Se você deseja excluir um nó P tem dois filhos, em seguida, encontrar um sucessor para o S P, e depois copiar o valor de S a P, e deixar que o ponto P S
  2. Se, eventualmente, eliminado nó P (P agora aponta para nó, eventualmente, eliminado) criança não está vazia, em seguida, substituí-lo com os nós filhos
  3. Se o nó é eventualmente eliminado é preto, em seguida, chamar o método para a reparação fixAfterDeletion

Ele diz que a substituição não está vazio, o deleteEntry lógica de execução. O dito acima um pouco prolixo, se nós simplesmente dizer, sete palavras para resumir: 找后继 -> 替换 -> 修复. Este de três etapas, a operação de reparação mais complexa. Para reativar a operação de reparo de árvore vermelho-preto para restaurar o equilíbrio, a fonte de operação de reparação da seguinte forma:

fixAfterDeletion método como se segue:

FixAfterDeletion face superior da lógica do código ter sido analisados ​​por forma analítica com a FIG caso, cada código de processamento lógica. A título de ilustração, deve ser bastante fácil de entender. Bem, TreeMap primeiro analisar o código fonte aqui.

 

 

 

Este link:  https://www.tianxiaobo.com/2018/01/11/TreeMap análise de código fonte /

0

Acho que você gosta

Origin www.cnblogs.com/youngao/p/12518994.html
Recomendado
Clasificación