首先java中的集合从存储数据上来说分为2种。一种是存放单个值的,另外一种是存放键值对的。
存放单个值的上级接口是Collection接口。同时jdk提供了一个对于集合操作的辅助类Collections。Collection暴露了一些简单的接口。
如
boolean add(E e);
boolean remove(Object o);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
Object[] toArray();
Iterator<E> iterator();
boolean contains(Object o);
boolean isEmpty();
int size();
接着在看看基于双向链表结构的LinkedList类
特点:每个节点都知道前一个节点和后一个节点
优点:对于插入时只需要修改插入节点的引用和对应节点的引用即可,因此插入效率比基于数组的ArrayList高(ArrayList需要先进行插入元素的位置寻找,然后再进行数组元素的移动,最后插入元素(如果数组长度不够大还需要数组大小的扩展))。
缺点:查找效率较低,需要从header进行查找遍历,而ArrayList或是根据数组下标或是根据元素(以为根据元素的会使用hash算法所以效率相对较高)
下面看看LinkedList类的常用方法以及实现原理
public LinkedList() {
header.next = header.previous = header;
}
首先发现该类的默认的构造方法,是将内部定义的一个Entry<E> header,将前节点的引用和后节点的引用都指向自身。
1.public boolean add(E e)
内部的实现代码如下
public boolean add(E e) {
addBefore(e, header);
return true;
}
即添加时每次都是将第二个参数传入header。header就有点像织毛衣的针一样
再看看addBefore的代码的实现
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
//解释:将新添加的节点的next指向header节点。将新添加元素的previous指向header的previous。然后再将新添加元素的previous的next指向新添加元素。再将新添加元素的next的previous指向新添加元素(实际上就是header的previous指向新添加元素)
最后让大小加一,返回新添加的entry对象
2.public boolean remove(Object o)方法
//再看看删除操作的实现原理
public boolean remove(Object o) {
if (o==null) {
for(Entry<E> e = header.next; e != header; e = e.next){
if (e.element==null) {
remove(e);
return true;
}
}
} else {
for(Entry<E> e = header.next; e != header; e =e.next){
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
//因为链表实际上是通过Entry链接到一起组成的链表,因此查找时需要从第一个Entry开始查找,即header的next。当找到指定的entry,这进行节点的移除
相关代码如下:
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
即让当前节点的previous的next指向当前节点的next,当前节点的next的previous指向当前节点的previous
header节点的内容节点和previous和next均为空
常用方法
public void addLast(E e)
其实和add是一样的
再看看里面的clone方法
public Object clone() {
LinkedList<E> clone = null;
try {
clone = (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
// Put clone into "virgin" state
clone.header = new Entry<E>(null, null, null);
clone.header.next = clone.header.previous = clone.header;
clone.size = 0;
clone.modCount = 0;
// Initialize clone with our elements
for (Entry<E> e = header.next; e != header; e = e.next)
clone.add(e.element);
return clone;
}
为了实现深度克隆,该类实现了Cloneable接口。并重写了clone方法
之所以重写是为了让该对象支持深层次克隆
要想让对象完全支持深度克隆需要对象内部的复杂成员类型也支持深度克隆,否则修改克隆对象的对象成员变量会让原来的对象受到影响。如果原来元素不支持可以像上面重新方法一样,新创建成员变量,并将原来成员变量的值传入新创建的成员变量当中。
存放单个值的上级接口是Collection接口。同时jdk提供了一个对于集合操作的辅助类Collections。Collection暴露了一些简单的接口。
如
boolean add(E e);
boolean remove(Object o);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
Object[] toArray();
Iterator<E> iterator();
boolean contains(Object o);
boolean isEmpty();
int size();
接着在看看基于双向链表结构的LinkedList类
特点:每个节点都知道前一个节点和后一个节点
优点:对于插入时只需要修改插入节点的引用和对应节点的引用即可,因此插入效率比基于数组的ArrayList高(ArrayList需要先进行插入元素的位置寻找,然后再进行数组元素的移动,最后插入元素(如果数组长度不够大还需要数组大小的扩展))。
缺点:查找效率较低,需要从header进行查找遍历,而ArrayList或是根据数组下标或是根据元素(以为根据元素的会使用hash算法所以效率相对较高)
下面看看LinkedList类的常用方法以及实现原理
public LinkedList() {
header.next = header.previous = header;
}
首先发现该类的默认的构造方法,是将内部定义的一个Entry<E> header,将前节点的引用和后节点的引用都指向自身。
1.public boolean add(E e)
内部的实现代码如下
public boolean add(E e) {
addBefore(e, header);
return true;
}
即添加时每次都是将第二个参数传入header。header就有点像织毛衣的针一样
再看看addBefore的代码的实现
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
//解释:将新添加的节点的next指向header节点。将新添加元素的previous指向header的previous。然后再将新添加元素的previous的next指向新添加元素。再将新添加元素的next的previous指向新添加元素(实际上就是header的previous指向新添加元素)
最后让大小加一,返回新添加的entry对象
2.public boolean remove(Object o)方法
//再看看删除操作的实现原理
public boolean remove(Object o) {
if (o==null) {
for(Entry<E> e = header.next; e != header; e = e.next){
if (e.element==null) {
remove(e);
return true;
}
}
} else {
for(Entry<E> e = header.next; e != header; e =e.next){
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
//因为链表实际上是通过Entry链接到一起组成的链表,因此查找时需要从第一个Entry开始查找,即header的next。当找到指定的entry,这进行节点的移除
相关代码如下:
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
即让当前节点的previous的next指向当前节点的next,当前节点的next的previous指向当前节点的previous
header节点的内容节点和previous和next均为空
常用方法
public void addLast(E e)
其实和add是一样的
再看看里面的clone方法
public Object clone() {
LinkedList<E> clone = null;
try {
clone = (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
// Put clone into "virgin" state
clone.header = new Entry<E>(null, null, null);
clone.header.next = clone.header.previous = clone.header;
clone.size = 0;
clone.modCount = 0;
// Initialize clone with our elements
for (Entry<E> e = header.next; e != header; e = e.next)
clone.add(e.element);
return clone;
}
为了实现深度克隆,该类实现了Cloneable接口。并重写了clone方法
之所以重写是为了让该对象支持深层次克隆
要想让对象完全支持深度克隆需要对象内部的复杂成员类型也支持深度克隆,否则修改克隆对象的对象成员变量会让原来的对象受到影响。如果原来元素不支持可以像上面重新方法一样,新创建成员变量,并将原来成员变量的值传入新创建的成员变量当中。