Java常用數(shù)據(jù)結(jié)構(gòu)分析

總結(jié)一下常用的數(shù)據(jù)結(jié)榨崩,以上是它們大概的繼承關(guān)系谴垫。

  • List:列表
  • Map:key-value映射關(guān)系
  • Set:集合中元素唯一
Collection 
├─List
│  ├─ArrayList
│  ├─LinkedList
│  ├─Vector
│ 
├─Set
│  ├─HashSet
│  ├─TreeSet

Map
├─HashMap
├─TreeMap
├─LinkedHashMap

List

ArrayList
使用數(shù)組進(jìn)行緩存,好處的遍歷快母蛛,缺點(diǎn)的插入/刪除中間元素比較慢

//數(shù)據(jù)集合
private transient Object[] elementData;

中間插入分析

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //需要copy(size-n)個(gè)單位翩剪,時(shí)間消耗比較長(zhǎng)
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

當(dāng)數(shù)組空間不足時(shí),會(huì)調(diào)用Arrays.copyOf進(jìn)行擴(kuò)展

 private void grow(int minCapacity) {

        int oldCapacity = elementData.length;
        //一般擴(kuò)展1.5倍的長(zhǎng)度
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

LinkedList使用雙鏈表的結(jié)構(gòu)

transient Node<E> first;
transient Node<E> last;

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;
        }
    }

插入主要有3個(gè)方法彩郊,中間插入因?yàn)椴恍枰猚oye數(shù)組前弯,所以比較快

 private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

ArrayList和LinkedList比較經(jīng)過(guò)上面的分析,中間插入元素LinkedList較好秫逝,因?yàn)?ArrayList 使用數(shù)組博杖,是連續(xù)的內(nèi)存,所以遍歷會(huì)比較快筷登。

Vector的數(shù)據(jù)結(jié)果和實(shí)現(xiàn)跟ArrayList差不多剃根,主要的區(qū)別是操作數(shù)據(jù)的方法都加了synchronized關(guān)鍵字修飾,它是線程安全的前方,也犧牲了些性能狈醉,慎用。

public synchronized void insertElementAt(E obj, int index) {
        modCount++;
        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount);
        }
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        elementData[index] = obj;
        elementCount++;
    }

Map

HashMap使用數(shù)組和單鏈表的形式保存數(shù)據(jù)

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
}

接下來(lái)看一下具體的保存實(shí)現(xiàn)

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        //先計(jì)算獲取hash
        int hash = hash(key);
        //通過(guò)hash計(jì)算數(shù)組的索引惠险,(hash & (length-1))
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //hash相同或key相等時(shí)苗傅,不插入table,替換value班巩,并且返回舊值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //插入
        addEntry(hash, key, value, i);
        return null;
    }

HashMap的擴(kuò)展規(guī)則的是:當(dāng)前元素size大于threshold時(shí)渣慕,將2倍擴(kuò)展,所有元素將重新計(jì)算索引值在插入到新的table抱慌。

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
    
    //threshol的值的當(dāng)前table數(shù)組長(zhǎng)度*loadFactor逊桦,loadFactor可以設(shè)置,默認(rèn)0.75
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);

LinkedHashMap繼承了HashMap抑进,數(shù)據(jù)結(jié)果和HashMap一樣强经,但增加了雙向列表的結(jié)果,用來(lái)記錄插入的順序寺渗,結(jié)果如下

private transient Entry<K,V> header;
private static class Entry<K,V> extends HashMap.Entry<K,V> {
        Entry<K,V> before, after;
}

只要重寫(xiě)createEntry的

 void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

//插入到header前面
 private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }  
 void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

雙向鏈表的結(jié)果為:E0-E1-E2-...-header

TreeMap使用紅-黑樹(shù)結(jié)果匿情,具有元素排序功能

 private transient Entry<K,V> root = null;
 static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left = null;
        Entry<K,V> right = null;
        Entry<K,V> parent;
        boolean color = BLACK;
}

HashSet使用HashMap來(lái)保存對(duì)象

private transient HashMap<E,Object> map;
//使用key來(lái)保持元素唯一
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

TreeSet默認(rèn)使用TreeMap的key來(lái)保存元素兰迫,具有排序功能

 public TreeSet() {
        this(new TreeMap<E,Object>());
    }

小提示

在大多數(shù)據(jù)結(jié)果使用modCount來(lái)記錄操作數(shù),目的是為了檢驗(yàn)數(shù)據(jù)安全炬称,比如在多線程中使用不當(dāng)就會(huì)拋出異常ConcurrentModificationException汁果,比如遍歷或者序列化的時(shí)候

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{

        //保存當(dāng)前操作數(shù)
        int expectedModCount = modCount;
        s.defaultWriteObject();

        clone()
        s.writeInt(size);
        
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        //對(duì)比操作數(shù),不同就拋出異常玲躯,
        //在進(jìn)入其他線程前做好能copy一份數(shù)據(jù)须鼎,使用副本數(shù)據(jù)比較安全
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市府蔗,隨后出現(xiàn)的幾起案子晋控,更是在濱河造成了極大的恐慌,老刑警劉巖姓赤,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赡译,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡不铆,警方通過(guò)查閱死者的電腦和手機(jī)蝌焚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)誓斥,“玉大人只洒,你說(shuō)我怎么就攤上這事±涂樱” “怎么了捐川?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵贞奋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)龄章,這世上最難降的妖魔是什么筝家? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任初婆,我火速辦了婚禮砾跃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘离斩。我一直安慰自己银舱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布跛梗。 她就那樣靜靜地躺著寻馏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茄袖。 梳的紋絲不亂的頭發(fā)上操软,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天嘁锯,我揣著相機(jī)與錄音宪祥,去河邊找鬼聂薪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蝗羊,可吹牛的內(nèi)容都是我干的藏澳。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼耀找,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼翔悠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起野芒,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蓄愁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后狞悲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體撮抓,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年摇锋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丹拯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荸恕,死狀恐怖乖酬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情融求,我是刑警寧澤咬像,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站生宛,受9級(jí)特大地震影響施掏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茅糜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一七芭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔑赘,春花似錦狸驳、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至酥馍,卻和暖如春辩昆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旨袒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工汁针, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留术辐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓施无,卻偏偏與公主長(zhǎng)得像辉词,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猾骡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容