`

集合类源码

    博客分类:
  • j2se
阅读更多

主要包括Java类库中提供的几个具体的类: 
LinkedList 
ArrayList 
HashMap 
HashSet 
TreeMap 
TreeSet 
PriorityQueue(顺序按下面的讲解顺序) 


----------------------------------------------------------------------------------------------------- 
1、java.util.LinkedList<E> 
当我们创建一个LinkedList类的对象,并且试图增加一个新的元素的时候,到底是如何组织我们传进去的数据的呢? 

Java代码  收藏代码
  1. //创建一个LinkedList类型的对象  
  2. java.util.LinkedList<String> l=new java.util.LinkedList<String>();  
  3. l.add(e);//e为E类的对象  


打开add方法的源码看看: 
Java代码  收藏代码
  1. public boolean add(E e) {  
  2. //调用LinkedList的私有方法  
  3. //header是LinkedList中的一个属性,这样定义的private transient Entry<E> //header = new Entry<E>(null, null, null);  
  4.   
  5.     addBefore(e, header);          
  6. return true;  
  7.     }  
  8. //被调用的私有方法  
  9. private Entry<E> addBefore(E e, Entry<E> entry) {  
  10.     Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);  
  11.     newEntry.previous.next = newEntry;  
  12.     newEntry.next.previous = newEntry;  
  13.     size++;  
  14.     modCount++;  
  15.     return newEntry;  
  16.     }  
  17. //Entry<E>是LinkedList的内部类,包装每一个E类型的对象e,形成一个链表  
  18. private static class Entry<E> {  
  19.     E element;  
  20.     Entry<E> next;  
  21.     Entry<E> previous;  
  22.   
  23.     Entry(E element, Entry<E> next, Entry<E> previous) {  
  24.         this.element = element;  
  25.         this.next = next;  
  26.         this.previous = previous;  
  27.     }  
  28.     }  


我们惊喜的发现,原来就是把我们传去的e对象包装成了Entry<E>,然后通过Entry<E>的next和previous两个属性形成了一个以包装后的e对象(即Entry<E>)为节点的双向链表。 
于是我们彻底明白了LinkedList果然名副其实,就是一个链表嘛! 



----------------------------------------------------------------------------------------------------- 
2、java.util.ArrayList<E> 


我们看看在ArrayList对象调用add();方法时,底层到底是如何执行的 
Java代码  收藏代码
  1. public boolean add(E e) {  
  2.     ensureCapacity(size + 1);  // size是ArrayList中元素的个数  
  3.     elementData[size++] = e;   //在调整后的elementData末尾加入新的元素  
  4.     return true;  
  5.     }  
  6.  public void ensureCapacity(int minCapacity) {  
  7.     modCount++;  
  8.     //elementData就是ArrayList中一个数组类型的属性,用来放进去的元素:    //Object[] elementData  
  9.     int oldCapacity = elementData.length;  
  10.     if (minCapacity > oldCapacity) {//原来的elementData空间不够用了!  
  11.         Object oldData[] = elementData;  
  12.         int newCapacity = (oldCapacity * 3)/2 + 1;  
  13.        //如果通过oldCapacity 计算出的新空间依然不够用  
  14.     if (newCapacity < minCapacity)         
  15.     newCapacity = minCapacity;  
  16.             // minCapacity is usually close to size, so this is a win:  
  17.     //这一步最后会调用System.arraycopy(original, 0, copy, 0,  
  18.                              Math.min(original.length, newLength));  
  19.     //来实现将所有的元素copy到长度更大的数组中,这一步将很费时间  
  20.          elementData = Arrays.copyOf(elementData, newCapacity);  
  21.     }  
  22.     }  


于是我们发现:原来ArrayList也是如名字说的,用Array组织数据。不过它内部定义的那个调整elementData数组的方法copy太多,显然当数据量大的时候,性能不会很好。 



----------------------------------------------------------------------------------------------------- 
3、java.util.HashMap<K,V> 


Java代码  收藏代码
  1. //向HashMap中插入键值对  
  2.      public V put(K key, V value) {  
  3.              if (key == null)   //如果没有输入的key是null值  
  4.                  return putForNullKey(value);//插在Entry[0]的第一个,返回null  
  5.         //获得哈希码  
  6.         //1、首先用key类定义的hashcode()方法计算得到一个int  
  7.         //2、进行一些>>>和^的操作  
  8.              int hash = hash(key.hashCode());  
  9.         //通过&运算将hash按二进制位取反(1变为0,0变为1)  
  10.         //得到要插入的元素在table中的index  
  11.              int i = indexFor(hash, table.length);  
  12.           
  13.         //遍历table[i]数据元下拖带的一个链表的所有元素  
  14.              for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  15.                  Object k;  
  16.             //如果有一个已经存在的元素的哈希码"=="为true,  
  17.             //并且key值"=="或者"equals"为true  
  18.             //也就是所谓的key经过hashcode()的一系列运算和  
  19.            //equals()的一系列运算相同的元素,就替换原来的value  
  20.                  if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  21.                      V oldValue = e.value;  
  22.                      e.value = value;  
  23.                      e.recordAccess(this);  
  24.                      return oldValue;  
  25.                  }  
  26.              }  
  27.   
  28.              modCount++;  
  29.         //把原来在table[i]位置的元素挤到Entry<K,V>的next位置  
  30.              addEntry(hash, key, value, i);  
  31.              return null;  
  32.          }  
  33.   }  


想必大家看这段代码都看到晕了吧,为了让大家能够更加形象的人知道HashMap对数据的的组织形式,上了一个HaspMap数据结构图: 

 

这里解释一下,这个图的最左边的一些就是上面源码中的table也就是HashMap的一个属性Entry[] table。将一个新的键值对插入需要经过这几步: 
---给key值计算哈码(计算在这一步int hash = hash(key.hashCode());), 
    ---得出在table数组的中index:int i = indexFor(hash, table. 
length); 
---将键值对插入index确定的上图所示的一个横向的链表中。如果在这个链表中有要插入的pair的key经过hashcode()的一系列运算和equals()的一系列运算相同的元素,就替换原来的value。(这也就是我们自己定义的类要用到HashMap存储的时候,必须重写hashcode()和equals()方法,并且要保证对同一对象两个方法计算结果要相同的原因。因为如果不相同,在一个同一对象为key插入值的时候就不会像你期望的那样后插入的value覆盖前面的value了,而是会重新开辟一个空间存储) 

于是,到这里我们明白了,原来HashMap就是通过散列表这种数据结构组织数据的! 


----------------------------------------------------------------------------------------------------- 
4、java.util.HashSet<E> 


Java代码  收藏代码
  1. public boolean add(E e) {  
  2.     //map是该类的一个属性,这样定义的:HashMap<E,Object> map  
  3.     //这里e作为key了  
  4.     //value用本类的属性代替private static final Object PRESENT = new Object();每个键值对都相同  
  5.     return map.put(e, PRESENT)==null;  
  6.     }  


小样直接自己不解决,抛给HashMap类的put()方法,也就是用一个散列表存数据。详解见第三条对HashMap的讲解 


----------------------------------------------------------------------------------------------------- 
5、java.util.TreeMap<E> 


Java代码  收藏代码
  1. public V put(K key, V value) {  
  2.         Entry<K,V> t = root;//root是整棵树的根节点  
  3.         if (t == null) {  
  4.         //插入的第一个元素会成为根节点  
  5.             root = new Entry<K,V>(key, value, null);  
  6.             size = 1;  
  7.             modCount++;  
  8.             return null;  
  9.         }  
  10.         int cmp;  
  11.         Entry<K,V> parent;  
  12.         // 调用Comparator的compare()方法确定新加的元素出现的位置。  
  13.     //我们可以再自己定义的类中实现Comparator接口,然后传给树集的构造器。从而按照自己定义的不同的比较规则来给整个树的数据进行排序。  
  14.         Comparator<? super K> cpr = comparator;  
  15.         if (cpr != null) {  
  16.             do {  
  17.                 parent = t;  
  18.                 cmp = cpr.compare(key, t.key);  
  19.                 if (cmp < 0)  
  20.                     t = t.left;  
  21.                 else if (cmp > 0)  
  22.                     t = t.right;  
  23.                 else  
  24.                     return t.setValue(value);  
  25.             } while (t != null);  
  26.         }  
  27.         else {  
  28.             if (key == null)  
  29.                 throw new NullPointerException();  
  30.             Comparable<? super K> k = (Comparable<? super K>) key;  
  31.             do {  
  32.                 parent = t;  
  33.                 cmp = k.compareTo(t.key);  
  34.                 if (cmp < 0)  
  35.                     t = t.left;  
  36.                 else if (cmp > 0)  
  37.                     t = t.right;  
  38.                 else  
  39.                     return t.setValue(value);  
  40.             } while (t != null);  
  41.         }  
  42.     //这里我们将传进来的数据包装成Entry<K,V> ,通过Entry<K,V> 内部类的//属性 Entry<K,V> parent来组织一棵树  
  43.         Entry<K,V> e = new Entry<K,V>(key, value, parent);  
  44.         if (cmp < 0)  
  45.             parent.left = e;  
  46.         else  
  47.             parent.right = e;  
  48.         fixAfterInsertion(e);  
  49.         size++;  
  50.         modCount++;  
  51.         return null;  
  52.     }  


我们又可以开心的大笑了,原来就是如此简单,就是按照一定的规律形成一棵二叉树来存数据。 
大笑过后,我们再次静下心来观察,源码中出现了这样一句:k.compareTo(t.key);是说用key对应的类中实现的compareTo()方法来判断两个key的先后顺序。有若干标准的java平台类都实现了Compatable接口(Compatator可以自己定义不同的比较规则,不过这个接口的比较规则只有一个,是定义key的类的时候定义的,没有可变性),如String类: 

Java代码  收藏代码
  1. //用字典式排序。不展开分析了。  
  2.  public int compareTo(String anotherString) {  
  3.     int len1 = count;  
  4.     int len2 = anotherString.count;  
  5.     int n = Math.min(len1, len2);  
  6.     char v1[] = value;  
  7.     char v2[] = anotherString.value;  
  8.     int i = offset;  
  9.     int j = anotherString.offset;  
  10.   
  11.     if (i == j) {  
  12.         int k = i;  
  13.         int lim = n + i;  
  14.         while (k < lim) {  
  15.         char c1 = v1[k];  
  16.         char c2 = v2[k];  
  17.         if (c1 != c2) {  
  18.             return c1 - c2;  
  19.         }  
  20.         k++;  
  21.         }  
  22.     } else {  
  23.         while (n-- != 0) {  
  24.         char c1 = v1[i++];  
  25.         char c2 = v2[j++];  
  26.         if (c1 != c2) {  
  27.             return c1 - c2;  
  28.         }  
  29.         }  
  30.     }  
  31.     return len1 - len2;  
  32.         }  

所以,我们自己定义key的类的时候,要特别注意compareTo()方法中算法的选择,以便有一个最好的插入、查找、遍历的性能。一般而言将元素添加到树集的速度快于数组和链表,慢于散列表(素服比较:数组、链表<树集<散列表)。 



----------------------------------------------------------------------------------------------------- 
6、java.util.TreeSet<E> 


Java代码  收藏代码
  1. public boolean add(E e) {  
  2.     return m.put(e, PRESENT)==null;  
  3.     }  

相信大家看到源码立马就能明白了吧,向HashSet一样TreeSet也偷懒了(至于为什么要偷懒,感兴趣的朋友可以去研究,这里不展开了),也是用二叉树的结构存数据,不多说! 



----------------------------------------------------------------------------------------------------- 
7、java.util.PriorityQueue<E> 
PriorityQueue<E>重新写了一份: 


我们看看调用add()方法在底层到底发生了什么事情! 
Java代码  收藏代码
  1. public boolean add(E e) {  
  2.         return offer(e);  
  3.     }  
  4.   
  5. public boolean offer(E e) {  
  6. //前面这的几行无非就是判断非空,判断本类的属性queue的长度是否够用然后做相应调整  
  7.         if (e == null)  
  8.             throw new NullPointerException();  
  9.         modCount++;  
  10.         int i = size;  
  11.         if (i >= queue.length)  
  12.             grow(i + 1);  
  13.         size = i + 1;  
  14. //最后终于要将元素插进去了  
  15. //如果queue空就插在index为0的位置,很好理解  
  16. //否则调用siftUp()方法(第一个参数是the position to fill,第二个参数是the element to insert)  
  17.         if (i == 0)  
  18.             queue[0] = e;  
  19.         else  
  20.             siftUp(i, e);  
  21.         return true;  
  22.     }  
  23. //再来看看siftUp()方法是如何实现的  
  24. //api文档的注释的意思是:将x插入合适的位置保持heap的有序性不变  
  25. //排序标准有两种途径获取:  
  26. //1、在构造PriorityQueue的时候传入的Comparator ,这个优先选用  
  27. //2、 要插入的x自己实现的compareTo方法  
  28. private void siftDown(int k, E x) {  
  29.         if (comparator != null)  
  30.             siftDownUsingComparator(k, x);  
  31.         else  
  32.             siftDownComparable(k, x);  
  33.     }  
  34.   
  35. //这里我只需分析comparator的情况就可以了  
  36. private void siftUpUsingComparator(int k, E x) {  
  37. //最坏的情况是:我找了一圈发现x才是整棵树种最小的。这时k为0,也就是到达整个堆的最小的元素(或者整棵树的根节点),停止循环。          
  38. while (k > 0) {  
  39.     //第一句的意思是获得要插入的这个k位置在queue中对应的父元素的索引  
  40.     //我可以告诉大家这个式子的计算结果是:queue[n]节点的子节点是queue[2*n+1]和queue[2*(n+1)]  
  41.             int parent = (k - 1) >>> 1;  
  42.             Object e = queue[parent];  
  43.     //如果比较规则确定x"大于"父节点,就插在k位置了,跳出循环  
  44.             if (comparator.compare(x, (E) e) >= 0)  
  45.                 break;  
  46.     //如果发现x较小,就将父节点的元素移到这个k位置  
  47.             queue[k] = e;  
  48.             k = parent;//现在要插入的位置变为原来父节点的位置  
  49.         }  
  50.         queue[k] = x;//  
  51.     }  

嗯,这个类用了一种“堆”(逻辑上是二叉树,存储上用数组,树中的元素有大小关系,越小在数组中的index也越小)的数据结构。 
典型应用是存储有优先级的任务,因为每次调用remove移除最小的元素(优先级最高的元素),都会自动排序,保证每次移除的都是优先级最高的任务。 
同样,TreeMap逻辑上也是通过有序二叉树来组织数据的,不过,TreeMap通过节点的链接来组织存储结构,而PriorityQueue是通过数组的一些列计算确定逻辑上的树的节点的存放位置。 

分享到:
评论

相关推荐

    Java集合类源码(摘自jr源码)

    在给定的压缩包文件中,包含了一些关键的集合类源码,如`TreeMap`、`Hashtable`、`ArrayList`、`HashMap`、`LinkedList`、`List`、`Map`、`TreeSet`、`LinkedHashMap`和`Set`。这些类都是Java集合框架的重要组成部分...

    Trove 集合类源码

    当你需要基本数据类型的集合时,你需要自定义集合类,或使用第三方库,如 Trove 。出于性能考虑,使用TIntObjectHashMap,效率会高于直接使用...该资源为源码包,可以自己打为JAR包使用,也可以参考实现自定义的集合类。

    JAVA Map集合类源码说明

    java.util 中的集合类包含 Java 中某些最常用的类。 最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象元素列表。 List 适用...

    java集合类演示源码

    集合类的框架为集合的实现者提供了大量的接口和抽象类,并对其中的某些机制给予了描述,例如,Iterator(迭代协议)。实现Comparable接口或Comparator接口,用户可以根据需要对集合中的元素进行排序。为了方便用户...

    java集合类源码分析之Set详解.docx

    Set接口在Java集合框架中扮演着重要角色,它是一个不包含重复元素的集合。Set接口继承自Collection接口,提供...了解和掌握这两种集合类的源码分析有助于深入理解Java集合框架的底层实现,从而更好地应用在实际开发中。

    java集合类源码分析之Set详解

    Java集合类源码分析之Set详解 Java集合类中的Set Interface是用于存储无序、不可重复元素的集合接口。Set Interface继承自Collection Interface,提供了基本的集合操作,如add、remove、contains等。Set Interface...

    java集合源码-jdk-collection:java集合类源码阅读

    集合源码 JDK-Collection集合入门 总的list和set类结构大致如下所示 Map不继承Collection,其结构如下 首先介绍下迭代器的概念 迭代器无非是一个接口,假设我们有一个数组,如果我们要实现迭代器,只需要根据该接口定义...

    集合框架源码分析

    所有集合类都提供了迭代器接口,用于遍历集合中的元素。迭代器通过`hasNext()`检查是否有下一个元素,`next()`获取并移除下一个元素。`remove()`方法可以删除当前元素,这是安全的,因为其他线程无法同时修改迭代器...

    集合类底层源码解析汇总

    java所有集合类底层源码解析汇总,包括ArrayList、HashMap、HashSet、LinkedList、TreeMap、HashSet、ConcurrentHashMap等集合框架的底层实现源码大白话解读。

    java类源码-JavaCollection:基于JDK1.8的集合类源码分析

    本篇文章将深入探讨`JavaCollection`类源码,了解其背后的实现原理。 首先,我们要明确`JavaCollection`是`java.util`包中的接口,它是所有集合的父接口,包括`List`, `Set`和`Queue`等。`Collection`接口提供了...

    JAVA集合类的深入探索

    集合类源码的深入研究 探索JAVA的本质 提高代码质量

    java集合源码-Java8-CollectionExp:java8集合类源码review

    下面将详细讨论Java 8中集合类的源码分析和相关知识点。 1. **接口的默认方法** Java 8为集合接口如`List`, `Set`, `Map`等引入了默认方法,这是一种不需实现类显式覆盖的接口方法。例如,`forEach(Consumer)`和`...

    java 集合源码学习,jdk1.8集合类所有的源码讲解

    本篇文章将深入探讨这些集合类的源码,揭示其内部实现机制。 首先,我们来看`List`接口,它代表了有序的元素集合,允许有重复元素。`ArrayList`和`LinkedList`是`List`接口的主要实现。`ArrayList`基于动态数组实现...

    小西瓜API系统集合系统源码

    2023全新小西瓜API系统集合系统源码安装教程:访问 http://你的域名/install 进行安装

    易语言源码易语言数组排序算法集合源码.rar

    易语言源码易语言数组排序算法集合源码.rar 易语言源码易语言数组排序算法集合源码.rar 易语言源码易语言数组排序算法集合源码.rar 易语言源码易语言数组排序算法集合源码.rar 易语言源码易语言数组排序算法集合...

    net.zip_.net源码学习

    例如,通过研究.NET中的集合类源码,可以了解如何实现高效的数据结构;查看控件的源码,可以学习到UI设计和事件处理的细节。 在这个压缩包中,有一个名为“.net面试题目.doc”的文件,这很可能是针对.NET开发者的一...

    android开发工具集合类xutils源码

    本文将深入探讨XUtils的源码,帮助你理解其内部机制,提升你的Android开发技能。 XUtils是一个集成了网络请求、图片加载、数据库操作、SharedPreferences管理、文件操作等功能于一体的全能型工具类库。它的设计理念...

    修改phprpc源码以支持集合类的string类型的转换

    在这个主题中,“修改phprpc源码以支持集合类的string类型的转换”涉及到对Phprpc框架的源代码进行定制化改造,以适应处理集合类中的字符串类型转换需求。在IT行业中,这样的改造通常是出于特定业务场景的需求,比如...

    Java常用类源码

    2. `ArrayList` 和 `LinkedList` 类:这两个类都是`List`接口的实现,用于存储有序的元素集合。`ArrayList`基于动态数组,适用于随机访问,而`LinkedList`基于双向链表,适合于频繁插入和删除。通过源码分析,我们...

    Java基础----集合类汇总

    源码阅读对于理解Java集合类的内部工作原理至关重要。例如,通过查看ArrayList的扩容机制,我们可以了解到为什么在预估集合大小时提前分配足够空间能提高性能。LinkedList的实现揭示了如何通过双向链表实现高效的...

Global site tag (gtag.js) - Google Analytics