本文是个人学习的阶段总结,中间或许存在着错误,欢迎大家指正,不甚感激!
实际的Java开发中,都会遇到很多的对象,如何来高效的管理这些对象就显得很重要了。为此Java提供了集合框架来解决这个问题。Java类库帮助我们在程序设计中实现了很多传统的数据结构。话不多少,先来一张集合类之间的关系图。
从上述的图中可以看出,集合类中两个基本的接口是Collection和Map,在Collection下面又有List,Set接口,各自的接口下面又有相应的实现类,所以在了解具体的实现类之前,了解接口是很有必要的。
1.Collection接口
通过查看Collection接口的源码可以知道,该接口继承了Iterable接口,在Iterable接口中,只有一个iterator()方法,返回的是一个Iterator迭代器,接口Iterator的定义如下:
<span style="font-size:14px;"><span style="font-family:FangSong_GB2312;">public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}</span></span>
在该接口中,有三个方法。next()方法返回的是迭代中的下一个元素,通过反复调用next()方法,可以访问集合中的每一个元素,但是,如果到了集合的末端的话,next方法就会抛出一个异常出来,所以在调用next的方法之前需要调用hasNext()的方法进行判断,如果迭代器中还有多个供访问的元素,这个方法就会返回True。remove方法将会删除上次调用next方法时返回的元素。通过next()方法访问迭代器中元素如下所示:
<span style="font-size:14px;">Collection<String>c=.....
Iterator<String>iter=c.iterator();
while(iter.hasNext()){
String element=iter.next();
//do something with element
}
for(String element:c){
//do something with element
}</span>
在Collection接口中还定义了如下的方法:
在实现了Collection接口的具体类当中都要去实现相应的方法,15个方法的含义很好理解,不在一一叙述。
2.Map接口
首先Map接口是没有继承Collection接口的,从最开始的那张图中可以看的很清楚,二者之间是没有关系的,Map提供了Key到Value的映射,在Map中不存在相同的key值,Map 接口提供 3 种集合的视图,Map 的内容可以被当作一组 Key 集合,一组 Value 集合,或者一组 Key-Value 映射。Map接口中的方法如下:
跟Collection一样,Map接口中也定义了15个方法,简单介绍几个主要的方法。
1.put方法:将键与值对应的关系插入到映射表中,如果这个键是已经存在的,新的对象将会取代与这个键对应的旧对象,方法返回 键对应的旧值,如果键之前是没有存在过的话返回null,键值可以为null,但是value值不可以为null。
2.get方法,获取与键对应的值,返回与键对应的对象,没有的话返回null。
3.entrySet方法,返回映射表中的键值对视图,可以从这个返回的集合中删除元素,就相当于在映射表中删除了,但是不可以添加 元素。
Map接口的实现类主要有HashMap,TreeMap,Hashtable。
3.List
List是这样定义的public interface List<E> extends Collection<E>,很显然,这是一个继承了Collection的接口。List是一个有序的集合(有时候也叫做序列),不同于我们接下来将会介绍的set,List是允许有重复的元素的,当然也是允许空元素的,用户可以使用索引来访问list中的元素,类似于Java中的数组。除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。List的常见的实现类有ArrayList,LinkedList和Vector。
ArrayList实现了可变大小的数组,每一个ArrayList的实例都有一个容量,表示存储元素数组的大小,这个容量在,默认是10,在每次向其中添加元素时,ArrayList都会检查是否需要扩容操作,所以在构建集合的时候可以根据我们的业务需求估计一个合适的容量,这样可以减少因为不断的扩容而带来的拷贝问题。ArrayList的实现不是同步的如果有多个线程同时访问一个ArrayList的实例,所以为了保持同步,可以在创建的时候来完成。
<span style="font-size:14px;">List list = Collections.synchronizedList(new ArrayList(...)); </span>
LinkedList实现了List接口,内部实现是双向链表,除了实现了List接口之外,Linkedlist还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。同ArrayList一样,LinkedList也是不是同步的。
ArrayList的实现基于数组,LinkedList的实现基于链表,对于随机访问来说,ArrayList明显要由LinkedList,而对于增加和删除操作来说,LinkedList要由于ArrayList,因为ArrayList要移动数据。
至于List提供的其他的方法,在这里就不一一叙述了,直接查阅文档即可。特别强调一点,如果在List中要根据对象的内容来判断是否相等的话,需要重写equals和hashcode方法。
4.Set
同List一样,Set也是继承自Collection的一个接口,不同的是Set是一种不包含重复元素的集合即任意的两个元素 e1 和 e2 都有 e1.equals(e2)=false。Set 最多有一个 null 元素。很明显,Set 的构造函数有一个约束条件,传入的 Collection 参数不能包含重复的元素。Set的实现类有HashSe和TreeSet。
HashSet继承自AbstractSet实现了Set接口,在AbstractSet中实现了大部分的Collection中的方法。HashSet的实现是基于HashMap,所以在HashSet的很多方法实现中都是基于HashMap的,例如size方法:
<span style="font-size:14px;">public int size() {
return map.size();
}</span>
TreeSet和HashMap一样也是基于TreeMap实现的,不一样的是TreeSet是有序的,基于有序二叉树。
5.Map
之前已经提到过了,Map是Key到Value的映射,Map的常见实现类有HashMap,TreeMap和Hashtable。接下来各自详细介绍。
<span style="font-size:14px;">public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable</span>
HashMap继承了AbstractMap实现了Map接口,在AbstractMap类中实现了Map接口中的大部分方法。HashMap主要提供了3种构造函数:
<span style="font-size:14px;">public HashMap()
public HashMap(int initialCapacity)
public HashMap(int initialCapacity, float loadFactor)</span>
第一个构造函数是默认的初始容量为16和默认的装填因子为0.75的空HashMap,第二个是构造一个默认的装填因子和给定的初始容量的HashMap,第三个是构造一个带有指定的初始容量和装填因子的HashMap.
在这里提到了两个参数:初始容量,加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小.
HashMap的数据结构如下图所示:
可以看出,HashMap的底层实现还是数组,数组的每一个项都是一条链,构造函数中的initialCapacity就代表了数组的长度,数组的长度一般是2的幂次方。在HashMap的构造函数中,每次新建一个HashMap时都会初始化一个table数组,数组里面存放的都是Entry节点,其中Entry为HashMap的内部类,它包含了键key、值value、下一个节点next,以及hash值。接下里看HashMap的存取实现。
HashMap的put函数如下:
<span style="font-size:14px;"> 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++;
addEntry(hash, key, value, i);
return null;
}</span>
可以看出,在进行put操作时候,首先判断添加的key值是否为空,如果key值为空直接返回,否则计算key的hash值,通过hash值来找到key在table数组中的位置,接下来对i位置处的链表进行查找,如果找到了相同的key值,则用新的value值覆盖原来的value值,返回旧的value值,否则将该元素保存在链头。
hashMap的get方法相对于put方法来说简单一点,首先还是判断key的值是否为空,接下来计算hash值,通过hash值在table数组中定位,最后在链表中进行查找返回。
<span style="font-size:14px;">public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
</span>
HashTable的定义如下:
<span style="font-size:14px;">public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable</span>
Hashtable继承了Dictionary类和实现了Map接口,其中Dictionary是一个抽象类,可以将任意的键值映射到Value值,HashTable的实现和Hashmap的实现和类似。都是基于hash表的。
可以看出,HashTable中也是存在着一个数组,数组中存放的Entry的节点,每一个Entry代表一个键值对。
HashMap和Hashtable存在很多的相同点,但是还是由区别的,首先他们各自的继承类不一样,在HashMap中是允许存在着一个null值,但是在HashTable中是不允许存在着null值得,HashTable的方法是同步的,而HashMap的方法不是。
至于TreeMap的实现是基于红黑树的,内容涉及较多,在这里不叙述了,有时间的话会单独总结出来。
参考文章
Java核心技术卷一
991

被折叠的 条评论
为什么被折叠?



