抽象数据类型
抽象数据类型(abstract data type,ADT),表示带有一些操作的对象集合。这是一种数学抽象。对于操作的具体实现,ADT不做任何解释。比如表、集合、图都有各自的ADT,对于图ADT,可以有增加、删除、包含这样的操作,又或者有取并集,取交集,查找这样的操作,至于有哪些操作,具体如何实现则不关心。
表ADT
将形如 A 0 , A 1 , A 2 , A 3 , … … , A N − 1 A_0,A_1,A_2,A_3,……,A_{N-1} A0,A1,A2,A3,……,AN−1,N个对象线性结合称为表。大小为N,如果大小为0,我们称之为空表。java中我们熟知有两种表ArrayList和LinkedList,他们都是表ADT的具体实现。
数组表
ArrayList即为数组表。它是由简单的固定大小的数组构成我们都知道数组是需要内存连续的,当数组的大小不足够时,需要重新扩容,一般会按照数组长度new一个新的数组,然后将原先数组中的对象进行拷贝。数组表进行中间插入时,需要将后面的对象通通向后移动一位,同样删除时有需要被删除对象之后的对象通通向前移动一位,所以数组表的插入和删除的性能比较差,最坏的情况下为 O ( N ) O(N) O(N)。而访问数组表则性能就好,如果已知对象在表中的位置,那访问该对象则是常数时间 O ( 1 ) O(1) O(1)。
链表
LinkedList即为链表。为了避免数组表这种高开销的插入与删除,我们需要在不连续的内存中进行存储。链表通过每一个对象保存一个对下一个对象的引用实现连续对象在不连续内存上存储:
链表在插入和删除操作上优于数组表,他不需要将插入点以后的对象整体移动, 只需要修改插入点前后的引用即可,但是查询上性能较低,如果已经对象在表中中的位置,需要依次遍历链表,获取到其中的值,这是由于内存上的不连续,造成无法通过内存地址的偏移量直接获取到指定的对象。
上面这种只记录记录后驱节点引用的链表称为单向链表,如果一个节点同时记录前驱和后驱节点的引用,这种链表称为双向链表:
由于同时存在前后驱节点的引用,所以增加和删除时增加了常数级的时间消耗,但是方便了查询操作,让链表可进可退,单向链表只能前进不能后退。我们java中的LinkedList是双向链表。
Iterator
Iterator迭代器是java中提供的一个接口:
public interface Iterator<E> {
E next();
boolean hasNext();
default void remove()
}
其中包含next、hasNext、remove三个方法,next获取表中的下一个对象;hasNext判断下一个对象是否存在;remove用于删除当前对象,如果remove执行过之后不能重复执行:
Iterator iterator = list.iterator();
iterator.next();
iterator.remove();
iterator.remove();
Exception in thread “main” java.lang.IllegalStateException
at java.util.ArrayList$Itr.remove(ArrayList.java:872)
at com.huifu.test.TestYuHao.main(TestYuHao.java:30)
第二次执行时会抛出异常,直到iterator再次执行next以后才能再执行remove。大家熟知的java的for循环有两种:for(E e:list){}和for(int i=0;i<list.size();i++),第一种写法本质就是利用的iterator实现,在ArrayList中由于get方法可以利用内存偏移量直接获取到对象,时间消耗为常数,两种循环性能差距不大:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
list.add(i);
}
long startTime = System.currentTimeMillis();
for (Integer integer : list) {
System.out.println(integer);
}
long endTime = System.currentTimeMillis();
long startTime2 = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
long endTime2 = System.currentTimeMillis();
System.out.println("time1="+(endTime-startTime));
System.out.println("time1="+(endTime2-startTime2));
}
time1=9402
time2=9792
但是如果是LinkedList就大不一样,LinkedList每一次调用get方法,时间消耗都是线性时间,再加上外部一层循环,整体时间消耗为 O ( N 2 ) O(N^2) O(N2),而基于iterator的第一种循环方式,时间消耗为 O ( N ) O(N) O(N):
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
long startTime = System.currentTimeMillis();
for (Integer integer : list) {
System.out.println(integer);
}
long endTime = System.currentTimeMillis();
long startTime2 = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
long endTime2 = System.currentTimeMillis();
System.out.println("time1="+(endTime-startTime));
System.out.println("time2="+(endTime2-startTime2));
}
time1=1891
time2=7834
本来也想用一百万量级,但是发现第二种循环所需要的时间超出了我等待的容忍程度了,所以改成了十万级,可以看到第二种循环耗时近八秒,而迭代器耗时不到两秒。需要注意的是,iterator在使用过程中是不允许修改表的结构的:
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
for (int i = 0; i < 1000000; i++) {
list.add(i);
}
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
((LinkedList<Integer>) list).remove(0);
}).start();
for (Integer integer : list) {
System.out.println(integer);
}
}
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:966)
at java.util.LinkedList$ListItr.next(LinkedList.java:888)
at com.huifu.test.TestYuHao.main(TestYuHao.java:21)
当迭代器使用过程中,表结构发生变化,会抛出异常。原因是为了避免如下两种场景:当迭代器准备将某一项作为下一项时,刚好这一项被删除了,或者新的一项被插入到了两项之间。而如果你使用第二种循环方式,你可以随意修改表的结构。
手写ArrayList
package com.huifu.test.datastructor;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyArrayList<AnyType> {
private static final int DEFAULT_CAPACITY = 10;
private int theSize;
private AnyType[] theItems;
public MyArrayList() {
clear();
}
public void clear() {
theSize = 0;
ensureCapacity(DEFAULT_CAPACITY);
}
/**
* 使数组长度和存储对象的数量一致
*/
public void trimToSize() {
ensureCapacity(size());
}
/**
* 扩容
*
* @param newCapacity
*/
public void ensureCapacity(int newCapacity) {
if (newCapacity < theSize) {
return;
}
AnyType[] old = theItems;
theItems = (AnyType[]) new Object[newCapacity];
for (int i = 0; i < size(); i++) {
theItems[i] = old[i];
}
}
/**
* 判断是否为空
*
* @return
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* 获取大小
*
* @return
*/
public int size() {
return theSize;
}
/**
* 获取对象
*
* @param idx
* @return
*/
public AnyType get(int idx) {
if (idx < 0 || idx >= size()) {
throw new ArrayIndexOutOfBoundsException();
}
return theItems[idx];
}
/**
* 末尾追加对象
*
* @param obj
*/
public void add(AnyType obj) {
add(obj, size());
}
/**
* 指定位置添加对象
*
* @param obj
* @param idx
*/
public void add(AnyType obj, int idx) {
if (size() == theItems.length) {
ensureCapacity(size() * 2 + 1);
}
for (int i = size(); i > idx; i--) {
theItems[i] = theItems[i - 1];
}
theItems[idx] = obj;
theSize++;
}
/**
* 修改指定位置的对象
*
* @param newVal
* @param idx
* @return
*/
public AnyType set(AnyType newVal, int idx) {
if (idx < 0 || idx >= size()) {
throw new ArrayIndexOutOfBoundsException();
}
AnyType old = theItems[idx];
theItems[idx] = newVal;
return old;
}
/**
* 删除指定对象
*
* @param idx
* @return
*/
public AnyType remove(int idx) {
AnyType removeObj = theItems[idx];
for (int i = idx; i < size() - 1; i++) {
theItems[i] = theItems[i + 1];
}
theSize--;
return removeObj;
}
public static void main(String[] args) {
new MyArrayList<String>();
}
public Iterator<AnyType> iterator() {
return new MyArrayListIterator<>();
}
private class MyArrayListIterator<AnyType> implements Iterator<AnyType> {
private int current = 0;
@Override
public boolean hasNext() {
return current < size();
}
@Override
public AnyType next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return (AnyType) theItems[current++];
}
@Override
public void remove() {
MyArrayList.this.remove(--current);
}
}
}
MyArrayList中为了实现迭代器iterator,引入了内部类机制。所谓内部类是嵌套类的一种,嵌套类是在一个类中再写一个类,嵌套类分为静态嵌套类,形如:
class A{
static class B{
}
}
和非静态嵌套类又叫内部类:
class A{
class B{
}
}
两者在代码上只有一个static的区别,但是内部类是可以直接使用外部内中的属性、方法的,即使是私有属性和私有方法,而静态类嵌套类只能够访问外部类的静态属性和方法,内部类还是java实现多继承的一种方式。这里要使用内部类,而不能使用嵌套静态类的原因就是因为MyArrayListIterator这个内部类可以访问MyArrayList中的私有对象和私有方法。当然,也可以通过修改MyArrayList中的属性和方法的可见性,但是一定程度上违背了面向对象编程:让数据尽可能隐蔽的原则。
手写LinkedList
package com.huifu.test.datastructor;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyLinkedList<AnyType> {
private int theSize;
private int modCount = 0;
private Node<AnyType> beginMarker;
private Node<AnyType> endMarker;
public AnyType remove(int idx) {
return remove(getNode(idx));
}
private AnyType remove(Node<AnyType> p) {
p.prev.next = p.next;
p.next.prev = p.prev;
theSize--;
modCount++;
return p.data;
}
/**
* 在末尾追加
*
* @param data
*/
public void add(AnyType data) {
add(size(), data);
}
/**
* 修改指定位置的值
*
* @param idx
* @param x
* @return
*/
public AnyType set(int idx, AnyType x) {
Node<AnyType> node = getNode(idx);
AnyType old = node.data;
node.data = x;
return old;
}
public AnyType get(int idx) {
return getNode(idx).data;
}
/**
* 在指定为插入
*
* @param idx
* @param data
*/
public void add(int idx, AnyType data) {
addBefore(getNode(idx), data);
}
/**
* 判断是否为空
*
* @return
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* 获取节点
*
* @param idx
* @return
*/
public Node<AnyType> getNode(int idx) {
Node<AnyType> p;
if (idx < 0 || idx > size()) {
throw new IndexOutOfBoundsException();
}
if (idx < size() / 2) {
p = beginMarker.next;
for (int i = 0; i < idx; i++) {
p = p.next;
}
} else {
p = endMarker.prev;
for (int i = size(); i > idx; i--) {
p = p.prev;
}
}
return p;
}
/**
* 在前面添加节点
*
* @param p
* @param x
*/
public void addBefore(Node<AnyType> p, AnyType x) {
Node<AnyType> newNode = new Node<>(x, p.prev, p);
p.prev.next = newNode;
p.prev = newNode;
theSize++;
modCount++;
}
/**
* 获取长度
*
* @return
*/
public int size() {
return theSize;
}
/**
* 清空链表
*/
public void clear() {
beginMarker = new Node<AnyType>(null, null, null);
endMarker = new Node<AnyType>(null, null, null);
beginMarker.next = endMarker;
theSize = 0;
modCount++;
}
public Iterator<AnyType> iterator() {
return new MyLinkedListIterator();
}
private class MyLinkedListIterator implements Iterator<AnyType> {
private Node<AnyType> current = beginMarker.next;
private int exceptedModCount = modCount;
private boolean okToRemove = false;
@Override
public boolean hasNext() {
return current != endMarker;
}
@Override
public AnyType next() {
if (modCount != exceptedModCount) {
throw new ConcurrentModificationException();
}
if (!hasNext()) {
throw new NoSuchElementException();
}
AnyType nextItem = current.data;
current = current.next;
okToRemove = true;
return nextItem;
}
@Override
public void remove() {
if (modCount != exceptedModCount) {
throw new ConcurrentModificationException();
}
if (!okToRemove) {
throw new IllegalStateException();
}
MyLinkedList.this.remove(current.prev);
}
}
private static class Node<AnyType> {
public Node(AnyType data, Node<AnyType> prev, Node<AnyType> next) {
this.data = data;
this.prev = prev;
this.next = next;
}
private AnyType data;
private Node<AnyType> prev;
private Node<AnyType> next;
}
}