1:数据结构的定义
以某种形式将数据组合在一起的集合,它不仅有数据还包括提高访问、处理操作
2:java中的数据结构
java集合框架支持以下两种类型的数据结构,一种只存元素的集合,一种是保存键值对的图,java中的类图架构如下
3:java中的集合
java中集合主要包含三种类型:规则集(Set)、线性表(List)和队列(Queue),三者之间的差别在于规则集存储一组不重复的元素,线性表存储由元素构成的有序集合,队列用于存储先进先出的对象。
java集合中所有的接口和类都存在java.util包里面,并且所有的类都实现了Cloneable和Serializable接口,所有,它们的实力都是可以复制并且可序列化的
3.1)根接口Collection和抽象类AbstractCollection
类图如上面所示,AbstractCollection除了size方法和iterator方法没有实现外,其余的全部实现,另外查看它的源码你会发现这个类的方法基本都是通过iterator获取到iterator,然后进行遍历,但是就是add方法有点特别,直接抛出一个异常
看到这里个人觉得不太理解,add方法的确是要分不同的子类来实现的,但是为什么不直接把它作为一个抽象方法在AbstractCollection不实现了,反正都是子类进行实现的,何苦再这里直接抛出异常,书上给出的解释是“collection接口中有些方法是不能再子类直接实现的”,看到这部分的解释,我个人的理解的解释就是觉得知只是在父类的部分进行一个异常抛出,如果子类是可以实现此方法的就自己进行重写覆盖,而不能实现此方法的就直接抛出异常,这样的话就可以既保持了继承思想的好处,又可以进行特殊化的处理(抛出异常)
3.2)规则集
规则集的类图接口如下图所示
Set接口扩展了Collection接口,没有引入任何的方法和常量,仔细观察下图,你会发现它们俩压根就是一个模子刻出来的
但是这里存在一个问题,为什么Set是继承Collection的,而它们俩接口里面的东西都是一样的,但是为什么在源码中Set接口依旧将Collection中的所有属性和方法都重新定义了一次,按说已经继承了,这不是多余的吗?这是个问题,在此记录一下
AbsyractSet它拓展了AbstractCollection并且实现了Set接口,看类图
它是实现了equals、hashCode以及removeAll方法,重写equals、hashCode并不稀奇,但是让我奇怪的是为什么还要重写removeAll方法,查看源码进行比较
原来是进行了一次判断,AbstractSet这部分就是为了减少循环次数而进行不同的处理了,如果c的长度比本身Set的长度大的话,我们就循环Set的长度,反之,我们就循环c,但是我觉得这个方法可以在AbstractCollection这部分就可以做啊 为什么还要放在子类然后进行重写呢?按理说,所有的线性表、规则集、队列都可以用这种判断啊,但是单单就AbstractSet用了这部分 ,这又是个问题。。。
3.2.1)Set的第一个实现类(HashSet)散列集HashSet
查看HashSet的源码,你会发现HashSet其实内部是通过HashMap来进行实现的,之后再看HashMap来进行看它如何实现去除重复,在hashset添加的对象并没有顺序,因为散列集中的元素师没有特定顺序的,如果需要有顺序,则可以使用LinkedHashSet。
3.2.2)Set的第二个实现类(LinkedHashSet)链式散列集LinkedHashSetHashSet
LinkedHashSetHashSet继承自HashSet,并且它的内部实现的是通过LinkedHashMap来实现的,关于它的实现原理待会看LinkedHashMap再说,LinkedHashMap保持了元素插入时的顺序,但是如果需要不同的顺序(升序、降序等等),可以使用TreeSet,我们可以单纯的理解为LinkedHashSet只是在HashSet的基础上加上了一个按照插入顺序的特性,如果不需要维护被插入的顺序,就应该使用HashSet,它明显比LinkedHashSet快,毕竟它不用比较
3.2.3)Set的第三个实现类(TreeSet)树形集TreeSet
要说明treeSet,我们就需要了解SortedSet,SortedSet是Set的一个子接口,我们来看下它的类图
通过指定实现类必须实现这个方法来进行自定义Set的排序,而且提供了一些另外的方法,如上图所示。注意上述的红色笔记
NavigableSet继承了SortedSet,观察NavigableSet的类图,它额外的提供了一些方法,但是这些方法得注意一下,不然会出错的
Lower(E)方法:返回小于传入参数的最大值,它和SortedSet中定义的headSet(E)的区别在于它返回的是一个元素,而headSet返回的是NavigableSet
Floor(E)方法:返回小于或等于传入参数的最大值
Ceiling(E)方法:返回大于或等于传入参数的最小值
Higher(E)方法:返回大于传入参数的最小值
PollFirst()方法:返回并删除第一个元素,它和SortedSet中定义的first()方法区别在于删除与否
PollLast()方法:同上
这些方法咋乍一看还比较混淆,所以留下做个比较
在自定义排序的部分,Set提供了支持两种形式,一种是Comparable接口,一种是Comparator接口,区别在于Comparable是比较实现了Comparable接口的类的对象,而 Comparator是比较没有实现Comparable接口的对象,在TreeSet源码实现中,它会判断Comparator对象是否存在,如果存在就是利用比较器,不存在的话就使用Comparable比较
规则集部分总结
1)我们可以知道LinkedHashSet在HashSet的基础上加了按照插入顺序排序,而TreeSet是在LinkedHashSet的基础上加上了自定义排序(这个只是在效果上来看是这样的啊,并不是指实现上)。
2)规则集都是在Map的基础上来实现的,具体来看源代码
1)规则集内部实现是通过Map,而Map是可以存在null为键的(但是只有一个),所以Set也可以存入null
2)关于Set中如何判定是否重复呢?这个需要查阅资料,再继续记录
3.3)线性表3.3.1)线性表设计结构如下图
3.3.2)线性表和Set的区别
1)Set只能存不重复的元素,而线性表可以
2)线性表还可以制定用户存储的位置,增加了面向位置的操作
至于说List接口拓展Collection接口的一些方法,就不再赘述了,特别一点的就是iterator方法返回的是Iterator的子类listiterator,这个接口可以向前遍历和向后遍历。
List的实现类有三个:ArrayList、LinkedList、Verctor以及Stack,AbstractList实现了一部分List的方法
1)ArrayList实现了List接口中的方法,内部实现是通过数组来实现的,当达到一定容量的时候会自动创建一个更大的数组,然后就小数组的数组进行copy
2)LinkedList是通过每个节点对象来实现的,就是定义一个节点结构体,结构体存在属性指向上一个节点和下一个节点。
3)Vector向量类也是实现了List接口,它和ArrayList的区别在于它加上了一些访问和修改的同步方法
4)Statck是一种先进后出的结构,继承自Vector
Push:入栈 Pop:出栈并删除 Peek:出栈
总结:如果不需要再线性表中插入元素,则数组是最有效的数据结构,如果需要在任意位置插入、删除元素 则lingkedList则更有效,在末尾添加、删除元素则ArrayList更高效。
3.3.2) 队列:队列是一种先进先出的结构,在优先队列中,元素被赋予优先级(又是Comaprable和Comparator两种方式)
Deque接口继承自Queue,就是所谓的双端队列,我想我们还记得LinkedList,其实它就是一种Deque,也是实现了Queue,这种双向列表实现这个将是更轻松的,在我看来,双向列表最主要的就是要多实现descendingIterator方法,就是反向的一个iterator
数组实现:
结构体实现:
至于说优先队列,就只是在队列的基础上加上了排序。
3.3.4)图:图是一种键/值储存元素的容器,并且途中不包含重复的键值(Set就是利用这个性质来实现的),在一个map中可以有一个null的键,但是TreeSet没有
AbstractMap是一个抽象类,它实现了除了entrySet之外的所有方法,这个和AbstractSet类似
SortedMap是一个接口,它拓展了Map接口,并保持了映射以键值升序的顺序排列,它附加了方法firstKey、lastKey、headKey和tailkey方法,这个是不是很熟悉,这个的结构和Set是一样的,仔细观察其实Set和Map的总体设计结构差不多的,它和SortedSet类似
NavigableMap拓展了SortedMap接口,并提供了返回部分图的方法(就是小于某个键的图等等,但是多提供了类似小于某个键值的部分图这些),它的方法设计类似NavigableSet
Map的三个主要实现类比较
HashMap不存在顺序,所以就只保存了下一个节点,实现上用了数组和链式结构,它的结构是这样的
LinkedHashMap需要动态改变顺序,所以每个节点保存了上一个和下一个节点,实现上和HashMap差不多
实现来看源码:
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
void recordRemoval(HashMap<K,V> m) {
remove();
}
}
这个就是节点的结构体了,我们可以看到它有个recordAccess方法,这个方法的作用就是在当我们创建了Map的时候如果指定了访问顺序,则访问一次就调用这个方法,,在调用recordAccess的时候就把访问的节点插入到header的前面一个元素位置,header是数组为i处链表的头部
TreeMap以树形结构来实现,所以保存了父节点、左节点以及右节点
TreeMap的实现就是二叉树的实现了,查看源代码
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
// TBD:
// 5045147: (coll) Adding null to an empty TreeSet should
// throw NullPointerException
//
// compare(key, key); // type check
root = new Entry<K,V>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<K,V>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e); //这个没看懂
size++;
modCount++;
return null;
}
HashTable:它也是实现了Map接口,和hashMap不一样的在于它具有同步功能,所有对外方法(Public)都加上了synchronized,这一和Vector类似,但是它和Vector不 同在于它在contains方法上也加上了synchronized,而Vector并没有加上(这点很奇怪)
总结:关于Map如何去重复(如何判断这个还得查阅资料),如果判断是重复的,则进行修改而不是添加。