`

集合框架(三)-专用set和map机制分析

阅读更多
-----------------------------------------------------------------------------------------------------------------------
弱散列映射表WeakHashMap


该类的设计是为了解决在map中一个元素没有外部引用却在map的生命周期内总不被回收的bug。
public V put(K key, V value) {
        K k = (K) maskNull(key);//如果key为空,就new一个Object对象作为key
//调用HashMap的hash()方法处理经过hashcode()(自定义或者用java类库的)计算过的哈码
        int h = HashMap.hash(k.hashCode());
        Entry[] tab = getTable();//关键就在这一步,请看下面对这个方法的分析
	//可以看得出,这里也是用散列表来存储数据的,只是加进了一个回收弱引用的机制。
        int i = indexFor(h, tab.length);

        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
	//在要插入元素和i位置的元素的hash code相等情况下,还要保证原来在这个位置的元素e没有被自己写的program或者GC收掉的情况下,才用要插入的value替换原来位置的value,并返回原来位置的value
            if (h == e.hash && eq(k, e.get())) {
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }

        modCount++;
	Entry<K,V> e = tab[i];
	//我们会发现本类的内部组织数据的节点类Entry<K,V>继承了WeakReference<K>,而这个类继承了Reference<T>类。构造Entry<K,V>的时候一路super过去,最终我将key和queue变成了Reference中定义的两个属性referent(泛型T)和queue( 类型ReferenceQueue<? super T>)。而我们传进去的key是要插入的键,queue是WeakHashMap的一个属性queue(类型ReferenceQueue<? super T>)。
	一句话,我们将key和WeakHashMap中的queue属性绑定在reference类中,由ReferenceQueue管理。我们发现WeakHashMap中的Entry根本不保存key值。
	这时候,我们在回过头去看getTable()方法,当在expungeStaleEntries()方法中调用queue.poll()方法的时候我们已经用queue定义的规则来对外部对象引用不到和只有WeakReference对象获得的对象实施回收(sun.misc.VM.addFinalRefCount(-1);)。里面判断是否该回收的机制过于复杂,现在我还没看懂。
        tab[i] = new Entry<K,V>(k, value, queue, h, e);
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
	}
//正如API文档里说的,先清理staleEntries(暂时译为陈旧的条目),然后返回作为本类的属性Entry[] table
private Entry[] getTable() {
        expungeStaleEntries();
        return table;
	    }
//正如这个方法的名字,它可以删除一些stale的条目。
private void expungeStaleEntries() {
	Entry<K,V> e;
	//'queue'是存储将被清除的ReferenceQueue<K>类型的属性
        while ( (e = (Entry<K,V>) queue.poll()) != null) {
            int h = e.hash;
	//调用一个本类定义的方法获得hash code对应的index
	int i = indexFor(h, table.length);
            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    e.next = null;  // Help GC
                    e.value = null; //  "   "
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
	}


确实是一个挺有意思的类。虽然里面的机制很复杂,不过作为使用者的我们,我认为只要用的时候知道用WeakHashMap这个类创建对象可以自动回收没有任何引用的元素就行了。


-----------------------------------------------------------------------------------------------------------------------
链接散列集和链接散列映射表
包括LinkedHashMap和LinkedHashSet两个类。


同样是很有意思的一个类,它能克服一般散列表的缺点,记住我们插入元素的顺序。你可以获得key或value的符合插入顺序的迭代器。
下面,一起来看看LinkedHashMap是如何实现的:
本类就继承自HashMap<K,V>。
put()方法直接使用HashMap的,都没覆写。
不过put()方法的内部属性和方法如果LinkedHashMap有覆写,照样是用的LinkedHashMap的。
public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
	//这一步会调用LinkedHashMap中的方法,将新元素插进双向链表的末尾。
	并且判断removeEldestEntry()返回的值,为ture的话,就删除hear.after位置的额节点
        addEntry(hash, key, value, i);
        return null;
    }
get()方法字节重写了:
 public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);//调用了字节覆写的内部类Entry的方法
        return e.value;
    }
void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {//如果带顺序的方法定义为true(for access-order)
                lm.modCount++;
                remove();//在原来的链表位置移除该元素
                addBefore(lm.header);//传进去的参数是LinkedHashMap的一个属性,表示双向链表的头部,将我们调用get方法的那个元素重新插入到双向链表的尾部。
            }
        }
默认情况下,LinkedHashMap的判断是否可以按插入顺序排序的属性accessOrder为false,如果你想实现按插入迭代,用这个迭代器:
//accessOrder传入true值
public LinkedHashMap(int initialCapacity,
			 float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
	}


我们发现LinkedHashMap的属性从头到尾都没有实例化啊,到底是在哪里实例化的呢?
void init() {
        header = new Entry<K,V>(-1, null, null, null);
        header.before = header.after = header;
	}



原来自动实例化了。
如果你put一个新的元素的话,会加在链表的header.before位置(可以成为the doubly linked list的尾部)。
所以,如果你想实现一个每插进一个新的元素就删除一个(给定你自己的判断标准,对eldest元素进行评估),可以自定义removeEldestEntry()方法,让它返回true。
这个类可以应用在需要不断的从内存中读取数据,可以自己定义标准把常用的数据存在内存中,不常用的存在数据库中。嘿嘿~~

-----------------------------------------------------------------------------------------------------------------------
枚举集和枚举映射表EnumSet和EnumMap


简单说,枚举类型A就是枚举类只有有限个A的实例对象的属性。
设置枚举类getXxx()有三种方式:
在实现类中定义属性(可定义多个)
让实现类继承一个接口
在实现类中定义抽象方法
这样可以获得枚举类型A的更多绑定值。
EnumMap<K extends Enum<K>, V>:
//A是我自定义的一个枚举类型
//实际上如RED("红色");这样的书写形式,是在编译器里定义了一个类似静态工厂类的机制。如果只有"RED"就会直接将RED这个String值赋给name属性,然后通过name()方法可获取;不过如果你想定义多个属性只要在A中加入很多属性,然后写一个构造器可以传入所有的参数即可,这样你就可以通过自己的getXxx()函数来获取参数的值了。
enum A{
		RED("红色");
		private String name;
		private A(String name){
			this.name=name;
		}
//实例化EnumMap并插入一个枚举类型的为key的pair
java.util.EnumMap<A, String> em=new java.util.EnumMap<A, String>(A.class);
		em.put(A.RED, "");

public V put(K key, V value) {
        typeCheck(key);//检查key类型是否合适

        int index = ((Enum)key).ordinal();
        Object oldValue = vals[index];
        vals[index] = maskNull(value);//用新value替代原来位置的value
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);//返回原来index位置的元素
	}
看到这里,大家应该明白了,EnumMap通过ordinal()返回的值来确定插入的pair在数组vals中的位置,而vals中只存value。


EnumSet<E extends Enum<E>>:
//设置一个枚举类型到其中
public static <E extends Enum<E>> EnumSet<E> of(E e) {
	//创建一个可以接收指定类型的空集合
        EnumSet<E> result = noneOf(e.getDeclaringClass());
        result.add(e);
        return result;
	}
//在执行这一步的时候,如果universe.length <= 64就会new一个RegularEnumSet类型的对象,最终调用RegularEnumSet中覆写的add(E e)方法将e添加进去。
universe[k]位置的元素的Bit vector为2^k.如果elements的2^k位二进制的值为1,则代表通过逆方法获得的ordinal值对应的对象e在set中,否则不在。另一个类型JumboEnumSet也类似,通过一个elements[]来判断是否在set中。
 public boolean add(E e) {
        typeCheck(e);//检查类型

        long oldElements = elements;
        elements |= (1L << ((Enum)e).ordinal());
        return elements != oldElements;
    }


我们会发现所有的value都缓存在本类属性Enum[] universe中,所有的一开始就缓存在这里面了,至于如何判断是否一个e在set中,可以通过EnumSet的子类JumboEnumSet或RegularEnumSet的属性elements来判断,详细分析见上一端源码里的注释。
我们发现,枚举集和枚举映射表是通过不同的机制来存储元素的。
一切为了应用:看到这里,大家应该发现一个东东——枚举集不就是特定类型的常量集嘛!对,就是这么简单。如果我们试图获取一组特定类型的常量可以使用枚举集和枚举映射对它们进行管理。


-----------------------------------------------------------------------------------------------------------------------
弱标识散列映射表IdentityHashMap


到底IdentityHashMap和HahsMap有什么区别呢?我们一起来深入源码看看。
//插入新的pair的方法
public V put(K key, V value) {
        Object k = maskNull(key);//如果key为null做相应调整
        Object[] tab = table;//Object[]类型的属性
        int len = tab.length;
	//这一步不同于hashmap中自定义的hashcode()方法,而是调用System.identityHashCode(x)获得hashcode,这也是Object调用hashcode()方法时执行的,是根据内存地址来计算的。
        int i = hash(k, len);

        Object item;
        while ( (item = tab[i]) != null) {
            if (item == k) {//使用"=="来比较而不是equals()方法
		V oldValue = (V) tab[i + 1];
                tab[i + 1] = value;//将value放在i+1的索引位置,这里不同于HashMap的包装成Entry
                return oldValue;
            }
            i = nextKeyIndex(i, len);//如果i索引位置的key和原来的额key地址不同,就是所谓冲突了,就向后移两位(因为table中是i位置存key,i+1位置存value),最后遍历到table的末尾还是没有成功插入,就将i变为0,将pair插在0位置
        }

        modCount++;
        tab[i] = k;
        tab[i + 1] = value;
        if (++size >= threshold)
            resize(len); // len == 2 * current capacity.
        return null;
	}

源码里的注释说的很清晰了。

分享到:
评论
2 楼 aabcc 2010-11-30  
建议把 一和二的链接也附上...方便阅读

支持分享。
1 楼 181054867 2010-11-30  
上传一下源码,在这里看代码是一种折磨!

相关推荐

    day05-集合1

    Java集合框架主要由两个核心接口构成:`Collection`和`Map`。`Collection`接口是单元素集合的基类,它包括了如`List`、`Set`等子接口。`List`接口代表有序的集合,允许有重复元素;`Set`接口则代表无序且不包含重复...

    SUN JAVA 培训专用教材

    - List、Set、Queue和Map接口:了解各自的特点和常用实现类。 - 链表与数组:ArrayList和LinkedList的区别。 - HashMap和HashSet的实现原理。 5. 输入/输出流: - 文件操作:读写文件,包括字符流和字节流。 -...

    java 八股文,2024秋招专用

    2. **集合框架** - List、Set、Queue接口及其实现类:ArrayList、LinkedList、HashSet、TreeSet、LinkedList等的特性和使用场景。 - Map接口及实现类:HashMap、LinkedHashMap、TreeMap等的区别和应用场景。 - ...

    android,java,ios,php,程序员专用面试题(APK手机格式)

    - 集合框架:List、Set、Map接口及其实现类,泛型,集合操作。 - 内存管理:垃圾回收机制、内存泄漏检测。 - 多线程与并发:Thread、synchronized、volatile、Lock、并发工具类。 - 设计模式:工厂模式、单例...

    Java经典面试

    - 集合框架提供了一组统一的数据结构和算法接口,简化了数据操作。 #### 55. Java中的多线程 - 包括线程的创建、同步、通信和调度机制。 - 多线程可以提高程序的并发能力和响应速度。 #### 56. Java中的网络编程 -...

    Java 毕业设计专用 毕业论文

    此外,异常处理、集合框架(如List、Set、Map)和IO流也是Java编程中的核心知识点。 2. **Java设计模式**:设计模式是解决软件设计中常见问题的经验总结,例如单例模式、工厂模式、观察者模式、装饰器模式等。在...

    java面试题_(精典).doc

    `List`、`Set`和`Map`不直接继承`Collection`,但它们都是集合框架的一部分,其中`List`和`Set`间接实现`Collection`。 #### 54. 面向对象的特征 - 封装:隐藏实现细节。 - 继承:类间共享属性和行为。 - 多态:...

    各大企业javac++笔试面试题目(程序员专用)

    2. **集合框架**:深入理解ArrayList、LinkedList、HashSet、HashMap等容器的实现原理和使用场景。 3. **IO流**:了解输入输出流的不同分类,如字节流与字符流,以及缓冲流、转换流和对象流的使用。 4. **多线程**...

    小周专用下载

    集合框架的学习笔记,则可能涉及到了Java中用于存储和操作数据的多种数据结构,如List、Set、Map等。而深入到JVM的学习笔记,更是展示了小周对Java运行机制的深刻理解,这对于性能优化和问题排查至关重要。 从文件...

    JAVA编程+JAVASE各种方法总结思路+菜鸟入门,实习生码农专用+JAVASE.rar

    4. **集合框架**:熟悉ArrayList、LinkedList、HashSet、HashMap等集合类的使用,理解它们的特点和应用场景,以及List、Set、Map接口的概念。 5. **IO流**:掌握输入输出流的使用,包括文件操作、字节流、字符流、...

    java面试题总结

    - **概述**:Java集合框架提供了一组通用的接口和实现类,用来存储和操作一组对象。 - **接口**:`Collection`, `Set`, `List`, `Queue`, `Deque`, `Map` - **实现类**:`ArrayList`, `LinkedList`, `HashSet`, `...

    java常见面试题,面试专用笔记,非常全面

    熟练掌握List、Set、Map接口的实现类,如ArrayList、LinkedList、HashSet、HashMap等,以及它们的特性和使用场景。 12. I/O流: 理解输入输出流的基本概念,包括字节流和字符流,以及缓冲流、转换流和对象流的使用...

    计算机&软件工程&人工智能研究生复试资料整理

    - **Java集合类框架的基本接口**:包括`Collection`、`Set`、`List`、`Map`等。 - **List、Set、Map的区别**:`List`有序且允许重复;`Set`不允许重复元素;`Map`保存键值对。 - **迭代器**:遍历集合的工具。 -...

    Java企业面试题整理及答案

    如果要在集合框架中实现比较功能,需要实现 `Comparable` 接口或使用 `Comparator` 接口。前者适用于自身类型间的比较,后者适用于不同类型的比较。 **15. 用插入法进行排序代码如下** 插入排序是一种简单的排序...

    Java 语言基础学习

    4. **集合框架**:Java 集合框架包括 List、Set、Map 等接口及其实现类,如 ArrayList、LinkedList、HashSet、HashMap 等,它们提供了存储和操作对象的高效方式。 5. **输入/输出流**:Java 的 I/O 流系统强大且...

    2021-2022计算机二级等级考试试题及答案No.9252.docx

    - **知识点**: Java集合框架中的`List`、`Set`和`Map`接口及其主要实现类的区别。 - **解释**: - `List`: 有序且允许重复元素,主要实现类包括`ArrayList`(基于动态数组实现)和`LinkedList`(基于链表实现)。 -...

    Hibernate3.2官方中文参考手册+英文手册+API文档

    - **关联映射**:包括一对一、一对多、多对一和多对多关系的映射,如集合映射(List、Set、Map等)。 掌握以上知识点,将使你在使用Hibernate3.2进行Java开发时更加得心应手。无论是初学者还是经验丰富的开发者,这...

    面试试题大全

    1. List、Set和Map接口:理解它们的特点和常用的实现类,如ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等。 2. 泛型:泛型用于限制集合元素的类型,提高代码安全性和可读性。了解通配符、类型擦除...

    java面试题合集.zip

    2. **集合框架**:Java集合框架是面试中的重头戏,包括List、Set、Map接口以及其实现类如ArrayList、LinkedList、HashSet、HashMap等。理解它们的区别、性能特点以及操作方法是必不可少的。 3. **多线程**:Java对...

Global site tag (gtag.js) - Google Analytics