`
cowboy_bebop
  • 浏览: 111705 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

深入源码看数据结构(再探集合框架)

    博客分类:
  • J2SE
阅读更多

从源码的高度来看看集合中各个实现类的是如何组织我们存进去的数据的,主要包括Java类库中提供的几个具体的类: 
LinkedList 
ArrayList 
HashMap 
HashSet 
TreeMap 
TreeSet 
PriorityQueue(顺序按下面的讲解顺序) 

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

//创建一个LinkedList类型的对象
java.util.LinkedList<String> l=new java.util.LinkedList<String>();
l.add(e);//e为E类的对象


打开add方法的源码看看: 
public boolean add(E e) {
//调用LinkedList的私有方法
//header是LinkedList中的一个属性,这样定义的private transient Entry<E> //header = new Entry<E>(null, null, null);
	addBefore(e, header);        
return true;
	}
//被调用的私有方法
private Entry<E> addBefore(E e, Entry<E> entry) {
	Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
	newEntry.previous.next = newEntry;
	newEntry.next.previous = newEntry;
	size++;
	modCount++;
	return newEntry;
	}
//Entry<E>是LinkedList的内部类,包装每一个E类型的对象e,形成一个链表
private static class Entry<E> {
	E element;
	Entry<E> next;
	Entry<E> previous;
	Entry(E element, Entry<E> next, Entry<E> previous) {
	    this.element = element;
	    this.next = next;
	    this.previous = previous;
	}
    }


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



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


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


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



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


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


想必大家看这段代码都看到晕了吧,为了让大家能够更加形象的人知道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> 


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


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


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


public V put(K key, V value) {
        Entry<K,V> t = root;//root是整棵树的根节点
        if (t == null) {
	    //插入的第一个元素会成为根节点
            root = new Entry<K,V>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // 调用Comparator的compare()方法确定新加的元素出现的位置。
	//我们可以再自己定义的类中实现Comparator接口,然后传给树集的构造器。从而按照自己定义的不同的比较规则来给
整个树的数据进行排序。
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
	//这里我们将传进来的数据包装成Entry<K,V> ,通过Entry<K,V> 内部类的//属性 Entry<K,V> parent来组织一棵树
        Entry<K,V> e = new Entry<K,V>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
	}


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

//用字典式排序。不展开分析了。
 public int compareTo(String anotherString) {
	int len1 = count;
	int len2 = anotherString.count;
	int n = Math.min(len1, len2);
	char v1[] = value;
	char v2[] = anotherString.value;
	int i = offset;
	int j = anotherString.offset;
	if (i == j) {
	    int k = i;
	    int lim = n + i;
	    while (k < lim) {
		char c1 = v1[k];
		char c2 = v2[k];
		if (c1 != c2) {
		    return c1 - c2;
		}
		k++;
	    }
	} else {
	    while (n-- != 0) {
		char c1 = v1[i++];
		char c2 = v2[j++];
		if (c1 != c2) {
		    return c1 - c2;
		}
	    }
	}
	return len1 - len2;
	    }
      

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



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


public boolean add(E e) {
	return m.put(e, PRESENT)==null;
	}

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



----------------------------------------------------------------------------------------------------- 
7、java.util.PriorityQueue<E> 

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

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

分享到:
评论

相关推荐

    集合框架源码分析

    Java集合框架是Java编程语言中的一个核心组件,它为数据组织提供了一系列的...通过深入学习和分析集合框架的源码,我们可以更好地理解其工作原理,优化代码性能,避免潜在的问题,并设计出更加高效的算法和数据结构。

    数据结构和Java集合框架 英文版 第三版

    通过本篇内容的学习,我们不仅了解了数据结构的基础知识和Java集合框架的核心概念,还探讨了如何结合JDK源码来深入理解这些知识点。掌握好这些内容对于提高编程技能、优化代码质量和提升软件工程能力具有重要意义。...

    数据结构与算法(Java语言版)含有源码

    例如,Java集合框架(java.util包)为实现各种数据结构提供了基础类,如List、Set和Map接口,以及其实现类。同时,Java的异常处理、多线程、IO流等功能,对于处理复杂问题非常有帮助。 4. 源代码:提供的源码.zip...

    集合框架的使用方法

    在Java编程语言中,集合框架是处理对象集合的核心工具,它提供了一套高效、灵活的数据结构和算法。本文将深入探讨集合框架的使用方法,包括其基本概念、主要类库以及常见操作,同时也会提及一些源码分析和实用工具。...

    java数据结构和算法(第二版)[含源码]

    《Java数据结构与算法(第二版)》是一本深度探讨Java编程中数据结构和算法的专著,包含源码分析,旨在帮助读者深入理解并掌握这些核心概念。数据结构是计算机存储、组织数据的方式,而算法则是解决问题或执行任务的...

    java各个数据结构源码

    8. **集合框架**:Java集合框架包括接口(如List、Set和Queue)和实现类(如ArrayList、HashSet和PriorityQueue)。深入源码可以了解它们之间的关系,以及如何选择适合特定需求的集合类。 通过对这些源码的学习,...

    Java数据结构源码

    本资源"Java数据结构源码"提供了丰富的Java实现的数据结构和算法实例,旨在帮助开发者深入理解并应用这些概念。 1. **数据结构**: - **数组**:是最基本的数据结构,它提供了通过索引访问元素的能力。Java中的...

    Java集合框架详解

    集合框架是一个统一的数据结构模型,它定义了一系列接口,如Collection、List、Set和Map,以及它们的实现类,如ArrayList、LinkedList、HashSet、HashMap等。容器是这些接口和类的统称,它们用于存储和管理对象。...

    C#数据结构项目源码.zip

    本项目源码集合了C#实现的数据结构,为学习和理解提供了实践性的示例。本文将深入探讨其中的关键知识点。 首先,让我们了解一下数据结构的基本概念。数据结构主要包括数组、链表、栈、队列、树、图等。这些数据结构...

    java数据结构源码

    深入理解这些数据结构的源码,可以洞悉其内部实现机制,提高编程技巧,更好地解决实际问题。通过分析Java数据结构源码,开发者可以学习到如何优化内存管理,减少不必要的空间开销,以及如何设计高效算法来处理复杂的...

    深入理解Java集合框架.zip

    1. **集合接口**:Java集合框架的核心接口包括`List`、`Set`和`Queue`,它们定义了各种数据结构的行为。`List`接口维护元素的顺序,并允许重复元素;`Set`接口不允许重复元素,保持元素唯一性;`Queue`接口则实现了...

    [数据结构(Java版)(第4版)][叶核亚][程序源代码].rar

    这本书通过深入浅出的方式,帮助读者理解和掌握各种数据结构及其在实际问题中的应用。 在数据结构的学习中,抽象数据类型(ADT)是非常基础的概念。ADT是一种逻辑上的数据模型,它定义了数据的组织方式以及对这些...

    数据结构源码:多维数组

    在计算机科学中,数据结构是组织、存储和处理数据的方式,它是编程的基础。多维数组是一种常见且重要的数据结构,特别是在处理表格型数据或者矩阵运算时。本篇将深入探讨多维数组的概念、实现方式以及它在源码中的...

    java数据结构与算法+applet与源码

    例如,集合框架(java.util包)中就包含了多种数据结构的实现,如ArrayList、LinkedList、HashMap等,这些都是数据结构在Java中的具体体现。开发者可以通过研究这些类的源码来深入理解它们的工作原理。 Applet是...

    数据结构与算法(JAVA)及源码

    同时,JAVA的集合框架提供了现成的接口和类,如List、Set、Map等,方便开发者快速实现数据结构。 源码分析是学习数据结构与算法的重要环节,通过阅读和理解源码,可以深入了解各种数据结构和算法的内部工作原理,...

    java语言集合框架

    源码阅读对于深入理解Java集合框架至关重要。通过分析`ArrayList`、`HashMap`等类的实现,我们可以了解它们如何利用数据结构优化性能,以及如何处理并发问题。例如,`ArrayList`的扩容机制,`HashMap`的哈希冲突解决...

    java数据结构源码和applet演示

    《Java数据结构源码与Applet演示》是针对Java编程语言深入学习数据结构的重要资源,尤其适合想要在实际编程中理解和应用数据结构的开发者。这本书的第二版在第一版的基础上进行了更新和完善,提供了丰富的源码示例和...

    Java数据结构和算法中文第二版源码.rar

    在Java中,这些数据结构可以通过内置的集合框架(如ArrayList、LinkedList、Stack、Queue等)或者自定义类来实现。源码中的WorkshopApplets.ZIP可能包含了与数据结构相关的交互式小程序,通过可视化的方式帮助理解...

    Java数据结构和算法系列文章,源码.zip

    Java中的数据结构主要体现在其集合框架中,包括接口(如List、Set、Map)和实现类(如ArrayList、LinkedList、HashMap等)。例如,ArrayList基于动态数组实现,适合快速查找,而LinkedList则通过节点链接实现,更...

    Java集合框架常用集合源代码及其实现

    Java集合框架是Java编程语言中的一个核心部分,它为数据结构和对象的存储、管理和操作提供了统一的接口和实现。这个框架包括了多种类型的集合,如List、Set、Queue和Map,以及它们的各种实现类,如ArrayList、...

Global site tag (gtag.js) - Google Analytics