dojo/query源码解析

dojo/query模块是dojo为开发者提供的dom查询接口。该模块的输出对象是一个使用css选择符来查询dom元素并返回NodeList对象的函数。同时,dojo/query模块也是一个插件,开发者可以使用自定义的查询引擎,query模块会负责将引擎的查询结果包装成dojo自己的NodeList对象。

require(["dojo/query!sizzle"], function(query){
query("div")...

  要理解这个模块就要搞清楚两个问题:

  1. 如何查询,查询的原理?
  2. 查询结果是什么,如何处理查询结果?

  这两个问题涉及到本文的两个主题:选择器引擎和NodeList。

选择器引擎

  前端的工作必然涉及到与DOM节点打交道,我们经常需要对一个DOM节点进行一系列的操作。但我们如何找到这个DOM节点呢,为此我们需要一种语言来告诉浏览器我们想要就是这个语言描述的dom节点,这种语言就是CSS选择器。比如我们想浏览器描述一个dom节点:div > p + .bodhi input[type="checkbox"],它的意思是在div元素下的直接子元素p的下一个class特性中含有bodhi的兄弟节点下的type属性是checkbox的input元素。

  选择符种类

  • 元素选择符:通配符*、类型选择符E、类选择符E.class、ID选择符E#id
  • 关系选择符:包含(E F)、子选择符(E>F)、相邻选择符(E+F)、兄弟选择符(E~F)
  • 属性选择符: E[att]、E[att="val"]、E[att~="val"]、E[att^="val"]、E[att$="val"]、E[att*="val"]
  • 伪类选择符
  • 伪对象选择符:E:first-letter、E:first-line、E:before、E:after、E::placehoser、E::selection

  通过选择器来查询DOM节点,最简单的方式是依靠浏览器提供的几个原生接口:getElementById、getElementsByTagName、getElementsByName、getElementsByClassName、querySelector、querySelectorAll。但因为低版本浏览器不完全支持这些接口,而我们实际工作中有需要这些某些高级接口,所以才会有各种各样的选择器引擎。所以选择器引擎就是帮我们查询DOM节点的代码类库。

  选择器引擎很简单,但是一个高校的选择器引擎会涉及到词法分析和预编译。不懂编译原理的我表示心有余而力不足。

  但需要知道的一点是:解析css选择器的时候,都是按照从右到左的顺序来的,目的就是为了提高效率。比如“div p span.bodhi”;如果按照正向查询,我们首先要找到div元素集合,从集合中拿出一个元素,再找其中的p集合,p集合中拿出一个元素找class属性是bodhi的span元素,如果没找到重新回到开头的div元素,继续查找。这样的效率是极低的。相反,如果按照逆向查询,我们首先找出class为bodhi的span元素集合,在一直向上回溯看看祖先元素中有没有选择符内的元素即可,孩子找父亲很容易,但父亲找孩子是困难的。

选择器引擎为了优化效率,每一个选择器都可以被分割为好多部分,每一部分都会涉及到标签名(tag)、特性(attr)、css类(class)、伪节点(persudo)等,分割的方法与选择器引擎有关。比如选择器 div > p + .bodhi input[type="checkbox"]如果按照空格来分割,那它会被分割成以下几部分:

  • div
  • >
  • p
  • +
  • .bodhi
  • input[type="checkbox"]

 对于每一部分选择器引擎都会使用一种数据结构来表达这些选择符,如dojo中acme使用的结构:

{
                    query: null, // the full text of the part's rule
                    pseudos: [], // CSS supports multiple pseud-class matches in a single rule
                    attrs: [],    // CSS supports multi-attribute match, so we need an array
                    classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy
                    tag: null,    // only one tag...
                    oper: null, // ...or operator per component. Note that these wind up being exclusive.
                    id: null,    // the id component of a rule
                    getTag: function(){
                        return caseSensitive ? this.otag : this.tag;
                    }
                }

 从这里可以看到有专门的结构来管理不同的类型的选择符。分割出来的每一部分在acme中都会生成一个part,part中有tag、伪元素、属性、元素关系等。。;所有的part都被放到queryParts数组中。然后从右到左每次便利一个part,低版本浏览器虽然不支持高级接口,但是一些低级接口还是支持的,比如:getElementsBy*;对于一个part,先匹配tag,然后判断class、attr、id等。这是一种解决方案,但这种方案有很严重的效率问题。(后面这句是猜想)试想一下:我们可不可以把一个part中有效的几项的判断函数来组装成一个函数,对于一个part只执行一次即可。没错,acme就是这样来处理的(这里涉及到预编译问题,看不明白的自动忽略即可。。。)

  

  dojo/query模块的选择器引擎通过dojo/selector/loader来加载。如果没有在dojoConfig中配置selectorEngine属性,那么loader模块会自己判断使用acme和是lite引擎,原则是高版本浏览器尽量使用lite,而低版本尽量使用acme。

define(["../has", "require"],
        function(has, require){

"use strict";
var testDiv = document.createElement("div");
has.add("dom-qsa2.1", !!testDiv.querySelectorAll);
has.add("dom-qsa3", function(){
            // test to see if we have a reasonable native selector engine available
            try{
                testDiv.innerHTML = "<p class='TEST'></p>"; // test kind of from sizzle
                // Safari can't handle uppercase or unicode characters when
                // in quirks mode, IE8 can't handle pseudos like :empty
                return testDiv.querySelectorAll(".TEST:empty").length == 1;
            }catch(e){}
        });
var fullEngine;
var acme = "./acme", lite = "./lite";
return {
    // summary:
    //        This module handles loading the appropriate selector engine for the given browser

    load: function(id, parentRequire, loaded, config){
        var req = require;
        // here we implement the default logic for choosing a selector engine
        id = id == "default" ? has("config-selectorEngine") || "css3" : id;
        id = id == "css2" || id == "lite" ? lite :
                id == "css2.1" ? has("dom-qsa2.1") ? lite : acme :
                id == "css3" ? has("dom-qsa3") ? lite : acme :
                id == "acme" ? acme : (req = parentRequire) && id;
        if(id.charAt(id.length-1) == '?'){
            id = id.substring(0,id.length - 1);
            var optionalLoad = true;
        }
        // the query engine is optional, only load it if a native one is not available or existing one has not been loaded
        if(optionalLoad && (has("dom-compliant-qsa") || fullEngine)){
            return loaded(fullEngine);
        }
        // load the referenced selector engine
        req([id], function(engine){
            if(id != "./lite"){
                fullEngine = engine;
            }
            loaded(engine);
        });
    }
};
});

  选择器引擎的代码晦涩难懂,我们只需要关心最终暴露出来的接口的用法即可。

  acme:

query = function(/*String*/ query, /*String|DOMNode?*/ root)
............
query.filter = function(/*Node[]*/ nodeList, /*String*/ filter, /*String|DOMNode?*/ root)
............
return query;

  lite:

liteEngine = function(selector, root)
...............
liteEngine.match =  function(node, selector, root)
..............

return liteEngine

NodeList

  NodeList来自于原生DOM,是一系列dom节点的集合,一个类素组对象,MDN中的解释:

  NodeList objects are collections of nodes such as those returned by Node.childNodes and the document.querySelectorAll method.

  我们看到document.querySelectorAll方法返回一个NodeList对象,而且这个方法返回的NodeList对象是一个静态的集合。

  In other cases, the NodeList is a static collection, meaning any subsequent change in the DOM does not affect the content of the collection. document.querySelectorAll returns a static NodeList.

  所以大多数的前端类库都参照原生设计,查询接口返回的都是一个静态集合。只是有的明确点明如dojo,有的含蓄的实现如jQuery。

  要了解dojo中的NodeList需要把握以下规则:

  1. dojo中的NodeList就是扩展了能力的Array实例。所以需要一个函数将原生array包装起来
  2. NodeList的任何方法返回的还是NodeList实例。就像Array的slice、splice还是返回一个array一样

  

  has("array-extensible")的作用是判断数组是否可以被继承,如果原生的数组是可被继承的,那就将NodeList的原型指向一个数组实例,否则指向普通对象。

var nl = NodeList, nlp = nl.prototype =
        has("array-extensible") ? [] : {};// extend an array if it is extensible

  下面这句话需要扎实的基本功,如果理解这句话,整个脉络就会变得清晰起来。

var NodeList = function(array){
        var isNew = this instanceof nl && has("array-extensible"); // 是不是通过new运算符模式调用
        。。。。。。。。。。
    };

  new的作用等于如下函数:

Function.prototype.new = function(){
    // this指向的new运算符所作用的构造函数
    var that = Object.create(this.prototype);

    var other = this.apply(that, arguments);

    return (other && typeof other === 'object') || that;
}

  放到NodeList身上是这样的:

var nl = new NodeList(array);
//等于一下操作

var that = Object.create(NodeList.prototype);
//这时候NodeList中的this关键字指向that,that是NodeList的实例
var other = NodeList.apply(that, array);

nl = (other && typeof other === 'object') || that;

  isNew为true,保证了NodeList实例是一个经过扩展的array对象。

  NodeList函数的源码:

var NodeList = function(array){
        var isNew = this instanceof nl && has("array-extensible"); // 是不是通过new运算符模式调用
        if(typeof array == "number"){
            array = Array(array); // 如果array是数字,就创建一个array数量的数组
        }
        //如果array是一个数组或类数组对象,nodeArray等于array否者是arguments
        var nodeArray = (array && "length" in array) ? array : arguments;
        //如果this是nl的实例或者nodeArray是类数组对象,则进入if语句
        if(isNew || !nodeArray.sort){
            // make sure it's a real array before we pass it on to be wrapped
            //if语句的行为保证了经过该函数包装后的对象的是一个真正的数组对象。
            var target = isNew ? this : [],
                l = target.length = nodeArray.length;
            for(var i = 0; i < l; i++){
                target[i] = nodeArray[i];
            }
            if(isNew){//这时候便不再需要扩展原生array了
                return target;
            }
            nodeArray = target;
        }
        // called without new operator, use a real array and copy prototype properties,
        // this is slower and exists for back-compat. Should be removed in 2.0.
        lang._mixin(nodeArray, nlp);
        // _NodeListCtor指向一个将array包装成NodeList的函数
        nodeArray._NodeListCtor = function(array){
            // call without new operator to preserve back-compat behavior
            return nl(array);
        };
        return nodeArray;
    };

  可以看到如果isNew为false,那就对一个新的array对象进行扩展。

  扩展的能力,便是直接在NodeList.prototype上增加的方法。大家直接看源码和我的注释即可。

// add array redirectors
    forEach(["slice", "splice"], function(name){
        var f = ap[name];
        //Use a copy of the this array via this.slice() to allow .end() to work right in the splice case.
        // CANNOT apply ._stash()/end() to splice since it currently modifies
        // the existing this array -- it would break backward compatibility if we copy the array before
        // the splice so that we can use .end(). So only doing the stash option to this._wrap for slice.
        //类似于:this._wrap(this.slice(parameter), this);
        nlp[name] = function(){ return this._wrap(f.apply(this, arguments), name == "slice" ? this : null); };
    });
    // concat should be here but some browsers with native NodeList have problems with it

    // add array.js redirectors
    forEach(["indexOf", "lastIndexOf", "every", "some"], function(name){
        var f = array[name];
        //类似于:dojo.indexOf(this, parameter)
        nlp[name] = function(){ return f.apply(dojo, [this].concat(aps.call(arguments, 0))); };
    });

    lang.extend(NodeList, {//将属性扩展至原型链
        // copy the constructors
        constructor: nl,
        _NodeListCtor: nl,
        toString: function(){
            // Array.prototype.toString can't be applied to objects, so we use join
            return this.join(",");
        },
        _stash: function(parent){//保存parent,parent应当也是nl的一个实例
            // summary:
            //        private function to hold to a parent NodeList. end() to return the parent NodeList.
            //
            // example:
            //        How to make a `dojo/NodeList` method that only returns the third node in
            //        the dojo/NodeList but allows access to the original NodeList by using this._stash:
            //    |    require(["dojo/query", "dojo/_base/lang", "dojo/NodeList", "dojo/NodeList-dom"
            //    |    ], function(query, lang){
            //    |        lang.extend(NodeList, {
            //    |            third: function(){
            //    |                var newNodeList = NodeList(this[2]);
            //    |                return newNodeList._stash(this);
            //    |            }
            //    |        });
            //    |        // then see how _stash applies a sub-list, to be .end()'ed out of
            //    |        query(".foo")
            //    |            .third()
            //    |                .addClass("thirdFoo")
            //    |            .end()
            //    |            // access to the orig .foo list
            //    |            .removeClass("foo")
            //    |    });
            //
            this._parent = parent;
            return this; // dojo/NodeList
        },

        on: function(eventName, listener){//绑定事件
            // summary:
            //        Listen for events on the nodes in the NodeList. Basic usage is:
            //
            // example:
            //        |    require(["dojo/query"
            //        |    ], function(query){
            //        |        query(".my-class").on("click", listener);
            //            This supports event delegation by using selectors as the first argument with the event names as
            //            pseudo selectors. For example:
            //        |         query("#my-list").on("li:click", listener);
            //            This will listen for click events within `<li>` elements that are inside the `#my-list` element.
            //            Because on supports CSS selector syntax, we can use comma-delimited events as well:
            //        |         query("#my-list").on("li button:mouseover, li:click", listener);
            //        |    });
            var handles = this.map(function(node){
                return on(node, eventName, listener); // TODO: apply to the NodeList so the same selector engine is used for matches
            });
            handles.remove = function(){
                for(var i = 0; i < handles.length; i++){
                    handles[i].remove();
                }
            };
            return handles;
        },

        end: function(){//由当前的nl返回父nl
            // summary:
            //        Ends use of the current `NodeList` by returning the previous NodeList
            //        that generated the current NodeList.
            // description:
            //        Returns the `NodeList` that generated the current `NodeList`. If there
            //        is no parent NodeList, an empty NodeList is returned.
            // example:
            //    |    require(["dojo/query", "dojo/NodeList-dom"
            //    |    ], function(query){
            //    |        query("a")
            //    |            .filter(".disabled")
            //    |                // operate on the anchors that only have a disabled class
            //    |                .style("color", "grey")
            //    |            .end()
            //    |            // jump back to the list of anchors
            //    |            .style(...)
            //    |    });
            //
            if(this._parent){
                return this._parent;
            }else{
                //Just return empty list.
                return new this._NodeListCtor(0);
            }
        },

        concat: function(item){
            // summary:
            //        Returns a new NodeList comprised of items in this NodeList
            //        as well as items passed in as parameters
            // description:
            //        This method behaves exactly like the Array.concat method
            //        with the caveat that it returns a `NodeList` and not a
            //        raw Array. For more details, see the [Array.concat
            //        docs](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/concat)
            // item: Object?
            //        Any number of optional parameters may be passed in to be
            //        spliced into the NodeList

            //return this._wrap(apc.apply(this, arguments));
            // the line above won't work for the native NodeList, or for Dojo NodeLists either :-(

            // implementation notes:
            // Array.concat() doesn't recognize native NodeLists or Dojo NodeLists
            // as arrays, and so does not inline them into a unioned array, but
            // appends them as single entities. Both the original NodeList and the
            // items passed in as parameters must be converted to raw Arrays
            // and then the concatenation result may be re-_wrap()ed as a Dojo NodeList.

            var t = aps.call(this, 0),
                m = array.map(arguments, function(a){//array.concat方法不会将原始的NodeList和dojo的NodeList作为数组来处理,所以在这之前将他们转化成普通的数组
                    return aps.call(a, 0);
                });
            return this._wrap(apc.apply(t, m), this);    // dojo/NodeList
        },

        map: function(/*Function*/ func, /*Function?*/ obj){
            // summary:
            //        see `dojo/_base/array.map()`. The primary difference is that the acted-on
            //        array is implicitly this NodeList and the return is a
            //        NodeList (a subclass of Array)
            return this._wrap(array.map(this, func, obj), this); // dojo/NodeList
        },

        forEach: function(callback, thisObj){
            // summary:
            //        see `dojo/_base/array.forEach()`. The primary difference is that the acted-on
            //        array is implicitly this NodeList. If you want the option to break out
            //        of the forEach loop, use every() or some() instead.
            forEach(this, callback, thisObj);
            // non-standard return to allow easier chaining
            return this; // dojo/NodeList
        },
        filter: function(/*String|Function*/ filter){
            // summary:
            //        "masks" the built-in javascript filter() method (supported
            //        in Dojo via `dojo/_base/array.filter`) to support passing a simple
            //        string filter in addition to supporting filtering function
            //        objects.
            // filter:
            //        If a string, a CSS rule like ".thinger" or "div > span".
            // example:
            //        "regular" JS filter syntax as exposed in `dojo/_base/array.filter`:
            //        |    require(["dojo/query", "dojo/NodeList-dom"
            //        |    ], function(query){
            //        |        query("*").filter(function(item){
            //        |            // highlight every paragraph
            //        |            return (item.nodeName == "p");
            //        |        }).style("backgroundColor", "yellow");
            //        |    });
            // example:
            //        the same filtering using a CSS selector
            //        |    require(["dojo/query", "dojo/NodeList-dom"
            //        |    ], function(query){
            //        |        query("*").filter("p").styles("backgroundColor", "yellow");
            //        |    });

            var a = arguments, items = this, start = 0;
            if(typeof filter == "string"){ // inline'd type check
                items = query._filterResult(this, a[0]);//如果filter是css选择器,调用query的filter方法从已有集合中选择合适的元素
                if(a.length == 1){
                    // if we only got a string query, pass back the filtered results
                    return items._stash(this); // dojo/NodeList
                }
                // if we got a callback, run it over the filtered items
                start = 1;
            }
            //如果filter是函数,那就调用array的filter先过滤在包装。
            return this._wrap(array.filter(items, a[start], a[start + 1]), this);    // dojo/NodeList
        },
        instantiate: function(/*String|Object*/ declaredClass, /*Object?*/ properties){
            // summary:
            //        Create a new instance of a specified class, using the
            //        specified properties and each node in the NodeList as a
            //        srcNodeRef.
            // example:
            //        Grabs all buttons in the page and converts them to dijit/form/Button's.
            //    |    var buttons = query("button").instantiate(Button, {showLabel: true});
            //这个方法主要用于将原生dom元素实例化成dojo的dijit
            var c = lang.isFunction(declaredClass) ? declaredClass : lang.getObject(declaredClass);
            properties = properties || {};
            return this.forEach(function(node){
                new c(properties, node);
            });    // dojo/NodeList
        },
        at: function(/*===== index =====*/){
            // summary:
            //        Returns a new NodeList comprised of items in this NodeList
            //        at the given index or indices.
            //
            // index: Integer...
            //        One or more 0-based indices of items in the current
            //        NodeList. A negative index will start at the end of the
            //        list and go backwards.
            //
            // example:
            //    Shorten the list to the first, second, and third elements
            //    |    require(["dojo/query"
            //    |    ], function(query){
            //    |        query("a").at(0, 1, 2).forEach(fn);
            //    |    });
            //
            // example:
            //    Retrieve the first and last elements of a unordered list:
            //    |    require(["dojo/query"
            //    |    ], function(query){
            //    |        query("ul > li").at(0, -1).forEach(cb);
            //    |    });
            //
            // example:
            //    Do something for the first element only, but end() out back to
            //    the original list and continue chaining:
            //    |    require(["dojo/query"
            //    |    ], function(query){
            //    |        query("a").at(0).onclick(fn).end().forEach(function(n){
            //    |            console.log(n); // all anchors on the page.
            //    |    })
            //    |    });
            //与array中的位置选择器类似

            var t = new this._NodeListCtor(0);
            forEach(arguments, function(i){
                if(i < 0){ i = this.length + i; }
                if(this[i]){ t.push(this[i]); }
            }, this);
            return t._stash(this); // dojo/NodeList
        }
    });

  NodeList提供的很多操作,如:map、filter、concat等,都是借助原生的Array提供的相应方法,这些方法返回都是原生的array对象,所以需要对返回的array对象进行包装。有趣的是NodeList提供end()可以回到原始的NodeList中。整个结构如下:

  

  我们来看一下包装函数:

nl._wrap = nlp._wrap = tnl;
var tnl = function(/*Array*/ a, /*dojo/NodeList?*/ parent, /*Function?*/ NodeListCtor){
        //将a包装成NodeList
        var nodeList = new (NodeListCtor || this._NodeListCtor || nl)(a);
        //设置nodeList._parent = parent;方便在end函数中返回原始nodeList
        return parent ? nodeList._stash(parent) : nodeList;
    };
end: function(){//由最近的nl返回父nl
            if(this._parent){
                return this._parent;
            }else{
                return new this._NodeListCtor(0);
            }
        },

  这就是dojo中NodeList的设计!

 

  query模块暴露的方法无非就是对选择器引擎的调用,下面就比较简单了。

function queryForEngine(engine, NodeList){
        var query = function(/*String*/ query, /*String|DOMNode?*/ root){
            if(typeof root == "string"){
                root = dom.byId(root);
                if(!root){
                    return new NodeList([]);
                }
            }
            //使用选择器引擎来查询dom节点
            var results = typeof query == "string" ? engine(query, root) : query ? (query.end && query.on) ? query : [query] : [];
            if(results.end && results.on){//有end和on方法则认为query已经是一个NodeList对象
                // already wrapped
                return results;
            }
            return new NodeList(results);
        };
        query.matches = engine.match || function(node, selector, root){
            // summary:
            //        Test to see if a node matches a selector
            return query.filter([node], selector, root).length > 0;
        };
        // the engine provides a filtering function, use it to for matching
        query.filter = engine.filter || function(nodes, selector, root){
            // summary:
            //        Filters an array of nodes. Note that this does not guarantee to return a NodeList, just an array.
            return query(selector, root).filter(function(node){
                return array.indexOf(nodes, node) > -1;
            });
        };
        if(typeof engine != "function"){
            var search = engine.search;
            engine = function(selector, root){
                // Slick does it backwards (or everyone else does it backwards, probably the latter)
                return search(root || document, selector);
            };
        }
        return query;
    }
    var query = queryForEngine(defaultEngine, NodeList);

如果您觉得这篇文章对您有帮助,请不吝点击推荐,您的鼓励是我分享的动力!!!

时间: 2024-11-05 04:52:23

dojo/query源码解析的相关文章

dojo/aspect源码解析

dojo/aspect模块是dojo框架中对于AOP的实现.关于AOP的详细解释请读者另行查看其它资料,这里简单复习一下AOP中的基本概念: 切面(Aspect):其实就是共有功能的实现.如日志切面.权限切面.事务切面等. 通知(Advice):是切面的具体实现.以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before).后置通知(After)与环绕通知(Around). 连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点. 目标对象(Target):就是那些即将切

dojo/io-query源码解析

该模块主要对url中的query部分进行处理,我们发送GET请求时,将参数直接放在URL中,经常碰到的需求就是把一个对象转化为query字符串放到url中去发送GET请求.io-query模块便提供了两个方法来处理query: objectToQuery将一个object转化成query字符串 queryToObject将query字符串转化成对象   从对象转化成query是拼接的过程,对象的字段和值都需要编码 objectToQuery: function objectToQuery(/*O

dojo Provider(script、xhr、iframe)源码解析

总体结构 dojo/request/script.dojo/request/xhr.dojo/request/iframe这三者是dojo提供的provider.dojo将内部的所有provider构建在Deferred基础上形成异步链式模型,utils.deferred函数向3个provider提供统一接口来规范其行为.数据请求在各个provider的发送过程几乎一致: 解析options参数util.parseArgs 创建dfd对象,该对象控制着整个数据接收.处理.传递的过程  //Mak

Java集合学习(十七) TreeSet详细介绍(源码解析)和使用示例

这一章,我们对TreeSet进行学习. 我们先对TreeSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeSet. 第1部分 TreeSet介绍 TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口. TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法.

Java集合学习(十六) HashSet详细介绍(源码解析)和使用示例

这一章,我们对HashSet进行学习. 我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet. 第1部分 HashSet介绍 HashSet 简介 HashSet 是一个没有重复元素的集合. 它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素. HashSet是非同步的.如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步.这通常是通过对自然封装该 set 的对象执行同步

Java集合学习(十三) WeakHashMap详细介绍(源码解析)和使用示例

这一章,我们对WeakHashMap进行学习. 我们先对WeakHashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用WeakHashMap. 第1部分 WeakHashMap介绍 WeakHashMap简介    WeakHashMap 继承于AbstractMap,实现了Map接口.    和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null.   不过WeakHashMap的键是"弱键&

Java集合学习(十二) TreeMap详细介绍(源码解析)和使用示例

这一章,我们对TreeMap进行学习. 第1部分 TreeMap介绍 TreeMap 简介 TreeMap 是一个有序的key-value集合,它是通过红黑树实现的. TreeMap继承于AbstractMap,所以它是一个Map,即一个key-value集合. TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法.比如返回有序的key集合. TreeMap 实现了Cloneable接口,意味着它能被克隆. TreeMap 实现了java.io.Serializabl

Java集合学习(十一) Hashtable详细介绍(源码解析)和使用示例

这一章,我们对Hashtable进行学习. 我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable. 第1部分 Hashtable介绍 Hashtable 简介 和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射. Hashtable 继承于Dictionary,实现了Map.Cloneable.java.io.Serializable接口. Hashtable 的函数都是同步的,这意味着它是线

Java集合学习(十) HashMap详细介绍(源码解析)和使用示例

这一章,我们对HashMap进行学习. 我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap. 第1部分 HashMap介绍 HashMap简介 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射. HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.Serializable接口. HashMap 的实现不是同步的,这意味着它不是线程安全的.它的key.value都可以为null.此外