一、集合框架
拿一张网上搜索的集合框架的图。
首先来整体讲一下集合框架。
整个集合框架的接口都是源于Collection接口。Collection接口继承了Iterable接口,使得所有继承Collection接口的接口,在被实现之后,都可以使用迭代器Iterator访问和遍历。
public interface Iterable<T> {
Iterator<T> iterator();
}
先说一下Collection接口下声明的方法。
(1)获得集合的大小:int size()
(2)判断集合是否为空:boolean isEmpty()
(3)是否包含指定的Object对象:boolean contains(Object o)
(4)获取迭代器:Iterator< E > iterator()
(5)将集合转为Object数组(无参):Object[] toArray()
(6)使用泛型,对集合进行数组转换:< T > T[] toArray(T[] a);
(7)增加元素:boolean add(E e)
(8)删除元素:boolean remove(Object o)
(9)与目标集合进行比较,判断是否相同:boolean containsAll(Collection< ? > c)
(10)往集合中增加一个集合:boolean addAll(Collection< ? extends E > c)
(11)删除在目标集合中的指定集合内的元素:boolean removeAll(Collection< ? > c)
(12)取得两个集合的交集:boolean retainAll(Collection< ? > c)
(13)清空集合:void clear()
(14)与集合进行比较:boolean equals(Object o)
(15)获取哈希值:int hashCode()
List接口和Set接口继承了Collection接口。而Map接口也是集合框架中的一个接口,但它的实现类是以键值对的形式作存储,与其他两个接口不一样。
下面分别说一说:
1、List
List是一个有序的集合接口,可以类比顺序表等线性表。
List接口是支持对元素进行增删改查动作的(add,set,get,remove)。List是有索引存在的。所以实现List的类都可以通过get(int index)获取对应的元素。
遍历List有2种,迭代器迭代遍历和循环遍历+get方法。
实现List接口的常见类有ArrayList、LinkedList、Vector、Stack、CopyOnWriteArrayList等。
List特有的迭代器ListIterator
ListIterator< E > listIterator()
Iterator接口的方法:
(1)删除集合中Iterator指向位置后面的元素:void remove()
(2)返回集合中Iterator指向位置后面的元素:E next()
(3)迭代器指向位置后面是否还有元素:boolean hasNext()
而ListIterator接口中新声明的方法有:
(ListIterator继承了Iterator)
(1)void add(E e)
将指定的元素插入列表,插入位置为迭代器当前位置之前
(2)void set(E e)
从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改为指定元素e
(3)E previous()
返回列表中ListIterator指向位置前面的元素
(4)boolean hasPrevious()
逆向遍历列表,判断列表迭代器前是否还有元素
(5)int previousIndex()
返回列表中ListIterator所需位置前面元素的索引
(6)int nextIndex()
返回列表中ListIterator所需位置后面元素的索引
ListIterator可以从头或者从尾进行遍历。ListIterator可以遍历修改,而Iterator只能遍历,不能修改。
//使用迭代器
Iterator<Object> it=list.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
//使用ListIterator
ListIterator<Object> listIt = list.listIterator();
while(listIt .hasNext()){
Object obj = listIt .next();
System.out.println(obj);
}
//使用foreach
for(Object obj : list){
System.out.println(obj);
}
下面来看看实现List接口的类。
(1)ArrayList
ArrayList是数据结构中的数组类型。
默认构造方法是新建一个长度为10的数组。
public ArrayList() {
this(10);
}
其遍历效率高,但增加删除麻烦。
因为ArrayList新建的时候,是通过创建新数组再复制老数组至新数组完成的,所以增加或删除的时候效率低。
这里列一下add的源码
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
通过计算add后的长度与现容量的大小作比较,必要时增加数组容量(新建一个更大的数组,然后将老数组复制至新数组),确保新加进来的数据是可以正常保存的。
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
ArrayList是线程不安全的(因为ArrayList内部没有使用同步)。在需要考虑多线程的情况下,可以通过Collections.synchronizedList(List i)函数返回一个线程安全的ArrayList类,或者使用Concurrent并发包下对应的集合类。
(2)LinkedList
LinkedList是基于双向循环链表实现的,是链表结构,不同步。
因为LinkedList是双向循环链表,所以顺序访问效率高,但是随机访问效率低。
由于实现了Queue接口,因此也可以用于实现堆栈、队列。
LinkedList接口
因为LinkedList实现了List接口,所以其类中也有get和remove等方法根据索引值获取值。它是通过让索引与1/2的长度做比较,决定从哪边开始遍历,从而较快找到对应索引的元素。其他利用索引进行的操作也是如此。(通过调用entry(index)的方法)
public E get(int index) {
return entry(index).element;
}
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
Entry<E> e = header;
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
而Entry是LinkedList内部的一个私有静态类。Entry类是一个链表的节点结构。包括了存储的数据,节点前和节点后对应的节点。
在LinkedList中,也包含了队列、堆栈相关操作的方法。
首先看一下LinkedList作为链表的方法。
获取LinkedList的第一个元素:E getFirst()
获取LinkedList的最后一个元素:E getLast()
删除LinkedList的第一个元素:E removeFirst()
删除LinkedList的最后一个元素:E removeLast()
在起始位置增加元素:addFirst(E e)
在结束位置增加元素:addLast(E e)
下面是关于模拟队列的操作:
返回第一个节点:E peek()
当大小为0时,返回null
public E peek() {
if (size==0)
return null;
return getFirst();
}
返回第一个节点:E element()
当大小为0时,抛出异常(NoSuchElementException)
public E element() {
return getFirst();
}
移除第一个元素:E poll()
移除第一个元素:E remove()
区别与上面的相同。
将元素添加到末端:boolean offer(E e)
将元素添加到第一个位置:boolean offerFirst(E e)
将元素添加到末端:boolean offerLast(E e)
返回第一个节点:E peekFirst()
返回最后一个节点:E peekLast()
删除并返回第一个节点:E pollFirst()
删除并返回最后一个节点:E pollLast()
模拟堆栈:
添加到第一个节点(进堆):void push(E e)
删除并返回第一个节点:E pop()
从LinkedList的实现方式可以发现,LinkedList不会出现容量不足的问题(双向链表特性)
因为LinkedList也实现了Deque接口,所以LinkedList也可以被当做双向队列进行使用。
队列方法 等效方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()
也可以当做堆栈进行使用:
栈方法 等效方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()
(3)Vector(已过时)
因为现在也不建议使用这个类了,所以就粗略讲讲就好了。
Vector实现了一个动态数组。和ArrayList和相似。
Vector是同步访问的。
Vector包含了许多传统的方法,这些方法不属于集合框架。
为什么不建议使用Vector?
因为Vector是线程安全的,所以它的性能是比ArrayList差很多的。而且现在也有更好的办法可以解决ArrayList线程不安全的问题。所以就过时了。
(4)Stack(已过时)
堆栈类。(过时的我也不想看了= =。懒)
为什么不建议使用Stack?
因为Stack继承了Vector。原因不讲了。
(5)CopyOnWriteArrayList
CopyOnWriteArrayList是一个ArrayList的线程安全的变体。其实这里可以抽成CopyOnWrite容器进行讨论,下面引用一下别人的文章段落来说说。
其实是一个CopyOnWrite的容器。当我们往一个容器添加元素的时候,不是直接往当前容器添加,而是先将当前容器进行复制,复制出一个新的容器,然后往新容器里添加元素,添加完之后,再将原容器的引用指向新容器。这样可以对CopyOnWrite容器进行并发的读,也不需要加锁。CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
看看add方法。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
在复制容器的过程中,是加了锁了,这样才能保证不会复制多个容器。
而在读的时候,是不需要加锁的,就算有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。
CopyOnWrite并发容器用于读多写少的并发场景。
CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。
内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。
针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。
数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
什么场景下更适宜使用LinkedList,而不用ArrayList
(1) 你的应用不会随机访问数据。因为如果你需要LinkedList中的第n个元素的时候,你需要从第一个元素顺序数到第n个数据,然后读取数据。
(2)你的应用更多的插入和删除元素,更少的读取数据。因为插入和删除元素不涉及重排数据,所以它要比ArrayList要快。
2、Set接口
Set接口中声明的方法与List基本一致。但是实现Set接口的类中的元素则是无序的、唯一的(不可重复)。
实现Set接口的类有:HashSet,LinkedHashSet,TreeSet,CopyOnWriteArraySet等等。
遍历Set的方式:
// 迭代遍历:
Set<String> set = new HashSet<String>();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
// for循环遍历:
for (String str : set) {
System.out.println(str);
}
(1)HashSet
HashSet是不保证顺序的,允许使用null元素。
HashSet底层结构是使用HashMap作存储。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable{
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<E,Object>();
}
……
}
而且HashSet实现不是同步的,涉及多个线程同时访问的时候,必须保持外部同步。使用 Collections.synchronizedSet 方法来“包装” Set。
Set s = Collections.synchronizedSet(new HashSet(...));
HashSet拥有List接口中的方法,用法一致,但实现方式不同。
下面举例add方法。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
PRESENT是类中定义的一个对象,个人理解其实就是用来充数占位而已。而使用要增加的对象做键,存入HashMap中。这就是为什么HashSet是无序且唯一的原因。
其他的方法都是对内置的HashMap对象进行数据操作。具体操作等到后面说到HashMap的时候再详细说。
如果需要将自定义对象放入HashSet中,需要重写该对象对应类的equals方法和hashCode()方法。
(2)LinkedHashSet
LinkedHashSet继承了HashSet,是使用哈希表和链表实现的。
LinkedHashSet底层使用LinkedHashMap来保存所有元素。这个与HashSet类似。
它定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。使得集合保证唯一性,看起来就像以插入顺序保存一样。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
(3)TreeSet
emmmm….猜的没错。
还是老样子。TreeSet的底层确实又是用TreeMap。TreeSet是基于TreeMap的NavigableSet接口(NavigableSet接口继承了SortedSet接口)实现的。
所以,TreeSet是实现SortedSet的其中一个实现类。(另一个是ConcurrentSkipListSet)
因为TreeSet实现了SortedSet接口,所以它能够确保集合元素处于排序状态。
TreeSet支持2种排序方式,自然排序和定制排序。这个取决于使用的构造方法。
构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
构造一个新的空 TreeSet,它根据指定比较器进行排序。
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<E,Object>(comparator));
}
如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法。
(4)CopyOnWriteArraySet
这个类跟上面讲过的CopyOnWriteArrayList类似。它是线程安全的、无序的集合。可以看成是一个线程安全的HashSet。而需要注意的一点是,CopyOnWriteArraySet是通过CopyOnWriteArrayList(动态数组队列)实现的。
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
因为写操作需要复制一个新的数组,所以对于写操作相关的开销很大。
这里引用一个例子,这个例子很好说明了CopyOnWriteArraySet的使用方式:
public class CollectionDemo {
// TODO: set是HashSet对象时,程序会出错。
//private static Set<String> set = new HashSet<String>();
private static Set<String> set = new CopyOnWriteArraySet<String>();
public static void main(String[] args) {
// 同时启动两个线程对set进行操作!
new MyThread("ta").start();
new MyThread("tb").start();
}
private static void printAll() {
String value = null;
Iterator iter = set.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+", ");
}
System.out.println();
}
private static class MyThread extends Thread {
MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 10) {
// “线程名” + "-" + "序号"
String val = Thread.currentThread().getName() + "-" + (i%6);
set.add(val);
// 通过“Iterator”遍历set。
printAll();
}
}
}
}
运行结果:
ta-1, tb-1,
ta-1, tb-1, ta-2,
ta-1, tb-1, ta-2, ta-3,
ta-1, tb-1, ta-2, ta-3, ta-4,
ta-1, ta-1, tb-1, tb-1,
ta-2, ta-1, ta-3, tb-1, ta-4, ta-2, ta-5,
ta-3, ta-4, ta-5, ta-1, tb-2,
tb-1, ta-1, ta-2, tb-1, ta-3, ta-2, ta-4, ta-3, ta-5, ta-4, tb-2, ta-5, ta-0,
ta-1, tb-1, ta-2, ta-3, ta-4, ta-5, tb-2, ta-0, tb-3,
ta-1, tb-2, tb-1, ta-0, ta-2, ta-3, ta-4, ta-5, tb-3,
tb-2, ta-0, tb-3,
ta-1, ta-1, tb-1, tb-1, ta-2, ta-2, ta-3, ta-4, ta-5, ta-3, tb-2, ta-4, ta-0, ta-5, tb-3,
tb-2, ta-0, ta-1, tb-3, tb-1, tb-4,
ta-2, ta-3, ta-1, ta-4, tb-1, ta-5, ta-2, tb-2, ta-3, ta-0, ta-4, tb-3, ta-5, tb-4,
tb-2, ta-0, tb-3, tb-4, tb-5,
ta-1, tb-1, ta-2, ta-3, ta-4, ta-5, tb-2, ta-0, tb-3, tb-4, tb-5, tb-0,
ta-1, tb-1, ta-2, ta-3, ta-4, ta-5, tb-2, ta-0, tb-3, tb-4, tb-5, tb-0,
ta-1, tb-1, ta-2, ta-3, ta-4, ta-5, tb-2, ta-0, tb-3, tb-4, tb-5, tb-0,
ta-1, tb-1, ta-2, ta-3, ta-4, ta-5, tb-2, ta-0, tb-3, tb-4, tb-5, tb-0,
ta-1, tb-1, ta-2, ta-3, ta-4, ta-5, tb-2, ta-0, tb-3, tb-4, tb-5, tb-0,
3、Map接口
Map接口的实现类的特点是以键值对(key-value)的形式进行存储。
public interface Map< K, V > {...}
常见的实现类有:HashMap,Hashtable,LinkedHashMap,TreeMap,Attributes,Properties等等。
下面先介绍一下Map接口中一些声明的主要方法。
int size():返回map集合的大小。
boolean isEmpty():判断集合是否为空。
boolean containsKey(Object key):判断是否包含某个key。
boolean containsValue(Object value):判断是否包含某个value。
V get(Object key):通过key获取值。
V put(K key, V value):向map集合中新增元素。
V remove(Object key):根据key移除对应元素。
void clear():清空map集合。
Set< K > keySet():获取key的集合。
还有equals和hashCode两个常见的方法,就不列举了。
遍历Map的方法:
Map<Object, Object> map = new HashMap<>();
// 遍历键
for(Object obj : map.keySet()){
System.out.println("key = " + obj);
}
// Map中的值遍历
for(Object obj : map.values()){
System.out.println("value = " + obj);
}
// 在for-each循环中使用entries来遍历
for (Map.Entry<Object , Object > entry : map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
// 使用Iterator遍历
Iterator<Map.Entry<Object , Object >> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<Integer, Integer> entry = entries.next();
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
// 键值遍历
for (Object key : map.keySet()) {
Object value = map.get(key);
System.out.println("Key = " + key + ", Value = " + value);
}
(1)HashMap
HashMap它实现了Map接口,所以存储的元素也是键值对映射的结构,并允许使用null值和null键,其内元素是无序的,如果要保证有序,可以使用LinkedHashMap。HashMap是线程不安全的。
HashMap的构造方法。
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。
下面是put方法:
public V put(K key, V value) {
// 处理key为null,HashMap允许key和value为null
if (key == null)
return putForNullKey(value);
// 得到key的哈希码
int hash = hash(key.hashCode());
// 通过哈希码计算出bucketIndex
int i = indexFor(hash, table.length);
// 取出bucketIndex位置上的元素,并循环单链表,判断key是否已存在
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;
}
}
// key不存在时,加入新元素
modCount++;
addEntry(hash, key, value, i);
return null;
}
还有关于HashMap的深入理解的文章《HashMap之深入理解》
看完上面的文章之后,自己初步总结的结论是:
HashMap在得到哈希值的时候,先是使用了hashCode计算出key的hash值,然后再调用本身的hash方法做更细致的计算,以降低hash冲突发生的概率(自认为一个好的哈希函数可以降低哈希冲突发生的次数)。在发生哈希冲突的时候,HashMap采用链表解决冲突。将新的节点(addEntry)加到对应位置的表头(第一个位置)。
HashMap另一个是及时扩容,通过哈希表容量*负载因子计算出一个临界值,到达这个临界值的时候,将做扩容操作,然后将老数组复制后给新数组。所以合理利用哈希表长度和负载因子可以有效提供HashMap的性能。扩容之后,元素对应的位置一般会发生改变。
另一个问题是,HashMap的方法是不同步的(线程不安全的),解决这个问题的方法可以有:
一是使用Hashtable;二是使用Collections类的synchronizedMap方法得到一个封装对象。
(2)Hashtable
Hashtable与HashMap类似,包括底层实现,哈希冲突的处理,扩容等等,但是Hashtable是线程安全的,HashMap是线程不安全的。
当然此处经常会有一个问题会遇到:
Hashtable和HashMap的区别
①继承的父类不同。Hashtable基于Dictionary类,而HashMap是基于AbstractMap。
②Hashtable的键和值都不允许为null值;而HashMap允许键和值都是null。
③Hashtable的方法是同步的,线程安全;HashMap的方法是不同步的,线程不安全。
④hash值不同。哈希值的使用不同,Hashtable直接使用对象的hashCode。而HashMap重新计算hash值。
⑤内部实现使用的数组初始化和扩容方式不同。
Hashtable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
Hashtable扩容时,将容量变为原来的2倍+1,而HashMap扩容时,将容量变为原来的2倍。
Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
(3)LinkedHashMap
LinkedHashMap继承了HashMap类,与HashMap有着同样的存储结构,但它又增加了一个双向链表的头结点,将所有添加到LinkedHashmap的节点一一串成了一个双向循环链表,所以LinkedHashMap也保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同。
public class LinkedHashMap<K,V>
extends HashMap<K,V> implements Map<K,V>{
private transient Entry<K,V> header;
...
}
其构造函数
// 默认构造方法
public LinkedHashMap() {
super();
accessOrder = false;
}
// 指定集合的初始容量大小的构造方法
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
// 指定集合初始容量大小和负载因子的构造方法
public LinkedHashMap(int initialCapacity, float loadFactor){
super(initialCapacity, loadFactor);
accessOrder = false;
}
// 指定集合容量大小、负载因子、排序方式的构造方法
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
// 传入Map的构造方法
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
其中accessOrder返回false代表是基于插入顺序,返回true代表是基于访问顺序。
LinkedHashMap内部类Entry定义了其前后节点的引用,构成了一个双向链表的结构基础。
private static class Entry<K,V> extends HashMap.Entry<K,V> {
Entry<K,V> before, after;
...
}
在LinkedHashMap中,put方法未重写,而是重写了父类HashMap中put方法中的调用方法void recordAccess(HashMap m) ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。
LinkedHashMap 能够做到按照插入顺序或者访问顺序进行迭代。
(4)TreeMap
TreeMap是基于红黑树实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap是非同步的。
// 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。
public TreeMap() {
comparator = null;
}
// 创建的TreeMap包含Map
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
// 指定Tree的比较器
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 创建的TreeSet包含SortedMap
public TreeMap(SortedMap<K, ? extends V> m) {
comparator = m.comparator();
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
// 这是TreeMap很重要的成员变量
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
private transient int size = 0;
这是TreeMap内部类Entry的结构,内部结构是树。
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;
...
}
此处关于红黑树的内容不做描述(因为我还没去看= =以后补上),因为TreeMap是基于红黑树算法实现的,所以此处就暂时到这里了。先附上一篇文章。
《Java提高篇(二七)—–TreeMap》
最后附上一个问题:
重写equals要满足几个条件:
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
对于任何非空引用值 x,x.equals(null) 都应返回 false。
其实集合框架的内容很多也很细,本来想做一些详细的学习和介绍,但这里只是做一个简单的了解和描述。也是自己学习Java中集合框架的一个起点吧,再接再厉。因为第一次写,没什么经验,结构和内容应该是比较混乱的。