1. 程式人生 > >ConcurrentSkipListMap原碼解析

ConcurrentSkipListMap原碼解析

SkipList介紹

1. SkipList(跳錶),在理論上能夠在O(log(n))時間內完成查詢、插入、刪除操作。SkipList是一種紅黑樹的替代方案,由於SkipList與紅黑樹相比無論從理論和實現都簡單許多,所以得到了很好的推廣。SkipList是基於一種統計學原理實現的,有可能出現最壞情況,即查詢和更新操作都是O(n)時間複雜度,但從統計學角度分析這種概率極小。使用SkipList型別的資料結構更容易控制多執行緒對集合訪問的處理,因為連結串列的區域性處理性比較好,當多個執行緒對SkipList進行更新操作(指插入和刪除)時,SkipList具有較好的區域性性,每個單獨的操作,對整體資料結構影響較小。而如果使用紅黑樹,很可能一個更新操作,將會波及整個樹的結構,其區域性性較差。因此使用SkipList更適合實現多個執行緒的併發處理。


2. ConcurrentSkipListMap採用Lock-Free Skip List實現。非併發版TreeMap是紅黑樹實現的,而為什麼併發版本不採用樹形結構呢,作者Doug Lea也說了,暫時沒有很好的無鎖操作樹形結構的演算法:The reason is that there are no known efficient lock-free insertion and deletion algorithms for search trees. 據說已經有lock-free trees的論文了: Lock-Free Red-Black Trees Using CAS,相關問題可以參見

Skip List vs. Binary Tree的討論。

3. 呼叫ConcurrentSkipListMap的size時,由於多個執行緒可以同時對對映表進行操作,所以對映表需要遍歷整個連結串列才能返回元素個數,這個操作是個O(log(n))的操作。

原始碼解析(基於jdk1.8.0_40)

無鎖鏈表

ConcurrentSkipListMap能夠採用無鎖實現,是因為採用了無鎖操作(增加/刪除)連結串列的演算法--在刪除連結串列結點時,使用標記刪除的思想,先將結點value設定為null(步驟1),然後插入一個後繼marker結點(步驟2,如果失敗則說明有併發的insert,重試append marker就好了)。以後在查詢發現marker結點時才進行真正的刪除(步驟3)。這樣設計讓insert/delete操作需要競爭修改同一個結點的next指標,避免了併發操作出現insert被吃掉的情況(例如併發insert/delete操作單鏈表時,執行緒A insert了一個結點x到n結點後面,而此時執行緒B立即delete掉了n結點,如果不加鎖或者append marker標記的話,新結點x就被一起誤刪了)。下面的演算法描述來源於該類的doc註釋:

     * Here's the sequence of events for a deletion of node n with
     * predecessor b and successor f, initially:
     *
     *        +------+       +------+      +------+
     *   ...  |   b  |------>|   n  |----->|   f  | ...
     *        +------+       +------+      +------+
     *
     * 1. CAS n's value field from non-null to null.
     *    From this point on, no public operations encountering
     *    the node consider this mapping to exist. However, other
     *    ongoing insertions and deletions might still modify
     *    n's next pointer.
     *
     * 2. CAS n's next pointer to point to a new marker node.
     *    From this point on, no other nodes can be appended to n.
     *    which avoids deletion errors in CAS-based linked lists.
     *
     *        +------+       +------+      +------+       +------+
     *   ...  |   b  |------>|   n  |----->|marker|------>|   f  | ...
     *        +------+       +------+      +------+       +------+
     *
     * 3. CAS b's next pointer over both n and its marker.
     *    From this point on, no new traversals will encounter n,
     *    and it can eventually be GCed.
     *        +------+                                    +------+
     *   ...  |   b  |----------------------------------->|   f  | ...
     *        +------+                                    +------+
     *

資料結構

 /*
     * This class implements a tree-like two-dimensionally linked skip
     * list in which the index levels are represented in separate
     * nodes from the base nodes holding data.  There are two reasons
     * for taking this approach instead of the usual array-based
     * structure: 1) Array based implementations seem to encounter
     * more complexity and overhead 2) We can use cheaper algorithms
     * for the heavily-traversed index lists than can be used for the
     * base lists.  Here's a picture of some of the basics for a
     * possible list with 2 levels of index:
     *
     * Head nodes          Index nodes
     * +-+    right        +-+                      +-+
     * |2|---------------->| |--------------------->| |->null
     * +-+                 +-+                      +-+
     *  | down              |                        |
     *  v                   v                        v
     * +-+            +-+  +-+       +-+            +-+       +-+
     * |1|----------->| |->| |------>| |----------->| |------>| |->null
     * +-+            +-+  +-+       +-+            +-+       +-+
     *  v              |    |         |              |         |
     * Nodes  next     v    v         v              v         v
     * +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
     * | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
     * +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
     *
這是一個level等於2的skip list,注意初始化時level為1。Level大於等於1的結點均為Index結點,特別的,最左邊的Index結點又是HeadIndex結點。而Level 0 也叫base-level,是Nodes單鏈表,是通過Level 1 的Index.node訪問到的。

Node:

/**
     * Nodes hold keys and values, and are singly linked in sorted
     * order, possibly with some intervening marker nodes. The list is
     * headed by a dummy node accessible as head.node. The value field
     * is declared only as Object because it takes special non-V
     * values for marker and header nodes.
     */
    static final class Node<K,V> {
        final K key;
        volatile Object value;
        volatile Node<K,V> next;

        /**
         * Creates a new regular node.
         */
        Node(K key, Object value, Node<K,V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        /**
         * Creates a new marker node. A marker is distinguished by
         * having its value field point to itself.  Marker nodes also
         * have null keys, a fact that is exploited in a few places,
         * but this doesn't distinguish markers from the base-level
         * header node (head.node), which also has a null key.
         */
        Node(Node<K,V> next) {
            this.key = null;
            this.value = this;
            this.next = next;
        }

        /**
         * compareAndSet value field
         */
        boolean casValue(Object cmp, Object val) {
            return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val);
        }

        /**
         * compareAndSet next field
         */
        boolean casNext(Node<K,V> cmp, Node<K,V> val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

        /**
         * Returns true if this node is a marker. This method isn't
         * actually called in any current code checking for markers
         * because callers will have already read value field and need
         * to use that read (not another done here) and so directly
         * test if value points to node.
         *
         * @return true if this node is a marker node
         */
        boolean isMarker() {
            return value == this;
        }

        /**
         * Returns true if this node is the header of base-level list.
         * @return true if this node is header node
         */
        boolean isBaseHeader() {
            return value == BASE_HEADER;
        }

        /**
         * Tries to append a deletion marker to this node.
         * @param f the assumed current successor of this node
         * @return true if successful
         */
        boolean appendMarker(Node<K,V> f) {
            return casNext(f, new Node<K,V>(f));
        }

        /**
         * Helps out a deletion by appending marker or unlinking from
         * predecessor. This is called during traversals when value
         * field seen to be null.
         * @param b predecessor
         * @param f successor
         */
        void helpDelete(Node<K,V> b, Node<K,V> f) {
            /*
             * Rechecking links and then doing only one of the
             * help-out stages per call tends to minimize CAS
             * interference among helping threads.
             */
            if (f == next && this == b.next) {
                if (f == null || f.value != f) // not already marked
                    casNext(f, new Node<K,V>(f));
                else
                    b.casNext(this, f.next);
            }
        }
		
		......
    }

Index:
    /**
     * Index nodes represent the levels of the skip list.  Note that
     * even though both Nodes and Indexes have forward-pointing
     * fields, they have different types and are handled in different
     * ways, that can't nicely be captured by placing field in a
     * shared abstract class.
     */
    static class Index<K,V> {
        final Node<K,V> node;
        final Index<K,V> down;
        volatile Index<K,V> right;

        /**
         * Creates index node with given values.
         */
        Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
            this.node = node;
            this.down = down;
            this.right = right;
        }

        /**
         * compareAndSet right field
         */
        final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
            return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
        }

        /**
         * Returns true if the node this indexes has been deleted.
         * @return true if indexed node is known to be deleted
         */
        final boolean indexesDeletedNode() {
            return node.value == null;
        }

        /**
         * Tries to CAS newSucc as successor.  To minimize races with
         * unlink that may lose this index node, if the node being
         * indexed is known to be deleted, it doesn't try to link in.
         * @param succ the expected current successor
         * @param newSucc the new successor
         * @return true if successful
         */
        final boolean link(Index<K,V> succ, Index<K,V> newSucc) {
            Node<K,V> n = node;
            newSucc.right = succ;
            return n.value != null && casRight(succ, newSucc);
        }

        /**
         * Tries to CAS right field to skip over apparent successor
         * succ.  Fails (forcing a retraversal by caller) if this node
         * is known to be deleted.
         * @param succ the expected current successor
         * @return true if successful
         */
        final boolean unlink(Index<K,V> succ) {
            return node.value != null && casRight(succ, succ.right);
        }

		......
    }

HeadIndex:
    /**
     * Nodes heading each level keep track of their level.
     */
    static final class HeadIndex<K,V> extends Index<K,V> {
        final int level;
        HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
            super(node, down, right);
            this.level = level;
        }
    }

建構函式和初始化方法:

    public ConcurrentSkipListMap() {
        this.comparator = null;
        initialize();
    }

    /**
     * Constructs a new, empty map, sorted according to the specified
     * comparator.
     *
     * @param comparator the comparator that will be used to order this map.
     *        If {@code null}, the {@linkplain Comparable natural
     *        ordering} of the keys will be used.
     */
    public ConcurrentSkipListMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
        initialize();
    }

    public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
        this.comparator = null;
        initialize();
        putAll(m);
    }

    public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
        this.comparator = m.comparator();
        initialize();
        buildFromSorted(m);
    }

initialize方法,注意初始的HeadIndex level為1:
    /**
     * Initializes or resets state. Needed by constructors, clone,
     * clear, readObject. and ConcurrentSkipListSet.clone.
     * (Note that comparator must be separately initialized.)
     */
    private void initialize() {
        keySet = null;
        entrySet = null;
        values = null;
        descendingMap = null;
        head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
                                  null, null, 1);
    }

doGet方法:

程式碼中的Node b, n, f 分別對應文章開頭圖示的單鏈表。過程比較清晰,先通過findPredecessor方法搜尋到base level層的Node結點,然後再繼續順序向後搜尋單鏈表,如果發現數據不一致的時候則回到outer迴圈重新查詢。
    /**
     * Gets value for key. Almost the same as findNode, but returns
     * the found value (to avoid retries during re-reads)
     *
     * @param key the key
     * @return the value, or null if absent
     */
    private V doGet(Object key) {
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        outer: for (;;) {
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                Object v; int c;
                if (n == null)
                    break outer;
                Node<K,V> f = n.next;
                if (n != b.next)                // inconsistent read
                    break;
                if ((v = n.value) == null) {    // n is deleted
                    n.helpDelete(b, f);
                    break;
                }
                if (b.value == null || v == n)  // b is deleted
                    break;
                if ((c = cpr(cmp, key, n.key)) == 0) {
                    @SuppressWarnings("unchecked") V vv = (V)v;
                    return vv;
                }
                if (c < 0)
                    break outer;
                b = n;
                n = f;
            }
        }
        return null;
    }

findPredecessor方法

通過索引查詢到Level 1層的Index結點q,然後返回q.node(即base level層的Node)。另外該方法還提供一個額外功能:發現Node結點被刪除後,刪除其Index索引結點。

    /**
     * Returns a base-level node with key strictly less than given key,
     * or the base-level header if there is no such node.  Also
     * unlinks indexes to deleted nodes found along the way.  Callers
     * rely on this side-effect of clearing indices to deleted nodes.
     * @param key the key
     * @return a predecessor of key
     */
    private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
        if (key == null)
            throw new NullPointerException(); // don't postpone errors
        for (;;) {
            for (Index<K,V> q = head, r = q.right, d;;) {
                if (r != null) {
                    Node<K,V> n = r.node;
                    K k = n.key;
                    if (n.value == null) {//發現結點已被標記刪除,則呼叫前繼結點q.unlink()方法刪除Index索引結點
                        if (!q.unlink(r))
                            break;           // restart
                        r = q.right;         // reread r
                        continue;
                    }
                    if (cpr(cmp, key, k) > 0) {
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                if ((d = q.down) == null)
                    return q.node;
                q = d;
                r = d.right;
            }
        }
    }

doPut方法:

第一步,outer迴圈,搜尋Skip List,找到key相等的結點則做更新操作,否則建立新結點並插入到base level層。

第二步,判斷是否要幫助新增結點建立索引,如果要,使用隨機數計算得到level值,作為要建立索引的層數。如果level>當前head.level,則要提升總Level層數(每次Level層數只增加1),並cas更新head屬性。然後構建Level 1 到level層的Index結點鏈(通過連結Index.down指標,後續再補建Index.right指標)。 

第三步,splice迴圈,從level層開始逐層補建Index.right指標。

    /**
     * Main insertion method.  Adds element if not present, or
     * replaces value if present and onlyIfAbsent is false.
     * @param key the key
     * @param value the value that must be associated with key
     * @param onlyIfAbsent if should not insert if already present
     * @return the old value, or null if newly inserted
     */
    private V doPut(K key, V value, boolean onlyIfAbsent) {
        Node<K,V> z;             // added node
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        outer: for (;;) {
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                if (n != null) {
                    Object v; int c;
                    Node<K,V> f = n.next;
                    if (n != b.next)               // inconsistent read
                        break;
                    if ((v = n.value) == null) {   // n is deleted
                        n.helpDelete(b, f);
                        break;
                    }
                    if (b.value == null || v == n) // b is deleted
                        break;
                    if ((c = cpr(cmp, key, n.key)) > 0) {
                        b = n;
                        n = f;
                        continue;
                    }
                    if (c == 0) {
                        if (onlyIfAbsent || n.casValue(v, value)) {
                            @SuppressWarnings("unchecked") V vv = (V)v;
                            return vv;
                        }
                        break; // restart if lost race to replace value
                    }
                    // else c < 0; fall through
                }

                z = new Node<K,V>(key, value, n);
                if (!b.casNext(n, z))
                    break;         // restart if lost race to append to b
                break outer;
            }
        }

        int rnd = ThreadLocalRandom.nextSecondarySeed();
        if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
            int level = 1, max;
            while (((rnd >>>= 1) & 1) != 0)
                ++level;
            Index<K,V> idx = null;
            HeadIndex<K,V> h = head;
            if (level <= (max = h.level)) {
                for (int i = 1; i <= level; ++i)
                    idx = new Index<K,V>(z, idx, null);
            }
            else { // try to grow by one level
                level = max + 1; // hold in array and later pick the one to use
                @SuppressWarnings("unchecked")Index<K,V>[] idxs =
                    (Index<K,V>[])new Index<?,?>[level+1];
                for (int i = 1; i <= level; ++i)
                    idxs[i] = idx = new Index<K,V>(z, idx, null);
                for (;;) {
                    h = head;
                    int oldLevel = h.level;
                    if (level <= oldLevel) // lost race to add level
                        break;
                    HeadIndex<K,V> newh = h;
                    Node<K,V> oldbase = h.node;
                    for (int j = oldLevel+1; j <= level; ++j)
                        newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
                    if (casHead(h, newh)) {
                        h = newh;
                        idx = idxs[level = oldLevel];
                        break;
                    }
                }
            }
            // find insertion points and splice in
            splice: for (int insertionLevel = level;;) {
                int j = h.level;
                for (Index<K,V> q = h, r = q.right, t = idx;;) {
                    if (q == null || t == null)
                        break splice;
                    if (r != null) {
                        Node<K,V> n = r.node;
                        // compare before deletion check avoids needing recheck
                        int c = cpr(cmp, key, n.key);
                        if (n.value == null) {
                            if (!q.unlink(r))
                                break;
                            r = q.right;
                            continue;
                        }
                        if (c > 0) {
                            q = r;
                            r = r.right;
                            continue;
                        }
                    }

                    if (j == insertionLevel) {
                        if (!q.link(r, t))
                            break; // restart
                        if (t.node.value == null) {
                            findNode(key);
                            break splice;
                        }
                        if (--insertionLevel == 0)
                            break splice;
                    }

                    if (--j >= insertionLevel && j < level)
                        t = t.down;
                    q = q.down;
                    r = q.right;
                }
            }
        }
        return null;
    }

doRemove方法:

該方法流程類似doGet(),查詢到base level層的Node結點後,做標記刪除n.casValue(v, null),並append Marker結點。後續利用findPredecessor方法清除無效的Index索引結點。

    /**
     * Main deletion method. Locates node, nulls value, appends a
     * deletion marker, unlinks predecessor, removes associated index
     * nodes, and possibly reduces head index level.
     *
     * Index nodes are cleared out simply by calling findPredecessor.
     * which unlinks indexes to deleted nodes found along path to key,
     * which will include the indexes to this node.  This is done
     * unconditionally. We can't check beforehand whether there are
     * index nodes because it might be the case that some or all
     * indexes hadn't been inserted yet for this node during initial
     * search for it, and we'd like to ensure lack of garbage
     * retention, so must call to be sure.
     *
     * @param key the key
     * @param value if non-null, the value that must be
     * associated with key
     * @return the node, or null if not found
     */
    final V doRemove(Object key, Object value) {
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        outer: for (;;) {
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                Object v; int c;
                if (n == null)
                    break outer;
                Node<K,V> f = n.next;
                if (n != b.next)                    // inconsistent read
                    break;
                if ((v = n.value) == null) {        // n is deleted
                    n.helpDelete(b, f);
                    break;
                }
                if (b.value == null || v == n)      // b is deleted
                    break;
                if ((c = cpr(cmp, key, n.key)) < 0)
                    break outer;
                if (c > 0) {
                    b = n;
                    n = f;
                    continue;
                }
                if (value != null && !value.equals(v))
                    break outer;
                if (!n.casValue(v, null))//標記刪除
                    break;
                if (!n.appendMarker(f) || !b.casNext(n, f))//插入marker結點
                    findNode(key);                  // retry via findNode
                else {
                    findPredecessor(key, cmp);      // clean index
                    if (head.right == null)
                        tryReduceLevel();
                }
                @SuppressWarnings("unchecked") V vv = (V)v;
                return vv;
            }
        }
        return null;
    }

相關內容連結

相關推薦

ConcurrentSkipListMap解析

SkipList介紹 1. SkipList(跳錶),在理論上能夠在O(log(n))時間內完成查詢、插入、刪除操作。SkipList是一種紅黑樹的替代方案,由於SkipList與紅黑樹相比無論從理論和實現都簡單許多,所以得到了很好的推廣。SkipList是基於一種統計學

ConcurrentLinkedQueue解析

描述 ConcurrentLinkedQueue是一個基於單鏈表的無界執行緒安全佇列,該佇列是FIFO的。ConcurrentLinkedQueue/ConcurrentLinkedDeue和LinkedBlockingQueue/LinkedBlockingDeue 相

Glide生命週期解析

Glide生命週期管理的原碼其實就在 Glide.with(getContext()) 這個方法裡面 @NonNull public static RequestManager with(@NonNull Context context) { return

Okhttp3解析(一)

首先看一下Okhttp3是怎麼進行請求的//建立OkHttpClient物件 OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS) .writeTim

【JUC源解析ConcurrentSkipListMap

sde 空指針 順序 pri 不一致 前驅 values dex warning 簡介 基於跳表,支持並發,有序的哈希表。 跳表 紅色路徑為尋找結點F。 拿空間換時間,時間復雜度,O(nlogn). 源碼分析 內部類 Node 屬性 1

C++ 、補碼和反解析

首先對於有符號數,第一位的位置是表示的正負,為1時是負數,為0時是正數 其次,正數的原碼、補碼、反碼一致 負數的反碼是原碼的數值位(也就是除了第一位的符號位之外)取反,負數的補碼是反碼加1  在計算

,反,補碼雜談

http 同余 而已 機器 wan 機器數 整理 把他 需要 本文從原碼講起。通過簡述原碼,反碼和補碼存在的作用,加深對補碼的認識。力爭讓你對補碼的概念不再局限於:負數的補碼等於反碼加一。 接觸過計算機或電子信息相關課程的同學,應該都或多或少看過補碼這哥仨。每次都是

laravel-index源解析

composer kernel 加載 時間 exceptio app 使用 def cati <?php /*設置腳本開始時間 define(‘LARAVEL_START‘, microtime(true)); 引入composer的自動加載,在composer

jQuery源解析(架構與依賴模塊)

源碼 cto and click dom元素 ack bsp 性能 selector 回溯處理 jQuery對象棧:jQuery內部維護著一個jQuery對象棧。每個遍歷方法都會找到一組新元素(一個jQuery對象),然後jQuery會把這組元素推入到棧中。 而每個jQue

opengl es入門---常見代解析

字符串數組 chm 視口 posit detail 編寫 組件 eat 包含著 轉自:http://blog.csdn.net/wangyuchun_799/article/details/7736928,尊重原創! 3.1創建渲染緩沖區 GLuint m

gunicorn syncworker 源解析

_for html bit int 文件的 ini exc mks list   gunicorn支持不同的worker類型,同步或者異步,異步的話包括基於gevent、基於eventlet、基於Aiohttp(python版本需要大於3.3),也有多線程的版本。下面是gu

HashMap源代解析

new imu 感覺 sna 因子和 遍歷 會有 修改 color   HashMap原理剖析   之前有看過別人的HashMap源代碼的分析,今天嘗試自己來分析一波,純屬個人愚見。聽一些老的程序員說過,當別人跟你說用某樣技術到項目中去,而你按照別人的想法實現了的時候,你只

ArrayList源解析(一)

unary 定義 cte 轉換 ora gif 成員類 con ins 目錄 1.位置 2.變量和常量 3.構造函數 4.trimToSize()方法 正文 源碼解析系列主要對Java的源碼進行詳細的說明,由於水平有限,難免出現錯誤或描述不準確的地方,還請大家指

Java二進制指令代解析

pos 無法 兩個 ade ceo default val center 時間 http://www.blogjava.net/DLevin/archive/2011/09/13/358497.html http://blog.csdn.net/sum_rain/art

02.15_Java語言基礎(補碼的講解).avi

cnblogs logs 基礎 java語言基礎 alt blog 補碼 http nbsp 02.15_Java語言基礎(原碼反碼補碼的講解).avi

Stack源解析

public syn 開頭 sync sys 彈出 for循環 last -s Stack介紹:   堆棧(Stack)是一個元素序列。盾戰中唯一能被刪除、訪問或修改的元素是最近插入的元素。這個元素就是位於堆棧頂部的那個元素。   舉例來說,自助餐廳的盤子架就是一個由盤子構

java基礎:補碼

gin 微軟雅黑 基礎 image p s 分享 ont style mil 計算機在操作的時候,都是采用數據對應二進制的補碼來計算的: 原碼 反碼 補碼 原碼:用原碼,反碼,補碼來分別表示+7,和-7. 首先得到7的二進制:111 java基

CopyOnWriteArrayList源解析

except set nts nbsp color cep 多線程 cnblogs 異常 CopyOnWriteArrayList是java1.5版本提供的一個線程安全的ArrayList變體。 在講解5.1.1ArrayList的時候,有說明ArrayLis

機器學習完整過程案例分布解析,python代解析

然而 表示 離散 好的 了解 成了 傳感器 att and 所謂學習問題,是指觀察由n個樣本組成的集合,並依據這些數據來預測未知數據的性質。 學習任務(一個二分類問題): 區分一個普通的互聯網檢索Query是否具有某個垂直領域的意圖。如果如今有一個O2O領域的垂直

ajax 底層源解析

源碼 不同 操作 cati 數據 t對象 增長 asc orm 對象: XMLHttpRequest屬性:readyState請求狀態,開始請求時值為0直到請求完成這個值增長到4 responseText目前為止接收到的響應體,readyState<3此屬性為空字符串