1. 程式人生 > >【原始碼】HashMap原始碼剖析

【原始碼】HashMap原始碼剖析

//-----------------------------------------------------------------------------------

轉載需註明出處:http://blog.csdn.net/chdjj

//-----------------------------------------------------------------------------------

注:以下原始碼基於jdk1.7.0_11


之前的幾篇文章介紹了List集合中一些比較常見的類,比如ArrayList、LinkedList、Vector等。本篇文章將介紹集合框架的另一塊內容——Map集合。本文主要介紹HashMap。 先回顧下雜湊表。 雜湊表定義:根據設定的hash函式和處理衝突的方式(開放定址、公共溢位區、鏈地址、重雜湊...)將一組關鍵字對映到一個有限的連續的地址集上(即bucket陣列或桶陣列),並以關鍵字在地址集中的“像”作為記錄在表中的儲存位置,這種表稱為hash表
,這一對映過程稱為雜湊,所得儲存位置稱為雜湊地址或雜湊地址。hash表具有良好的查詢效能,在衝突概率很小的情況下,時間複雜度為O(1)。 裝載因子:loadfactor = 表中填入的記錄數/雜湊表的長度.所以loadfactor標誌著雜湊表的裝滿程度。 直觀的看,裝載因子越小,發生衝突的概率越小(因為桶中還沒裝幾個資料,就需要擴容),也就是查詢效能越好,但同時浪費的空間就變大。相反,裝載因子越大,發生衝突的概率越大(等到桶快填滿時才能擴容,比如,採用連結串列法處理衝突,在此種情況下,會導致連結串列過長),查詢效能越差,同時浪費的空間會減少。 後面我們會看到,HashMap預設的裝載因子為0.75.
下面,依然是自頂向下分析,首先看Map介面
public interface Map<K,V> {//Map集合的頂級介面定義
    // Query Operations
    int size();
    boolean isEmpty();
    boolean containsKey(Object key);//是否包含指定鍵
    boolean containsValue(Object value);//是否包含指定值
    V get(Object key);
    // Modification Operations
    V put(K key, V value);
    V remove(Object key);
    // Bulk Operations
    void putAll(Map<? extends K, ? extends V> m);//批量放置元素
    void clear();
    // Views
   //三種檢視
    Set<K> keySet();//獲取鍵集
    Collection<V> values();//獲取值集
    Set<Map.Entry<K, V>> entrySet();//獲取鍵值集合

    interface Entry<K,V> {//Map的內部介面,代表一個鍵值對
        K getKey();//獲取鍵
        V getValue(); //獲取值
        V setValue(V value);//設定值
        boolean equals(Object o);
        int hashCode();
    }

    // Comparison and hashing
    boolean equals(Object o);
    int hashCode();
}

Map介面定義了Map集合的操作規範,具體實現交由實現類完成,其內部有個介面Entry,代表一個鍵值對. AbstractMap是一個抽象類,其實現了Map介面中的大部分函式。
public abstract class AbstractMap<K,V> implements Map<K,V>

下面來看幾個方法:
public boolean containsKey(Object key) {
        Iterator<Map.Entry<K,V>> i = entrySet().iterator();//獲得迭代器
        if (key==null) {//判斷key是否為空,分別處理
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)//為空的話使用等號判斷
                    return true;
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))//不為空的話使用equals方法判斷
                    return true;
            }
        }
        return false;
    }
    public boolean containsValue(Object value) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        if (value==null) {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getValue()==null)
                    return true;
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (value.equals(e.getValue()))
                    return true;
            }
        }
        return false;
    }
首先是containsKey和containsValue方法,需要注意的是這裡的key和value是允許為空的,也就是說其子類預設是支援null鍵和值的。這裡的entrySet方法是個抽象方法:
public abstract Set<Entry<K,V>> entrySet();

abstractMap並沒有實現put方法,而簡單的丟擲了異常,這要求子類必須複寫此方法:
 public V put(K key, V value) {
        throw new UnsupportedOperationException();
    }

但是其卻實現了get方法:
 public V get(Object key) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        if (key==null) {//依然是根據鍵值是否為null做不同處理
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    return e.getValue();
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    return e.getValue();
            }
        }
        return null;
    }

下面來看HashMap。

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap繼承了AbstractMap類,並實現了Map介面以及Cloneable、Serializable介面。 其成員變數如下:
 static final int DEFAULT_INITIAL_CAPACITY = 16;//預設初始容量
    static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量為2的30次方
    static final float DEFAULT_LOAD_FACTOR = 0.75f;//預設裝載因子為0.75
    transient Entry<K,V>[] table;//桶陣列,存放鍵值對
    transient int size;//實際儲存的鍵值對個數
    int threshold;//HashMap的閾值,用於判斷是否需要調整HashMap的容量(threshold = 容量*載入因子)
    final float loadFactor;//裝載因子
    transient int modCount;//hashmap被改變的次數
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

這裡我們得到如下資訊:1.HashMap的預設大小為16,即桶陣列的預設長度為16;2.HashMap的預設裝載因子是0.75;3.HashMap內部的桶陣列儲存的是Entry物件,也就是鍵值對物件。 再看構造器:
 public HashMap(int initialCapacity, float loadFactor) {//可手動設定初始容量和裝載因子的構造器
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        // Find a power of 2 >= initialCapacity
        //找出“大於initialCapacity”的最小的2的冪
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
        this.loadFactor = loadFactor;
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        //初始化桶陣列
        table = new Entry[capacity];
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init();//一個鉤子函式,預設實現是空
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public HashMap() {//使用預設的初始容量和預設的載入因子構造HashMap
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }
    // internal utilities

    void init() {
    }

需要注意的點: 1.構造器支援指定初始容量和裝載因子,為避免陣列擴容帶來的效能問題,建議根據需求指定初始容量。裝載因子儘量不要修改,0.75是個比較靠譜的值。 2.實際的容量capacity一般情況下是大於我們傳進去的initialCapacity的,因為內部會通過一個迴圈去找到一個比initialCapacity大且為2的整數次冪的一個數作為實際容量。除非傳進去的數恰好為2的整數次方(capacity取2的整數次冪,是為了使不同hash值發生碰撞的概率較小,這樣就能使元素在雜湊表中均勻地雜湊。)。 通過前面的分析,我們知道了HashMap內部通過Entry陣列儲存鍵值對,那麼這個Entry是怎麼實現的呢? 接下來我們看下Entry的實現
 static class Entry<K,V> implements Map.Entry<K,V> {//實現Map.Entry介面
        final K key;//鍵,final型別,不可更改/
        V value;//值
        Entry<K,V> next;//HashMap通過連結串列法解決衝突,這裡的next指向連結串列的下一個元素
        int hash;//hash值
        /**
         * Creates new entry.
         */
        //構造器需指定連結串列的下一個結點,所有衝突結點放到一個連結串列上
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        public final K getKey() {
            return key;
        }
        public final V getValue() {
            return value;
        }
        //允許設定value
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            //保證鍵值都相等
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }
        public final int hashCode() {//鍵為空則hash值為0,否則通過通過hashcode計算
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }
        public final String toString() {
            return getKey() + "=" + getValue();
        }
        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }
        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }

需要注意的點: 1.HashMap內部陣列儲存的是鍵值對,也就是Entry物件; 2.Entry物件儲存了鍵、值,並持有一個next指標指向下一個Entry物件(HashMap通過連結串列法解決衝突); 3.Entry可以通過setValue設定值,但不允許設定鍵. 下面我們研究下HashMap中比較重要的方法。從put開始:
  public V put(K key, V value) {//向集合中新增一個鍵值對
        if (key == null)//如果鍵為空,則呼叫putForNullKey
            return putForNullKey(value);
        int hash = hash(key);//否則根據key生成一個hash索引值
        int i = indexFor(hash, table.length);//在根據索引值找到插入位置
      //迴圈遍歷指定位置的Entry連結串列,若找到一個鍵與當前鍵完全一致的Entry,那麼覆蓋原來的鍵所對應的值,並返回原值 
       for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //hash值相同且鍵相同
            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;
    }

put方法是向hashMap中新增一個鍵值對,這個方法需要注意的有: 1.允許鍵為null。put方法會針對null鍵做相應的處理,呼叫pullforNullKey方法:
  private V putForNullKey(V value) {
     //空鍵,其hash值為0,必然儲存在陣列的0索引位置上。
//我們需要遍歷0位置的entry連結串列,如果已經有一個null鍵了,那麼也是覆蓋
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
//若沒有,則插入
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

2.不允許兩個鍵相同,如果鍵相同,那麼後插入的鍵所對應的值會覆蓋之前的值。 3.HashMap是通過呼叫hash()方法獲得鍵的hash值,並通過indexFor方法找到實際插入位置,具體程式碼如下:
  final int hash(Object k) {//根據鍵生成hash值
        int h = 0;
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
//根據hash值計算鍵在桶陣列的位置
    static int indexFor(int h, int length) {
        return h & (length-1);//由put方法可知,這個length就是陣列長度,而且由構造器發現數組長度始終為2的整數次方,那麼這個&操作實際上就是是h%length的高效表示方式,可以使結果小於陣列長度.
    }

4.put方法通過addEntry方法將鍵值插到合適位置: 5.當容量超過閾值(threshold)時,會發生擴容,擴容後的陣列是原陣列的兩倍。
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);
    }
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];//獲取原來在該位置上的Entry物件
        table[bucketIndex] = new Entry<>(hash, key, value, e);//將當前的鍵值插到該位置,並作為連結串列的起始結點。其next指標指向先前的Entry
        size++;
    }

這個resize方法就是擴容方法:
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];//建立新陣列
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = oldAltHashing ^ useAltHashing;
        transfer(newTable, rehash);//將原陣列中所有鍵值對轉移至新陣列
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {//需遍歷每個Entry,耗時
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

6.擴容操作需要開闢新陣列,並對原陣列中所有鍵值對重新雜湊,非常耗時。我們應該儘量避免HashMap擴容。 再來看get方法:
public V get(Object key) {
        if (key == null)//若鍵為空
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);//獲取Entry物件
//未找到就返回null,否則返回鍵所對應的值
        return null == entry ? null : entry.getValue();
    }

這個getForNullKey方法就是在陣列0索引位上的連結串列去尋找null鍵:
 private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

而這個getEntry方法就是通過鍵生成hash值,然後得到其在陣列的索引位,查詢該位置的連結串列,找到第一個滿足的鍵,並返會Entry物件:

final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

再看下remove方法:
public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
    final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key);//先計算hash值
        int i = indexFor(hash, table.length);//找到索引位
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        while (e != null) {//遍歷連結串列找到該鍵,並修改連結串列相關指標指向
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
        return e;
    }

最後看下clear方法:
 public void clear() {
        modCount++;
        Entry[] tab = table;
        for (int i = 0; i < tab.length; i++)//遍歷陣列
            tab[i] = null;//置空
        size = 0;
    }
總結: 1.HashMap的預設大小為16,即桶陣列的預設長度為16; 2.HashMap的預設裝載因子是0.75; 3.HashMap內部的桶陣列儲存的是Entry物件,也就是鍵值對物件。
4.構造器支援指定初始容量和裝載因子,為避免陣列擴容帶來的效能問題,建議根據需求指定初始容量。裝載因子儘量不要修改,0.75是個比較靠譜的值。 5.桶陣列的長度始終是2的整數次方(大於等於指定的初始容量),這樣做可以減少衝突概率,提高查詢效率。(可以從indexfor函式中看出,h&(length-1),若length為奇數,length-1為偶數那麼h&(length-1)結果的最後一位必然為0,也就是說所有鍵都被雜湊到陣列的偶數下標位置,這樣會浪費近一半空間。另外,length為2的整數次方也保證了h&(length-1)與h%length等效). 6.HashMap接受null鍵; 7.HashMap不允許鍵重複,但是值是可以重複的。若鍵重複,那麼新值會覆蓋舊值。 8.HashMap通過連結串列法解決衝突問題,每個Entry都有一個next指標指向下一個Entry,衝突元素(不是鍵相同,而是hash值相同)會構成一個連結串列。並且最新插入的鍵值對始終位於連結串列首部。 9.當容量超過閾值(threshold)時,會發生擴容,擴容後的陣列是原陣列的兩倍。擴容操作需要開闢新陣列,並對原陣列中所有鍵值對重新雜湊,非常耗時。我們應該儘量避免HashMap擴容。 10.HashMap非執行緒安全。

相關推薦

原始碼HashMap原始碼剖析

//----------------------------------------------------------------------------------- 轉載需註明出處:http://blog.csdn.net/chdjj //--------------

JavaHashMap原始碼分析——基本概念

在JDK1.8後,對HashMap原始碼進行了更改,引入了紅黑樹。 在這之前,HashMap實際上就是就是陣列+連結串列的結構,由於HashMap是一張雜湊表,其會產生雜湊衝突,為了解決雜湊衝突,HashMap採用了開鏈法,即對於用物件hashCode值計算雜湊

JavaHashMap原始碼分析——常用方法詳解

上一篇介紹了HashMap的基本概念,這一篇著重介紹HasHMap中的一些常用方法:put()get()**resize()** 首先介紹resize()這個方法,在我看來這是HashMap中一個非常重要的方法,是用來調整HashMap中table的容量的,在很多操作中多需要重新計算容量。原始碼如下: 1

JDK8HashMap集合 原始碼閱讀

    JDK8的HashMap資料結構上覆雜了很多,因此讀取效率得以大大提升,關於原始碼中紅黑樹的增刪改查,博主沒有細讀,會在下一篇博文中使用Java實現紅黑樹的增刪改查。     下面是類的結構圖:      程

JDK1.7原始碼分析集合HashMap的死迴圈

前言 在JDK1.7&1.8原始碼對比分析【集合】HashMap中我們遺留了一個問題:為什麼HashMap在呼叫resize() 方法時會出現死迴圈?這篇文章就通過JDK1.7的原始碼來分析並解釋這個問題。 如下,併發場景下使用HashMap造成Race Condition

原創原始碼剖析IO流(四)管道流--轉載請註明出處

一、管道流的特點與作用: PipedInputStream與PipedOutputStream分別為管道輸入流和管道輸出流。管道輸入流通過連線到管道輸出流實現了類似管道的功能,用於執行緒之間的通訊。在使用時,通常由某個執行緒向管道輸出流中寫入資料。根據管道的特性,這些資料會自動傳送到與管道輸

原創原始碼剖析IO流(三)快取流--轉載請註明出處

一、BufferedInputStream 關於BufferedInputStream,首先我們要看一下,官方給予的對於BufferedInputStream這個類的備註: /** * A <code>BufferedInputStream</code> add

原創原始碼剖析IO流(一)輸入流與輸出流--轉載請註明出處

InputStream與OutPutStream兩個抽象類,是所有的流的基礎,首先來看這兩個流的API InputStream: public abstract int read() throws IOException; 從輸入流中讀取資料的下個位元組

原創原始碼剖析IO流(二)檔案流--轉載請註明出處

一、FileInputStream 在FileInputStream中,首先我們需要進行關注的方法,就是read()方法,下面可以來看一下read()方法的原始碼: public int read() throws IOException { return read0()

原始碼Hashtable原始碼剖析

注:以下原始碼基於jdk1.7.0_11 上一篇分析了HashMap的原始碼,相信大家對HashMap都有了更深入的理解。本文將介紹Map集合的另一個常用類,Hashtable。 Hashtable出來的比HashMap早,HashMap 1.2才有,而Hashtable

集合詳解HashMap原始碼解析

一、HashMap概述 二、HashMap的資料結構 三、HashMap原始碼分析 1.繼承 2、關鍵屬

筆記ThreadPoolExecutor原始碼閱讀(三)

執行緒數量的維護 執行緒池的大小有兩個重要的引數,一個是corePoolSize(核心執行緒池大小),另一個是maximumPoolSize(最大執行緒大小)。執行緒池主要根據這兩個引數對執行緒池中執行緒的數量進行維護。 需要注意的是,執行緒池建立之初是沒有任何可用執行緒的。只有在有任務到達後,才開始建立

AndroidRetrofit原始碼分析

Retrofit簡介 retrofit n. 式樣翻新,花樣翻新 vt. 給機器裝置裝配(新部件),翻新,改型 Retrofit 是一個 RESTful 的 HTTP 網路請求框架的封裝。注意這裡並沒有說它是網路請求框架,主要原因在於網路請求的工作並不是 Retrofit

AndroidOkHttp原始碼分析

Android為我們提供了兩種HTTP互動的方式:HttpURLConnection 和 Apache HttpClient,雖然兩者都支援HTTPS,流的上傳和下載,配置超時,IPv6和連線池,已足夠滿足我們各種HTTP請求的需求。但更高效的使用HTTP 可以讓您的應用執行更快、更節省

NLPjieba原始碼分析之關鍵字提取(TF-IDF/TextRank)

【一】綜述 利用jieba進行關鍵字提取時,有兩種介面。一個基於TF-IDF演算法,一個基於TextRank演算法。TF-IDF演算法,完全基於詞頻統計來計算詞的權重,然後排序,在返回TopK個詞作為關鍵字。TextRank相對於TF-IDF,基本思路一致,也是基於統計的思想,只不過其計算詞的權

NLPjieba原始碼分析之分詞

【一】詞典載入 利用jieba進行分詞時,jieba會自動載入詞典,這裡jieba使用python中的字典資料結構進行字典資料的儲存,其中key為word,value為frequency即詞頻。 1. jieba中的詞典如下: jieba/dict.txt X光 3 n X光線 3

NLPjieba原始碼分析之詞性標註

【一】詞性標註 詞性標註分為2部分,首先是分詞,然後基於分詞結果做詞性標註。 【二】jieba的詞性標註程式碼流程詳解 1. 程式碼位置 jieba/posseg/_init_.py 2. 流程分析 def cut(sentence, HMM=True): """

NLPfasttext原始碼解析

【一】關於fasttext fasttext是Facebook開源的一個工具包,用於詞向量訓練和文字分類。該工具包使用C++11編寫,全部使用C++11 STL(這裡主要是thread庫),不依賴任何第三方庫。具體使用方法見:https://fasttext.cc/ ,在Linux 使用非常方便

8netty4原始碼分析-flush

轉自 http://xw-z1985.iteye.com/blog/1971904 Netty的寫操作由兩個步驟組成: Write:將msg儲存到ChannelOutboundBuffer中 Flush:將msg從ChannelOutboundBuffer中flush到套接字的傳送緩

7netty4原始碼分析-write

轉自 http://xw-z1985.iteye.com/blog/1970844 Netty的寫操作由兩個步驟組成: Write:將msg儲存到ChannelOutboundBuffer中 Flush:將msg從ChannelOutboundBuffer中flush到套接字的傳送緩