声明:
1、数据结构参考资料
2、ArrayList源码来源
3、HashMap的参考资料
java集合类的关系如下图(里面只包含个人认为比较重要的一些集合类,其他的以后有空再补上)
1 java集合类一共分为两种主要类型:Collection和Map。
两者的区别在于每个数据单元所能存储的元素个数不同。Collection中每个单元只能存储一共元素;而Map中每个单元都以key-value(键值对)的形式存储一组互相关联的元素。
2 在集合类中,Collection、List、Set和Map都是接口,无法进行实例化。要得到一个集合类的实例必须通过实现它们的子类来进行。
3 Map是我们经常用到的集合类之一,其主要实现子类有HashMap、HashTable、WeakHashMap和TreeMap等。
HashMap是一种利用Hash函数进行定位的集合类,为了实现定位,HashMap采用一个数组进行数据的存储,每个位置对应一个hash值,程序根据元素key值的hashcode计算出其对应的hash值并对应到数组上的一个单元上。而为了解决hash冲突的问题,HashMap将每个hash对应的单元设置成一个链表的形式来解决冲突。
在初始化一个HashMap对象时,系统为我们分配一个一定长度(默认为16)的数组供我们存储数据。在我们插入的数据没有产生冲突(两个key产生的hash值)相同之前,系统直接将我们插入的元素插入到数组中;而一旦产生冲突,系统就会采用链表的形式(拉链法)存储相同hash的元素。
在HashMap中保存的元素总是以key-value的形式存在,而系统的操作总是按照key进行的,所以从某种意义上value可以看做是key上的一个附属。HashMap的实现提供了所有可选的映射操作,并允许使用null值和null键,而由于其插入规则(之后会看到),我们可以得到一个有多个null值的entry,而只能得到一个null键的entry。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 默认的初始容量是16,必须是2的幂。 static final int DEFAULT_INITIAL_CAPACITY = 16; // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << 30; // 默认加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 存储数据的Entry数组,长度是2的幂。 // HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表 transient Entry[] table; // HashMap的大小,它是HashMap保存的键值对的数量 transient int size; // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子) int threshold; // 负载因子实际大小 final float loadFactor; // HashMap被改变的次数 transient volatile int modCount; // 指定“容量大小”和“负载因子”的构造函数</span> public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // HashMap的最大容量只能是MAXIMUM_CAPACITY if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 找出“大于initialCapacity”的最小的2的幂 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; // 设置“负载因子” this.loadFactor = loadFactor; // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 threshold = (int)(capacity * loadFactor); // 创建Entry数组,用来保存数据 table = new Entry[capacity]; init(); } // 指定“容量大小”的构造函数 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 默认构造函数。 public HashMap() { // 设置“负载因子” this.loadFactor = DEFAULT_LOAD_FACTOR; // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); // 创建Entry数组,用来保存数据 table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); } // 包含“子Map”的构造函数 public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); // 将m中的全部元素逐个添加到HashMap中 putAllForCreate(m); } static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } // 返回索引值 // h & (length-1)保证返回值的小于length static int indexFor(int h, int length) { return h & (length-1); } }
从代码中的三个构造函数,我们可以看到其中由两个数据是可以由我们进行设置的,它们分别是initialCapacity和loadFactor。其中initialCapacity是开始时提供的数组的大小,默认值是16。而loadFactor则是hash中经常用到的负载因子默认值为0.75,系统根据负载因子和数组大小来决定HashMap的最大容量,当元素数目超过最大容量,HashMap就会进行增容操作。
HashMap既然以Hash为名,除了负载因子外,还包含了很多其他的hash元素。为了尽量避免冲突,HashMap提供了hash方法来进一步使各元素的hash均匀的分别,hash(int h)方法根据key的hashcode重新进行一次散列计算,为了防止低位不变,高位变化时造成hash冲突,该方法中加入了高位计算来消除这种冲突。另外,为了提高性能,HashMap也进行了一些巧妙的优化。我们知道,为了把数据放到有限长度的数组中,我们通常会把hash值对数组的长度进行取模,即进行%运算。而%运算属于算术运算,其消耗较大。于是HashMap巧妙的使用indexFor方法中所示的位运算使运算速度得以提高。再次基础上,为了提高空间的利用率减少冲突,HashMap还采用自动设置数组长度为大于等于初始长度(initialCapacity)的最小2的幂,也就是说数组的实际长度不一定是你开始时输入的initialCapacity的大小。这样使得length-1的值转换成二进制的时候各位上都是1,避免部分位置无法有效利用的情况(例:如果将length取值为15则length-1的二进制数为1110,这时无论我们输入的hash值是8(1000)还是9(1001),其余1110进行&运算后得到的结果都将是8,而下标为9的位置则永远没有数据可以匹配,从而造成空间的浪费和过多冲突导致的性能下降)。
public V put(K key, V value) { // HashMap允许存放null键和null值。 // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。 if (key == null) return putForNullKey(value); // 根据key的hashCode重新计算hash值。 int hash = hash(key.hashCode()); // 搜索指定hash值所对应table中的索引。 int i = indexFor(hash, table.length); // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。 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; } } // 如果i索引处的Entry为null,表明此处还没有Entry。 // modCount记录HashMap中修改结构的次数 modCount++; // 将key、value添加到i索引处。 addEntry(hash, key, value, i); return null; } private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; }
以上是向HashMap中添加元素时调用的方法。我们可以看到,当我们输入的Entry的key对应数组空间中为空的时候(key为nul也一样),程序直接将我们输入的Entry存放到对应空间中,当产生冲突时程序则会遍历对应链表,如果链表中没有相同的key则将Entry插入到链表头部,如果出现相同的key则会用新输入的value值替换掉旧的value值,这也是我之前说只能有一个null的Entry的原因。而以往HashMap存在值为null的Entry,所以当我们通过get反复得到一个null值时,有可能是因为该key对应的value为null,也可能是因为该key值不存在。所以在HashMap中不能通过get方法来判断key值是否存在。如果要进行判断需要调用其containsKey方法。
当我们的HashMap的元素数量超过loadFactor*数组长度时,HashMap就会进行最消耗性能的扩容工作。
//HashMap数组扩容 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; //如果当前的数组长度已经达到最大值,则不在进行调整 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } //根据传入参数的长度定义新的数组 Entry[] newTable = new Entry[newCapacity]; //按照新的规则,将旧数组中的元素转移到新数组中 transfer(newTable); table = newTable; //更新临界值 threshold = (int)(newCapacity * loadFactor); } //旧数组中元素往新数组中迁移 void transfer(Entry[] newTable) { //旧数组 Entry[] src = table; //新数组长度 int newCapacity = newTable.length; //遍历旧数组 for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
和其他以数组为基础的集合类一样,系统会重新给HashMap实例分配一个更大的数组空间(在HashMap中为原长度的2倍),之后将原数组中的数据拷贝到新的数组中,不同的是拷贝之前系统会重新对数组的hash值进行计算,而为此HashMap的每个Entry中不但包含了key值、value值和next指针,另外还包含了hash值记录其原来的hash值。
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry[] table; static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; …… }
而当数组的容量扩展到最大值(2^29)时,扩容将不再继续,而是将loadFactor自动赋值为Integer.MaxValue来达到hash标准。至于更加极端的情况,需要的内存太大,所以在此之前基本就已经因为内存不足而报错了。
最后,HashMap是线程不安全的。当我们使用迭代器访问一个HashMap实例的同时使用其他的线程对其进行修改将会触发HashMap的Fail-Fast机制,导致系统抛出ConcurrentModificationException。在HashMap中采用modCount(修改次数)来记录对象被修改的次数,每次对象修改时都会对其进行加1的运算。在初始化HashMap对象的迭代器的过程中会将该值附给迭代器中的expectedModCount值
HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } }
在迭代的过程中,程序通过对比expectedModCount和modCount的大小来判断该HashMap实例是否在迭代过程中被修改过,如果两者不等则证明已经被修改过,从而抛出异常。
final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException();
在HashMap的API中指出,由所有HashMap类的“Collection视图方法”返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的remove方法,其他任何时间任何方式的修改都会导致迭代器抛出ConcurrentModificationException异常。但是,快速失败只是尽可能的抛出异常,所以我们不能依赖该机制来编写我们的程序。
HashTable是Dictionary类的一个子类,属于一个过时的集合类,在java 4 被重写并实现了Map接口。HashTable和HashMap一样都是使用哈希链表的形式来组织数据,底层实现基本是相同的。不过,两者也存在不少差异的地方:
a、继承不同,HashTable继承自Dictionary,其中Dictionary是任何可以将键映射到值的类的父类。而HashMap继承自基于Map的骨干实现的AbstractMap类,该类可以最大限度的减少实现Map接口所需的工作。
public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
b、HashTable是线程安全的,不需要额外的同步机制就可以达到多线程安全的目的。
c、 HashTable不接受null值作为key或value。
d、HashTable底层数组的默认大小是11,扩容方式是old*2+1,而HashMap默认值是16,扩容方式是old*2。
e、HashTable直接使用key的hashcode作为计算位置的对象。
f、HashTable可以使用Iterator和Enumeration的方式进行遍历,而HashMap只能用Iterator进行。TreeMap是一个会对输入的元素安key排序的Map,为了有更好的查询和插入的性能,它使用了二叉排序树的形式来存储数据。二叉排序树的平均查找时间复杂度为O(logn),但是当输入有序的时候复杂度就会出现糟糕的O(n)的情况。为了避免这种情况,TreeMap采用了二叉排序树中的平衡二叉树来进行存储,为了解决插入时为了保持平衡而需要大量调整二叉树而使性能下降的情况,TreeMap又选择了复杂而高效的红黑树进行数据的组织。(上面说了红黑树的实现十分复杂,所以这里就不给大家展示了,我之后会对红黑树进行一个比较详细的讲解。这里先贴一个别人的讲解根据红黑树的算法来分析TreeMap的实现)
4 Collection的子类特征
List 以特定的次序存储元素(以线性方式存储元素)。所以之后取出的元素顺序与放入时的顺序可能不同。
Set中每个值只能保存一个元素,且不能含有重复的元素。
Queue中的元素遵守队列先进先出的原则。
4.1 在这里实现List接口的主要有ArrayList、LinkList、Vector、Stack等。
ArrayList 实现了一个长度可变的数组(Array),当我们实例化一个ArrayList时系统将会为这个实例分配一个数组空间(默认长度为10)。
在该实例中元素个数少于已分配空间大小的时候,对该实例的操作相当于对一个数组的操作;而当元素个数大于分配空间大小的时候,系统会先将实例的元素长度增加到原长度的1.5倍再加1,如果增加后的长度大于实际元素长度则分配一个大小为扩充后长度大小的数组给该实例对象存储实际,并将原数组空间中的数据拷贝到新数组空间中(同时释放原数组空间);如果扩展后的长度仍然不够则按照实际元素长度分配新数组空间。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; private transient Object[] elementData; private int size; public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList() { <span style="font-family: Arial, Helvetica, sans-serif;">this(10);</span> } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } public int size() { return size; } }
上面的代码是ArrayList的构造函数,其中最重要的两个属性是Object数组和size属性,分别定义了ArrayList的存储空间和元素个数。
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }上面的代码是ArrayList在元素个数没有超出空间大小是增添元素时调用的函数。
/** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != EMPTY_ELEMENTDATA) // any size if real element table ? 0 // larger than default for empty table. It's already supposed to be // at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
上面的代码是ArrayList扩容时调用的函数代码。
而ArrayList的删除工作则与数组的删除机制相同,当要删除第index(0<=index<len)个元素时,需要将分配数组第index之后的元素均向前移动,时间复杂度为O(n)。
// 删除ArrayList指定位置的元素 public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index,numMoved); elementData[--size] = null; // Let gc do its work return oldValue; } // 删除ArrayList的指定元素 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } // 快速删除第index个元素 private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; // 从"index+1"开始,用后面的元素替换前面的元素。 if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index,numMoved); // 将最后一个元素设为null elementData[--size] = null; // Let gc do its work } // 删除元素 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { // 遍历ArrayList,找到“元素o”,则删除,并返回true。 for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
而以上具体实现让我们可以清楚的看到ArrayList的添加和删除工作时间复杂度都较高(O(n)),而查找和更新的复杂度则较低(随机存取,时间复杂度为O(1))。
vector 的数据组织方式与ArrayList相同,两者在很多地方都是相似的。两者的区别在于Vector是同步的,而ArrayList不是;同时,Vector的默认长度也是10,但是其默认的扩容方式是增长为原来的两倍,而且Vector初始化时我们可以选择设置每次增加的数量,而ArrayList没有这样的功能。因为Vector是同步的,所以运行多个线程同时操作一个Vector对象,但为此也付出了一定的性能上的代价。所以,在不要求同步的情况下我们通常使用ArrayList,而需要同步的时候则使用Vector好一点。
LinkList 采用双向链表的形式组织数据。每一个结点除了含有元素外,还包含向前和向后的指针。
新建一个LinkList时系统生成一个头结点,其元素为null,前驱和后续均指向自己。
之后,我们向LinkList上添加信息,其底层将以双向链表的形式组织数据结构。
其底层代码结构如下图所示:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0; } private static class Entry<E> { E element; Entry<E> next; Entry<E> previous; }由于LinkList采用双向链表的形式进行组织,所以其插入和删除只要通过操作链表的前驱和后续即可,其时间复杂度为O(1);而进行查找操作时要遍历数组其时间复杂度为O(n)。
Stack继承自Vector,实现一个先进后出的堆栈。Stack提供了5个额外的方法,使得Vector可以被当做堆栈使用。push、pop分别是实现入栈和出栈的功能,peek实现对栈顶元素的读取,search方法用于查找元素在Stack中的位置,empty则用于判断Stack是否为空。
4.2 在这里我们主要讲一下set的三个子类HashSet、TreeSet和WeakHashSet的特性
HashSet是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素。下面我们来看一下HashSet的源码:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { // 使用 HashMap 的 key 保存 HashSet 中所有元素 private transient HashMap<E,Object> map; // 定义一个虚拟的 Object 对象作为 HashMap 的 value private static final Object PRESENT = new Object(); ... // 初始化 HashSet,底层会初始化一个 HashMap public HashSet() { map = new HashMap<E,Object>(); } // 以指定的 initialCapacity、loadFactor 创建 HashSet // 其实就是以相应的参数创建 HashMap public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<E,Object>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(initialCapacity , loadFactor); } // 调用 map 的 keySet 来返回所有的 key public Iterator<E> iterator() { return map.keySet().iterator(); } // 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数 public int size() { return map.size(); } // 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空, // 当 HashMap 为空时,对应的 HashSet 也为空 public boolean isEmpty() { return map.isEmpty(); } // 调用 HashMap 的 containsKey 判断是否包含指定 key //HashSet 的所有元素就是通过 HashMap 的 key 来保存的 public boolean contains(Object o) { return map.containsKey(o); } // 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap private transient NavigableMap<E,Object> m; public boolean add(E e) { return map.put(e, PRESENT) == null; } // 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素 public boolean remove(Object o) { return map.remove(o)==PRESENT; } // 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素 public void clear() { map.clear(); } ... }
</pre><p></p><pre>
由上面的程序我们可以看出HashSet的实现其实非常简单,它只是封装了一个HashMap来存储所以的集合元素,所以放入HashSet中的元素实际上都使用HashMap的key值来保存,而HashMap的vual值则用来保存一个名为PRESENT的虚拟的静态Object对象。
HashSet的大部分功能都是调用HashMap的方法来实现的所以两者在实现本质上是相同的。
与HashSet的情况类似,TreeSet也是通过封装TreeMap的形式实现的。其代码如下:
package java.util; public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { // NavigableMap对象 private transient NavigableMap<E,Object> m; // TreeSet是通过TreeMap实现的, // PRESENT是键-值对中的值。 private static final Object PRESENT = new Object(); // 不带参数的构造函数。创建一个空的TreeMap public TreeSet() { this(new TreeMap<E,Object>()); } // 将TreeMap赋值给 "NavigableMap对象m" TreeSet(NavigableMap<E,Object> m) { this.m = m; } // 带比较器的构造函数。 public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<E,Object>(comparator)); } // 创建TreeSet,并将集合c中的全部元素都添加到TreeSet中 public TreeSet(Collection<? extends E> c) { this(); // 将集合c中的元素全部添加到TreeSet中 addAll(c); } // 创建TreeSet,并将s中的全部元素都添加到TreeSet中 public TreeSet(SortedSet<E> s) { this(s.comparator()); addAll(s); } // 返回TreeSet的顺序排列的迭代器。 // 因为TreeSet时TreeMap实现的,所以这里实际上时返回TreeMap的“键集”对应的迭代器 public Iterator<E> iterator() { return m.navigableKeySet().iterator(); } // 返回TreeSet的逆序排列的迭代器。 // 因为TreeSet时TreeMap实现的,所以这里实际上时返回TreeMap的“键集”对应的迭代器 public Iterator<E> descendingIterator() { return m.descendingKeySet().iterator(); } // 返回TreeSet的大小 public int size() { return m.size(); } // 返回TreeSet是否为空 public boolean isEmpty() { return m.isEmpty(); } // 返回TreeSet是否包含对象(o) public boolean contains(Object o) { return m.containsKey(o); } // 添加e到TreeSet中 public boolean add(E e) { return m.put(e, PRESENT)==null; } // 删除TreeSet中的对象o public boolean remove(Object o) { return m.remove(o)==PRESENT; } // 清空TreeSet public void clear() { m.clear(); } // 将集合c中的全部元素添加到TreeSet中 public boolean addAll(Collection<? extends E> c) { // Use linear-time version if applicable if (m.size()==0 && c.size() > 0 && c instanceof SortedSet && m instanceof TreeMap) { SortedSet<? extends E> set = (SortedSet<? extends E>) c; TreeMap<E,Object> map = (TreeMap<E, Object>) m; Comparator<? super E> cc = (Comparator<? super E>) set.comparator(); Comparator<? super E> mc = map.comparator(); if (cc==mc || (cc != null && cc.equals(mc))) { map.addAllForTreeSet(set, PRESENT); return true; } } return super.addAll(c); } // 返回子Set,实际上是通过TreeMap的subMap()实现的。 public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { return new TreeSet<E>(m.subMap(fromElement, fromInclusive, toElement, toInclusive)); } // 返回Set的头部,范围是:从头部到toElement。 // inclusive是是否包含toElement的标志 public NavigableSet<E> headSet(E toElement, boolean inclusive) { return new TreeSet<E>(m.headMap(toElement, inclusive)); } // 返回Set的尾部,范围是:从fromElement到结尾。 // inclusive是是否包含fromElement的标志 public NavigableSet<E> tailSet(E fromElement, boolean inclusive) { return new TreeSet<E>(m.tailMap(fromElement, inclusive)); } // 返回子Set。范围是:从fromElement(包括)到toElement(不包括)。 public SortedSet<E> subSet(E fromElement, E toElement) { return subSet(fromElement, true, toElement, false); } // 返回Set的头部,范围是:从头部到toElement(不包括)。 public SortedSet<E> headSet(E toElement) { return headSet(toElement, false); } // 返回Set的尾部,范围是:从fromElement到结尾(不包括)。 public SortedSet<E> tailSet(E fromElement) { return tailSet(fromElement, true); } // 返回Set的比较器 public Comparator<? super E> comparator() { return m.comparator(); } // 返回Set的第一个元素 public E first() { return m.firstKey(); } // 返回Set的最后一个元素 public E first() { public E last() { return m.lastKey(); } // 返回Set中小于e的最大元素 public E lower(E e) { return m.lowerKey(e); } // 返回Set中小于/等于e的最大元素 public E floor(E e) { return m.floorKey(e); } // 返回Set中大于/等于e的最小元素 public E ceiling(E e) { return m.ceilingKey(e); } // 返回Set中大于e的最小元素 public E higher(E e) { return m.higherKey(e); } // 获取第一个元素,并将该元素从TreeMap中删除。 public E pollFirst() { Map.Entry<E,?> e = m.pollFirstEntry(); return (e == null)? null : e.getKey(); } // 获取最后一个元素,并将该元素从TreeMap中删除。 public E pollLast() { Map.Entry<E,?> e = m.pollLastEntry(); return (e == null)? null : e.getKey(); } // 克隆一个TreeSet,并返回Object对象 public Object clone() { TreeSet<E> clone = null; try { clone = (TreeSet<E>) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } clone.m = new TreeMap<E,Object>(m); return clone; } // java.io.Serializable的写入函数 // 将TreeSet的“比较器、容量,所有的元素值”都写入到输出流中 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); // 写入比较器 s.writeObject(m.comparator()); // 写入容量 s.writeInt(m.size()); // 写入“TreeSet中的每一个元素” for (Iterator i=m.keySet().iterator(); i.hasNext(); ) s.writeObject(i.next()); } // java.io.Serializable的读取函数:根据写入方式读出 // 先将TreeSet的“比较器、容量、所有的元素值”依次读出 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in any hidden stuff s.defaultReadObject(); // 从输入流中读取TreeSet的“比较器” Comparator<? super E> c = (Comparator<? super E>) s.readObject(); TreeMap<E,Object> tm; if (c==null) tm = new TreeMap<E,Object>(); else tm = new TreeMap<E,Object>(c); m = tm; // 从输入流中读取TreeSet的“容量” int size = s.readInt(); // 从输入流中读取TreeSet的“全部元素” tm.readTreeSet(size, s, PRESENT); } // TreeSet的序列版本号 private static final long serialVersionUID = -2479143000061671589L; }
集合类我也还处于学习的过程,如果有什么错漏的地方希望大家能够及时提醒,再此谢过!