数据有逻辑结构和存储结构,逻辑结构又有四种:list,tree,set,graph。
存储结构有顺序印象和非顺序印象,因此有顺序存储结构和链式存储结构。
java JDK为常用的数据结构定义了一些Interface和Implementation。这些接口、实现类以及常用的排序、查找等算法成为Java Collection框架(Java Collection Framework)。
Collection接口定义了其上的3类操作:针对每个元素的基本操作、迭代器和Collection对象之间的批量操作。
接口Collection的子接口有Set和List。
集合(set)在Collection的基础之上增加了“不允许重复元素“的约束;
而List则在其上增加了”元素之间具有前弱、后继关系“的约束;除了第一个元素外,所有元素具有唯一的前驱;除了最有一个元素外,所有的元素具有唯一的后继。
java为集合接口提供了两个基本的实现:HashSet和Tree。
HashSet实现类按哈希算法来存储集合中的元素,查找性能较好但不能记忆元素之间的顺序,包括插入顺序。其子类LinkedHashSet也是根据元素hashCode(hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值)值来决定元素存储位置,但他同时使用链表维护元素的次序。
(set中不能存储相同的对象,这里相同的判断依据是equals方法,数据存入时,直接散列成哈希码,对象相同时,哈希码相同。)
TreeSet可以确保集合元素处于排序状态,根据实际值排序。TreeSet采用红黑树(较平衡)的数据结构排序,要求添加进TreeSet中的对象必须实现CompareTo接口。
List接口可以根据元素位置索引(index)来插入、替换、删除集合元素以及查找指定对象的位置。
***ArrayList实现类基于数组实现了 List接口,其内部封装了一个动态再分配的数组。每个数组对象都有capacity表示数组长度的自增长属性,默认为10。
LinkedList内部以链表来保存集合中的元素,因此随机访问容器性能较差,但在插入、删除元素时性能较好。*
Queue接口用于定义队列(先进后出)这种数据结构,直接继承了Collection接口。如果使用具有固定容量的队列,则应使用offer()和pool()存取元素,因为add()和remove()方法在因容器原因失败时抛出异常长。如果只是访问队首而不移出该元素,使用element()或者peek()方法。
LinkedList类实现了Queue接口,因此次我们可以把LinkedList当成了Queue来用。
PriorityQueue按大小记忆队列元素顺序,因此调用peek和pool方法直接取最小的元素。
Stack实现了List接口,提供了push和pop操作限制线性表中元素的插入和删除只能在线性表的同一端进行。
JDK 1.6 引入的 ArrayDequ 实现类被优先推荐作为栈使用。ArrayDeque 实现了 Deque 接 口。Deque 接口定义了双端队列,双端队列中的
元素可以从两端弹出,其限定定插入和删除操作在
表的两端进行。
知识点之间的衔接关系
Map 是一种典型名值对类型,它提供一种 Key-Value 对应保存的数据结构。客户程序通过 Key 值来访问对应的
Value,这个接口并没有继承 Collection 这接口;而其他的类或者接口,不管是 List、 Set、 Stack 等都继承或实现了
Collection。
TreeMap 和 HashMap算是 Java 集合类里面比较有难度的数据结构。
HashMap 元素存取的时间复杂度一般是 O(1).
TreeMap 内部对元素的操作复杂度为 O(logn)。
HashSet 的实现是基于 HashMap 的, 所 以 研 究HashSet 就要研究 HashMap。 TreeMap
记忆了顺序,TreeSet 内部的实现使用了 TreeMap。
HashMap可以看作三个视图:key的Set,value的Collection,Entry的Set。
(这里HashSet就是其实就是HashMap的一个视图。)
将对象放入到HashMap或HashSet中时,有两个方法需要特别关心:hashCode()和equals()。hashCode()方法决定了对象会被放到哪个bucket里,当多个对象的哈希值冲突时,equals()方法决定了这些对象是否是“同一个对象”。所以,如果要将自定义的对象放入到HashMap或HashSet中,需要@Override hashCode()和equals()方法。
//getEntry()方法
final Entry getEntry(Object key) {
......
int hash = (key == null) ? 0 : hash(key);
for (Entry e = table[hash&(table.length-1)];//得到冲突链表
e != null; e = e.next) {//依次遍历冲突链表中的每个entry
Object k;
//依据equals()方法判断是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
前面已经说过HashSet是对HashMap的简单包装,对HashSet的函数调用都会转换成合适的HashMap方法,因此HashSet的实现非常简单,只有不到300行代码。这里不再赘述。
//HashSet是对HashMap的简单包装
public class HashSet
{
......
private transient HashMap map;//HashSet里面有一个HashMap
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
......
public boolean add(E e) {//简单的方法转换
return map.put(e, PRESENT)==null;
}
......
}
。
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry left = null;
Entry right = null;
Entry parent;
boolean color = BLACK;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
// ... Ignored
}