Java 容器类库:抽象类和遗留构建.
可以看看看源码。比如HashMap这种容器就是看数组的组织形式和查询访问处理过程. 就是看
几个常用接口,比如put,get这些以及构造函数,就是数据初始化的时候都做了什么。
只有四种容器:List, Set, Queue, Map
Collection: List, Set, Queue
Collection接口的定义javainterface Collection {
boolean add(T e);
boolean addAll(Collection extends T> c);
void clear();
boolean contains(Object o);
boolean containsAll(Collection> c);
boolean isEmpty();
Iterator iterator();
boolean remove(Object o);
boolean removeAll(Collection> c);
boolean retainAll(Collection> c);
int size();
Object[] toArray();
T[] toArray(T[] a);
}Collection接口不包括随机访问元素get()方法,因为Collection要包括Set. Set是自己维护内部顺序的(这样随机访问就没有意义了),因此如果想检查Collection中的元素,就要使用迭代器Iteration。
ArrayList来保存数据集,然后向上转型为Collection:Collection c = new ArrayList(); 如果想用List额外的功能,如返回子集,需要转型:Collection c3 = ((List) c).subList(3,5);
通过Collection接口定义的源码可以看出,
3.1 是用模板实现的,适合各种类型,代码复用性更好了。
3.2 所有...All方法接收的参数都是Collection对象。也就是List, Set,Queue,通过向上转型提高了代码的复用性。
3.3 Collection定义了Iterator函数。用于访问容器元素。
List 接口的定义javainterface List extends Collection {
void add(int index, T element);
void add(int index, T element);
boolean addAll(int index, Collection extends T> c);
T get(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator listIterator();
ListIterator listIterator(int index);
T remove(int index);
T set(int index, T element);
List subList(int fromIndex, int toIndex);}
Iterator 接口的定义javainterface Iterator {
boolean hasNext();
T next();
void remove();
}
ListIterator 接口的定义(双向,支持向前和向后遍历,同时支持add和修改)javainterface ListIterator {
void add(T e);
boolean hasPrevious();
int nextIndex();
T previous();
int previousIndex();
void set(T e);
void remove();
}
ArrayList是支持泛型的,它继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。
他仅有的两个私有属性:javaprivate transient Object[] elementData ;
private int size;
具体说来:ArrayList是用数组实现的。ArrayList最重要的特征就是动态数组,即可以动态扩容。这是怎么实现的呢?
主要技术是使用了 elementData = Arrays.copyOf(elementData, newCapacity); 数组的复制。扩容时把原来数组里的所有元素拷贝到新创建的数组中,其中对数组的拷贝与移动大量使用了 System.arraycopy
http://blog.csdn.net/jzhf2012/article/details/8540410
http://blog.csdn.net/crave_shy/article/details/17436773
关于迭代器再说两句:javaprivate class Itr implements Iterator {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
}
Itr 依靠三个int变量实现遍历。cursor是当前位置,第一次调用 next() 将返回索引为 0 的元素。lastRet 记录上一次游标所在位置,因此它总是比 cursor 少 1。
方法 next(): 返回的是索引为 cursor 的元素 ,然后修改 cursor 和 lastRet 的值:javapublic Object next() {
checkForComodification();
try {
Object next = get( cursor);
lastRet = cursor ++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
Set
Set对存储的元素要求是唯一性: 所以要通过equals来确保对象的唯一性。
Set接口的定义javapublic interface Set extends Collection {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator iterator();
Object[] toArray();
T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection> c);
boolean addAll(Collection extends E> c);
boolean retainAll(Collection> c);
boolean removeAll(Collection> c);
void clear();
boolean equals(Object o); //Set 自己独有的
int hashCode(); //Set 自己独有的
}javaclass SetType {
int i ;
public SetType( int n){
i = n;
}
//判断是否相等1:类型相等 2:值相等
public boolean equals(Object o){
return o instanceof SetType && ( i==((SetType)o). i);
}
public String toString(){
return Integer.toString( i);
}
}
HashSet
(适合快速查找)
HashSet 底层是使用 HashmMap 实现的。 当使用 add 方法将对象添加到Set当中时,实际上是将该对象作为底层所维护的 Map 对象的 key,而 value 则都是同一个 Object 对象.javaclass HashType extends SetType{
HashType( int n){
super(n);
}
public int hashcode(){
return this .i ;
}
}
TreeSet
java//默认构造函数。使用该构造函数,TreeSet中的元素按照自然排序进行排列。
TreeSet()
// 创建的TreeSet包含collection
TreeSet(Collection extends E> collection)
// 指定TreeSet的比较器
TreeSet(Comparator super E> comparator)
// 创建的TreeSet包含set
TreeSet(SortedSet set)继承结构:TreeSet->SortedSet->Set(保持了次序:按照元素的插入顺序保存元素,底层是树,元素必须实现Comparable接口,可排序)
SortedSet(接口:按对象的比较函数对元素排序)
TreeSet 中的元素支持 2 种排序方式:自然排序 或者 根据创建 TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。TreeSet实际是TreeMap实现的,
因为TreeSet是Set所以支持add,remove,因为有序,就支持get.
TreeSet 支持的基本操作(add、remove 和contain) 提供受保证的 log(n) 时间开销。
因为执行了NavigableSet,TreeSet拥有了导航方法:
4.1 提供元素项的导航方法,返回某个元素。
4.2 另一类是提供集合的导航方法,返回某个集合。javasubSet(fromElement, toElement )
headSet(toElement)
tailSet(fromElement)javaclass TreeType extends SetType implements Comparable{
TreeType( int n){
super (n);
}
public int compareTo(TreeType arg){
return (arg.i < this. i ? -1 :(arg. i== this .i ?0:1));
}
}
LinkedHashSet
(具有HashSet的查询速度,内部使用链表维护(次序),因此保持了次序:按照元素的插入顺序保存元素)
SortedSet
(接口:按对象的比较函数对元素排序)
Queue:
队列是典型FIFO先进先出容器。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程特别重要,因为他们可以安全地将对象从一个任务传输给另一个任务。他的两个实现LinkedList和PriorityQueue,差异是排序,而非性能
Queue 接口的定义:javainterface Queue
//throws excexption
boolean add(E e);//将元素插入队尾
E remove();//移出返回队头
E element(); //返回队头
//return special value
boolean offer(E e); //将元素插入队尾
E poll();//移出返回队头
E peek();//返回队头
PriorityQueue根据优先级确定下一个移出对列的元素。放入PriorityQueue的元素需要实现Comparable接口,最小的元素会先在队首。
当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序(这依赖具体实现,PriorityQueue通常会在插入时排序(维护一个堆),但是也可能在移除时选择最重要的元素)。默认的排序是自然排序,但是可以通过自己的Comparator来修改这个顺序(优先级)。
PriorityQueue 接口的定义javaboolean add(E e)
void clear()
Comparator super E> comparator()
boolean contains(Object o)
Iterator iterator()
boolean offer(E e)
E peek()
E poll()
boolean remove(Object o)
int size()
Object[] toArray()
T[] toArray(T[] a)
优先级自定义举例:javapublic class ToDoList extends PriorityQueue{
static class ToDoItem implements Comparable{
private char primary;
private int secondary;
private String item;
public ToDoItem(String td, char pri, int sec){
this.primary = pri;
this.secondary = sec;
this.item = td;
}
// 因为类型 E 是自定义的 ToDoItem,为了能够定义优先级 - 排序
// 要 implements Comparable, 定义 compareTo
public int compareTo(ToDoItem arg){
if(this.primary > arg.primary){
return +1;
}
if(this.primary == arg.primary){
if(this.secondary < arg.secondary)
return -1;
else if(this.secondary > arg.secondary)
return 1;
else if(this.secondary == arg.secondary )
return 0;
}
return -1;
}
public String toString(){
return Character.toString(primary)+" "+this.secondary+" "+item;
}
}
public void add(String td, char pri, int sec){
super.add(new ToDoItem(td, pri, sec));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
Map
Map接口的定义javainterface Map {
void clear();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
boolean isEmpty();
Set> entrySet();
Set keySet();
void putAll(Map extends K, ? extends V> m);
V remove(Object key);
int size();
Collection values();
}javainterface Map.Entry {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o); //定义equals方法:判断两个Entry是否相同
int hashCode(); //定义获取hashCode的方法
}
Map 接口有6种实现:
HashMap 下面详细介绍,缺点线程不安全
LinkedHashMap 类似HashMap,迭代遍历时候,取得key-value pair的顺序是它的插入或者是LRU(最近最少使用)次序。
TreeMap 实现了SortedMap, 类似TreeSet的一些方法。红黑树的实现,所得到的结果是经过排序(次序由comparable和comparator决定)的。
WeakHashMap
ConcurrentHashMap 线程安全的Map.
IdentityHashMap
如果不为自己创建的类覆盖hashCode()和equals(),那么使用散列数据结构(HashSet,HashMap, LinkedHashSet,或者LinkedHashMap)就无法正确处理你的键。
HashMap类
HashMap继承自AbstractMap,实现了Map接口(这些内容可以参考《Java集合类》)。来看类的定义。javapublic class HashMap extends AbstractMap implements Map, Cloneable, Serializable
为什么要使用HashMap?
可以使用List作为基本结构,再覆盖hashCode()和equals()就可以实现基本的Map功能。
但是问题是对键的查询,键没有任何特定的顺序保存,所以只能使用线性查询,而线性查询是最慢的查询方式。
所以这里要使用一种叫哈希表(散列)的数据结构。这个DS综合(折中)了数组和链表的优点。
hash对每个要存储的数据进行计算:hash(key.hashCode),得到的哈希值是数组的index.
通过看HashMap源码,可以发现:
1. HashMap底层是用Entry数组实现的,我们放进HashMap的元素,实际上是放进数组中的。
数组名叫table。Entry是Map.Entry的实现。
table 当需要的时候会扩容resize。大小必须是2的幂。
2. [散列表实现机制] 当向 HashMap 中put 一对key-value pair时候,它会根据 key 的 hashCode 值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组中;
如果该位置有对象存在,则顺着此对象存在的链开始寻找(Entry 类有一个 Entry 类型的 next 成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用 equals 方法进行比较,如果此链上的某个对象equals方法比较为 false , 则将该对象放到数组中,然后将数组中该位置以前存在的那个对象链接到此对象后面。
遍历方法javaMap map = new Hashmap();
Iterator it = map.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry = (Map.Entry ) it.next();
Object key = entry.getKey();
Object value = entry.getValue();
}
HashMap使用了散列码hashcode来取代对key的缓慢搜索。hashcode相对唯一,代表对象。
可以实现什么算法?
1. 统计分布,比如统计Random随机数的分布。javafor( i=0;i
int r = rand.nextInt(20); //key
Integer freq = map.get(r);//返回value
if(freq == null){
map.put(r,1);
} else{
map.put(r,freq+1);
}
}
备注:
1. hashCode()
http://www.oschina.net/question/82993_75533 如何正确重写hashcode和equal方法,参考这个文章。
设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会产生一个不同的散列码,相当于产生一个不同的键。
此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值。这很糟糕!因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。所以,应该使用对象内有意义的标识符。
尽量保证使用对象的同一个属性来生成hashCode()和equals()两个方法。在Employee的案例中,我们使用员工id。
eqauls方法必须保证一致(如果对象没有被修改,equals应该返回相同的值).任何时候只要a.equals(b),那么a.hashCode()必须和b.hashCode()相等。两者必须同时重写。查看原文