`

源码阅读之Map和Set

阅读更多
HashSet是Set接口的实现,Set和List最明显的区别在于Set不允许重复元素,而List允许。Set为了做到不允许重复元素,采用的是基于HashMap来实现的
HashSet();
创建HashMap对象。
add(e);
调用HashMap的put(k,v);方法,将需要增加的元素作为map的key,而value则传入一个已有的Object常量。
remove(e);
调用HashMap的remove方法
contains(e);
调用HashMap的containsKey(k)方法。
iterator();
调用HashMap的keySet方法来返回iterator,HashSet不能使用get(i),来获取元素,只能通过iterator方式获得。

TreeSet和HashSet的主要区别在于,TreeSet对于排序的,TreeSet是基于TreeMap实现的。
TreeSet();
创建TreeMap对象。
add(e);
调用TreeMap的put(k,v);方法,将需要增加的元素作为map的key,而value则传入一个已有的Object常量。
remove(e);
调用TreehMap的remove方法
contains(e);
调用TreeMap的containsKey(k)方法。
iterator();
调用TreeMap的keySet方法来返回iterator,HashSet不能使用get(i),来获取元素,只能通过iterator方式获得。
可见,TreeSet和HashSet没有其他的区别。TreeSet在构建的时候,可以传入comparator接口的实现,descendingSet以及descendingIterator等类来自定义排序。
总结:
HashSet是基于HashMap实现,无限制容量
TreeSet是基于TreeMap实现,支持排序
HashSet,TreeSet都是非线程安全的





HashMap是Map中最常用的,具体实现方式如下。
HashMap();
将loadfactor设置成默认的0.75,threshold为12,并创建一个大小为16的Entry数组。可以通过HashMap的另外2个构造方法来控制初始化容量和loadfactor,至于创建的Entry数组的大小并非是初始化容量决定的,如下。
  // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

capacity才是真正的Entry数组的大小,即真实的Entry数组的大小应为大于initialCapacity的2的倍数。例如,调用new HashMap(5,0.6),那么按照HashMap的实现,则会将loadFactor的值设置为0.6,并且创建一个大小8的Entry数组threshold为4。

put(Object key,Object value);
对于key为null的情况下,HashMap的做法是获取Entry数组的第一个对象,并基于Entry对象的next属性进行遍历,当找到其中的Entry对象的key为null时,则将其的value赋值成新的value,然后返回,如果没有Entry对象的key为null时,则增加一个Entry对象,增加时,先获取当前Entry数组的第一个Entry对象e,并创建Entry对象,key为null,value为新传入的对象,next为之前获得的e.如果此时的Entry数组已用的大小>=threshold,则将当前的数组,扩大为
当前大小的2倍,扩大时,为当前Entry的对象重新hash,并填充数组,重新设置threshold值。
对于Key不为null的情况下,首先获取当前对象的hashcode,然后在对hashcode进行hash操作,其代码为:
 static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

hash完毕后,将hash出来的值与当前Entry对象数组的大小减一的值进行按位与操作,从而得出当前key要存到数组的位置,从这个过程可以看出,可能会出现不用的key找到相同存储位置的问题,也就是数据结构中经典的hash冲突的问题。HashMap是这样解决的。
在找到要存储的目标数组的位置后,获取该数组的对应的Entry对象,通过Entry对象的next方法来遍历,寻找hash和计算出的hash值相等的,并且key相等或者equals的Entry对象。如果找到了,则替换该Entry的对象的值,完成put操作,返回oldValye。如果未找到,则往对应的数组对象添加新的Entry对象,增加时采取的方法和key为null的基本相同,只是它是替换指定位置的Entry对象,可以看出,hashMap解决hash冲突采取的是链表方式,而不是开放定址的方法。
get(Object key)
get的过程和put的一样,也是根据key是否为null来分别处理,对于key为null的情况,则直接获取数组的第一个Entry对象,并且基于next属性进行遍历,寻找key为null的Entry对象,如找到返回Entry对象的value,如未找到,返回null。
对于key不为null的情况,则对key进行hash按位于操作,找到其对象的存储位置,然后获取此位置的Entry对象,基于next属性进行遍历,寻找到hash值相等,并且key相等或者equals的Entry对象,返回其value,如未找到返回null。
remove(Object key)
remove的过程和get相似,只要在找到匹配的key后,如数组上的元素等于key,则将数组上元素的值改为其next元素的值;如数据上的元素不等于key,则对链表进行遍历,一直到找到匹配的key或链表为止。
containsKey(Object key)
通过调用getEntry方法来完成,getEntry方法和get方法相同,只是在找到匹配的key后,直接返回Entry对象,而containsKey判断返回的Entry对象是否为null,为null则返回false,反之返回true;
keySet();
在使用map时,经常会通过keySet来遍历hashMap,调用keySet方法会返回HashMap中定义的ketSet实例,此keySet继承了abstractSet。当调用iterator时,返回setIterator实例,调用next方法时,遍历整个数组以及Entry对象的链表。如果在遍历的过程中有添加和删除呀un苏会抛出,concurrentModificationException.
HashMap无法保证顺序,要保证顺序要使用TreeMap.
TreeMap是一个基于Map排序方式的实现,其实现方法和HashMap不同。
TreeMap()
此处的comparator的属性赋值为null,如希望控制TreeMap元素的存储顺序,可以使用带Comparator参数的构造器。
put(Object key,Object value)
当调用put方法时,首先判断root属性是否为null(root为TreeMap中维护的数组),如为空,则创建一个新的Entry对象,并且赋值给root属性,如果不为空,则首先判断是否传入了指定的Comparator实现,如已传入,则基于红黑树的方式遍历,基于comparator来判断key是应该放在树的左边还是右边,如找到相等的key,则直接替换其value,并返回结束操作,如没找到相等的key,则一直寻找从左边或者右边为null的元素。如Comparator实现为空,则判断key是否为null,是则抛出NullPoingException,并将key造型为Comparable,进行与上面同样的遍历和比较过程。通过以上步骤,如果未找到相同的key,则进入以下过程,创建一个新的Entry对象,并将其的parent设置成上面找到的元素,并根据和parent key比较的情况来设置parent的左右的属性。TreeMap是一个典型的基于红黑树的实现,因此他要求一定要有key的比较方法。要么传入comparator,要么key对象实现Comparable接口。
get(Object key)
treeMap在查找key的时就是一个典型的红黑树查找过程。从根对象开始比较,一直找到相等的key,并返回value.
和put时同样的处理方式,如未传入Comparator接口,当传入的Object为null时,在直接抛出NullPointException.
remove(Object key)
remove首先要做的是getEntry,然后则是将此Entry在红黑树上删除,并重新调整树上的相关的节点。
ContainsKey(object key)
和get一样,都通过getEntry方法,因此过程和get一样,只是containskey直接判断返回的Entry是否为null,为null则返回false,反之返回true.
keySet();
调用keySet方法后,返回TreeMap的内部类keySet对象的实例,iterator的遍历从根开始,基于红黑树的方式完成。
总结:
HashMap采取数组方式存取key,value构成Entry链表对象,无容量限制
HashMap基于key hash寻找Entry对象存到数组的位置,对于hash冲突采取链表方式来解决。
HashMap在插入数组时,可能要扩充容量,在扩大容量的时候必须要重新计算hash,并复制对象到新的数组中。
TreeMap基于红黑树实现,无容量限制。
HashMap,TreeMap是非线程安全的。
注意事项:
1. HashSet底层是使用HashMap实现的。当使用add方法将对象添加到Set当中时,实际上是将该对象作为底层所维护的Map对象的key,而value则都是同一个Object对象(该对象我们用不上);
2. HashMap底层维护一个数组,我们向HashMap中所放置的对象实际上是存储在该数组当中;
3. 当向HashMap中put一对键值时,它会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
4. 如果该位置没有对象存在,就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(Entry类有一个Entry类型的next成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用equals方法进行比较,如果对此链上的某个对象的equals方法比较为false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象链接到此对象的后面。
5.
package com.collections;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class SetText {
	public static void main(String[] args) {
		Set set=new HashSet();
		System.out.println("Set第一次:"+set.add("123"));
		System.out.println("Set第二次:"+set.add("123"));
		System.out.println("=================");
		Map map=new HashMap();
		System.out.println("Map第一次:"+map.put("1", "1"));
		System.out.println("Map第二次:"+map.put("1", "1"));
	}
}

执行结果为:
Set第一次:true
Set第二次:false
=================
Map第一次:null
Map第二次:1
说明,如果把已有对象分别往Set和Map中放时,Set是不会继续放入,保留原有值,Map是覆盖原有值。
分享到:
评论
1 楼 xiaguangme 2012-12-28  
“capacity才是真正的Entry数组的大小,即真实的Entry数组的大小应为大于initialCapacity的2的倍数。例如,调用new HashMap(5,0.6),那么按照HashMap的实现,则会将loadFactor的值设置为0.6,并且创建一个大小8的Entry数组threshold为4。 ”
创建的Entry的大小为10 吧

相关推荐

    Map_Set.zip_C Map_C语言map_map.c

    在"Map_Set"这个压缩包中,"map_map.c"文件很可能是实现了上述功能的源代码。通过阅读和学习这段代码,你可以了解到如何在C语言中使用红黑树来构建高效的数据结构,并且可以将其应用于自己的项目中,实现类似C++中的...

    红黑树封装map&&set源代码

    封装红黑树的Map和Set源代码通常会包括以下关键部分: 1. **节点定义**:首先,需要定义红黑树的节点结构,包括键、值、颜色属性、左子节点和右子节点。例如: ```java class Node { Key key; Value value; ...

    java的序列 map list set sequene

    在Java编程语言中,Map、List和Set是三个核心的集合接口,它们分别代表了键值对、有序元素列表和不重复元素集合。这三种数据结构在实际开发中有着广泛的应用,理解它们的特性和使用方式是每个Java开发者的基础技能。...

    PropertySet的map/xml/jdbc

    标题中的"PropertySet的map/xml/jdbc"暗示了我们将探讨一个与Java编程相关的主题,它涉及到数据存储、序列化和数据库交互。PropertySet通常是指Java中的一个概念,它可能指的是属性集或者配置设置,而map、xml和jdbc...

    collection,list,set,map

    不过,从描述和标签中的“源码 工具”可以推测,作者可能在讨论Java中的集合框架,包括Collection接口、List、Set以及Map接口,这是Java开发中经常讨论的集合类型。 在Java编程中,Collection、List、Set和Map是...

    map set stack

    在IT行业中,`Map`、`Set`和`Stack`是Java编程语言中非常基础且重要的数据结构。它们属于Java集合框架的一部分,为开发者提供了高效的数据存储和操作能力。接下来,我们将深入探讨这三个核心概念以及它们在实际开发...

    Collection List Set Map 区别记忆

    在Java编程语言中,集合框架是数据操作的核心部分,它提供了对对象的高效存储和管理。其中,`Collection`、`List`、`Set`和`Map`是...通过阅读和实践,开发者可以更好地理解和利用这些接口,提高代码的可读性和效率。

    【STL源代码】C++标准库STL源代码下载

    阅读STL源代码可以帮助我们理解C++容器和算法的底层实现,从而优化自己的代码,提高程序性能。同时,也能让我们更好地掌握C++模板机制,以及如何利用泛型编程来编写高效且可复用的代码。通过分析源码,我们可以学习...

    linux源代码阅读工具vim+ctag+cscope

    Vim、ctags和cscope是Linux开发人员常用的代码阅读工具,它们能够帮助开发者更高效地理解和导航源代码。以下是对这些工具的详细介绍: 1. **Vim**:Vim是一款高度可配置的文本编辑器,它具有强大的文本操作能力和...

    JavaScript实现Array(数组)和Map

    这些功能可以通过阅读源码来了解其具体的实现方式和用途。 接着,Map是ES6中新增的一种数据结构,它解决了数组不能以任意对象作为键的问题。在Map中,任何值(对象或者原始类型)都可以作为键或值。与数组不同,Map...

    自己对List,Set,Map等集合类的理解

    在Java编程语言中,集合框架是处理对象组的重要工具,其中List、Set和Map是最基本的接口,分别代表了有序的列表、无序的集合和键值对的映射关系。下面将详细解释这些集合类及其特点。 1. **List接口**: List是一...

    Java 推荐读物与源代码阅读

    ### Java 推荐读物与源代码阅读 #### 一、Java语言基础学习书籍推荐 ...总之,通过系统地学习Java的基础知识、数据结构和源代码阅读技巧,结合实践中的不断探索,你将能够更深入地理解和掌握Java的核心概念和技术。

    C标准库源代码(学习C/C++必备)

    C标准库源代码\MAP C标准库源代码\MATH.H C标准库源代码\MBBTYPE.C C标准库源代码\MBCCPY.C C标准库源代码\MBCLEN.C C标准库源代码\MBCLEVEL.C C标准库源代码\MBCTYPE.C C标准库源代码\MBCTYPE.H C标准库源代码\...

    stl源码剖析源代码-免费

    通过阅读这些源代码,开发者可以深入理解STL内部的实现细节,比如迭代器的工作方式、容器的内存管理策略以及算法的效率优化。这对于提升C++编程技巧,特别是进行高性能、低级别的内存管理和优化时,是极其宝贵的资源...

    java源代码,java源代码

    Java源代码是编程世界的基石,它是Java程序员用Java语言编写的程序文本,包含了...对于压缩包中的"java源码",可能是某个具体项目或库的源代码,通过阅读和学习,我们可以深入了解其设计思路和实现方式,提升编程技能。

    map的js实现

    总之,`Map`的JS实现涉及数据结构设计、遍历机制和方法实现等多个方面。通过分析`map.js`文件,我们可以学习如何创建自定义数据结构以模拟JavaScript内置的`Map`行为,并了解其在实际应用中的优缺点。同时,结合测试...

    Dart 集合类型List Set Map详解 以及循环语句 forEach map where any every.zip

    本教程将深入探讨三种主要的集合类型:List、Set和Map,以及几种常用的循环语句,如forEach、map、where、any和every。这些概念对于理解Dart中的数据处理至关重要。 1. **List**: List是有序的元素集合,可以包含...

    js实现map用法

    JavaScript中的Map对象是ES6引入的一种新的数据结构,它提供了...通过熟练掌握Map的用法,开发者可以更高效地组织和操作数据,提高代码的可读性和维护性。对于深入学习JavaScript,理解并熟练运用Map是非常关键的一步。

    ES6学习笔记之Set和Map数据结构详解

    在ES6中,Set和Map是两种新的数据结构,它们为JavaScript编程提供了更加强大的工具。Set主要用于存储唯一值,而Map则用于存储键值对。 Set数据结构: 1. **Set的创建与初始化**:Set是一个构造函数,用于创建Set...

    C语言版的STL,包含set,list,map等基本数据结构和算法.zip

    这个压缩包中的"CSTL-master"可能是项目源代码的根目录,里面可能包含了源代码文件、头文件、编译脚本、测试案例、使用文档等资源。如果你正在寻找在C语言环境中使用类似C++ STL的功能,这个库可能是一个有价值的...

Global site tag (gtag.js) - Google Analytics