一、设计思路
1、设计结构图
2、接口代码
public interface List<E> {
public static final int ELEMENT_NOT_FOUND = -1; //接口里面默认的成员变量就是public static final
int size();
boolean isEmpty();
boolean contains(E element);
void clear();
void rangeCheck(int index);
void rangeCheckForAdd(int index);
void add(E element);
void add(int index, E element);
void remove(int index);
E set(int index, E element);
E get(int index);
int indexOf(E element);
String toString();
}
3、抽象类代码
通过一个实现接口的抽象类服复用代码!
public abstract class AbstractList<E> implements List<E> {
protected int size = 0;
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean contains(E element) {
return indexOf(element) == ELEMENT_NOT_FOUND ? false : true;
}
@Override
public void rangeCheck(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("动态数组越界");
}
}
@Override
public void rangeCheckForAdd(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("动态数组添加数据时越界");
}
}
@Override
public void add(E element) {
add(size, element);
}
}
二、ArrayList
1、代码
按照jdk的源码设计。实现一些简单接口。
基本类型及基本数据类型包装类内存图:
数组new直接将数据放在新申请的堆空间上面,而且每一种基本数据类型都有各自的缺省值。基本数据类型包装类的缺省值为null。
引用类型数组内存图:
对象数组刚new出来的时候其实只是在堆空间上申请一段都是null的空间。当在数组对应位置new对象的时候,这个位置上会存放指向对象的内存地址。
ArrayList实现比较简单,就是数组。空间不够(已申请的空间不够存放新增的数据)就动态扩容,位运算扩容为原来容量的1.5倍。空间冗余多(已存放的数据小于已申请空间的一而半且申请空间的大小比默认值来得大)就动态缩容,缩为原来的一半。注:扩容的倍数和缩容倍数不要互为倒数,不然很可能会出现复杂度振荡的情况。
插入数据的时候从后往前挪;删除数据的时候从前往后挪。
优点:查询速度快。
缺点:添加和删除数据的时候牵一发而动全身,还要考虑动态扩容。
public class ArrayList<E> extends AbstractList<E> {
private E[] elements;
private final int DEFAULT_CAPACITY = 2;
public ArrayList(int capacity) {
elements = (E[]) new Object[capacity];
}
public ArrayList() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
@Override
public void clear() {
//还需要释放栈中内存地址数组指向的对象空间
for (int i = 0; i < size; i++)
elements[i] = null;
this.size = 0;
// elements=null;//直接把内存地址数组也释放了, 就无法重复利用地址数组
}
/**
* 动态扩容
*
* @param capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容为1.5倍
if (capacity > oldCapacity) {
//位运算比浮点高效
E[] newArray = (E[]) new Object[newCapacity];
//拷贝数据
for (int i = 0; i < size; i++) {
newArray[i] = elements[i];
}
elements = newArray;
System.out.println(oldCapacity + "扩容为" + newCapacity);
}
}
/**
* 动态缩容
*/
private void trim() {
int oldCapacity = elements.length;
int newCapacity = oldCapacity >> 1;
if (this.size >= newCapacity || oldCapacity <= DEFAULT_CAPACITY)
return;
E[] newArray = (E[]) new Object[newCapacity];
//拷贝数据
for (int i = 0; i < size; i++) {
newArray[i] = elements[i];
}
elements = newArray;
System.out.println(oldCapacity + "缩容为" + newCapacity);
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacity(size + 1);
for (int i = size - 1; i >= index; i--) {
elements[i + 1] = elements[i];
}
elements[index] = element;
size++;
}
public void remove(int index) {
rangeCheck(index);
trim();
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
size--;
elements[size] = null; //清空内存地址数组最后一块指向的对象空间
}
public E set(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
public E get(int index) {
rangeCheck(index);
return elements[index];
}
public int indexOf(E element) {
for (int i = 0; i < size; i++) {
if (elements[i].equals(element))
return i;
}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("size=").append(size);
if (size != 0) {
str.append(", [").append(elements[0]);
for (int i = 1; i < size; i++) {
str.append(",").append(elements[i]);
}
str.append("]");
}
return str.toString();
}
public static void main(String[] args) {
List<Object> list = new ArrayList<>(); //多态, 成员方法编译看左边, 运行看右边
list.add(new Person(23, "小杰"));
list.add(0, 66);
list.add(99.9);
list.add(list.size(), 100);
list.remove(0);
list.remove(list.size() - 1);
list.set(1, 666);
System.out.println(list.get(0));
System.out.println(list);
}
}
2、测试结果
三、LinkedList
按照jdk的源码设计,其实LinkedList底层是双向链表实现的。
链表是链式存储,所有结点间的地址不一定连续。
本篇文章为了练习链表,写了三个版本的仿java源码的LinkedList。分别是不带头结点的单向链表,带虚拟头结点的单向链表,双向链表。
1、不带头结点的单向链表明细
LinkedList就是链表,实现主要还是遍历,挨个找。
插入和删除的时候都要找到前一个结点。增删的时间复杂度都是O(n),因为都需要遍历查询,单单处理数据的时候时间复杂度才是O(1)。
优点:添加和删除数据的时候只需改变个别结点,而且不需要动态扩容,插入和删除需要对头部进行单独处理。
缺点:查询慢,需要挨个遍历。
public class LinkedList<E> extends AbstractList<E> {
public Node<E> head;
public static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
@Override
public void clear() {
this.size = 0;
head = null; //清除所有结点的内存空间
}
@Override
public void add(int index, E element) {
//链表注意头和尾
if (index == 0) { //头部插入
Node<E> newNode = new Node<E>(element, head);
head = newNode;
//head = new Node<E>(element, head); //好看的代码
} else { //普通情况, 尾部插入也适用
rangeCheckForAdd(index);
Node<E> preNode = getNode(index - 1);
Node<E> newNode = new Node<E>(element, preNode.next);
preNode.next = newNode;
//preNode.next = new Node<E>(element, preNode.next); //好看的代码
}
size++;
}
@Override
public void remove(int index) {
rangeCheck(index);
//链表注意头和尾
if (index == 0) { //头部删除
head = head.next;
//Node<E> preNode = head; preNode = preNode.next; head = preNode; //等价写法, head和preNode只是变量名
} else { //普通情况, 尾部删除也适用
rangeCheck(index);
Node<E> preNode = getNode(index - 1);
preNode.next = preNode.next.next;
}
size--;
}
@Override
public E set(int index, E element) {
rangeCheck(index);
Node<E> temp = getNode(index);
E old = temp.element;
temp.element = element;
return old;
}
@Override
public E get(int index) {
rangeCheck(index);
Node<E> temp = getNode(index);
return temp.element;
}
private Node<E> getNode(int index) {
Node<E> temp = head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
}
@Override
public int indexOf(E element) {
Node<E> temp = head;
for (int i = 0; i < size; i++) {
if (temp.element.equals(element))
return i;
temp = temp.next;
}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("size=").append(size);
if (size != 0) {
Node temp = head;
str.append(", [");
for (int i = 0; i < size; i++) {
str.append(temp.element);
if (i != size - 1) {
str.append(",");
}
temp = temp.next;
}
str.append("]");
}
return str.toString();
}
public static void main(String[] args) {
List<Object> list = new LinkedList<>(); //多态, 成员方法编译看左边, 运行看右边
list.add(new Person(23, "小杰"));
list.add(0, 66);
list.add(99.9);
list.add(list.size(), 100);
list.remove(0);
list.remove(list.size() - 1);
list.set(1, 666);
System.out.println(list.get(0));
System.out.println(list);
}
}
2、测试结果(不含头结点)
3、带虚拟头结点的单向链表明细
思路和不带头结点一模一样,只是查询结点的时候,初始结点是头结点的下一个结点,并且增删的时候不需要在针对头部插入分情况,只需要修改查询初始值为头结点即可。
public class LinkedListWithHeadNode<E> extends AbstractList<E> {
public Node<E> head; //虚拟头结点, 指向的下一个结点才真正才真正存放数据
public LinkedListWithHeadNode() {
head = new Node<E>(null, null); //构造函数里面初始化虚拟头结点
}
public static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
@Override
public void clear() {
this.size = 0;
head = null; //清除所有结点的内存空间
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
Node<E> preNode = index == 0 ? head : getNode(index - 1); //链表头带上虚拟头结点之后头部变得通用了
Node<E> newNode = new Node<E>(element, preNode.next);
preNode.next = newNode;
//preNode.next = new Node<E>(element, preNode.next); //好看的代码
size++;
}
@Override
public void remove(int index) {
rangeCheck(index);
Node<E> preNode = null;
//链表头带上虚拟头结点之后头部变得通用了
if (index == 0) {
head.next = head.next.next;
//preNode=head; preNode.next=preNode.next.next; //等价写法, 因为虚拟头结点没有移动
size--;
return;
}
preNode = getNode(index - 1);
preNode.next = preNode.next.next;
size--;
}
@Override
public E set(int index, E element) {
rangeCheck(index);
Node<E> temp = getNode(index);
E old = temp.element;
temp.element = element;
return old;
}
@Override
public E get(int index) {
rangeCheck(index);
Node<E> temp = getNode(index);
return temp.element;
}
private Node<E> getNode(int index) {
Node<E> temp = head.next; //头结点的下一个结点才是真正存数据的结点
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
}
@Override
public int indexOf(E element) {
Node<E> temp = head.next; //头结点的下一个结点才是真正存数据的结点
for (int i = 0; i < size; i++) {
if (temp.element.equals(element))
return i;
temp = temp.next;
}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("size=").append(size);
if (size != 0) {
Node temp = head.next;
str.append(", [");
for (int i = 0; i < size; i++) {
str.append(temp.element);
if (i != size - 1) {
str.append(",");
}
temp = temp.next;
}
str.append("]");
}
return str.toString();
}
public static void main(String[] args) {
List<Object> list = new LinkedListWithHeadNode<>(); //多态, 成员方法编译看左边, 运行看右边
list.add(new Person(23, "小杰"));
list.add(0, 66);
list.add(99.9);
list.add(list.size(), 100);
list.remove(0);
list.remove(list.size() - 1);
list.set(1, 666);
System.out.println(list.get(0));
System.out.println(list);
}
}
4、测试结果(含头结点)
5、双向链表明细
双向链表的每个结点都有分别指向前驱节点和后继结点的指针,而且链表中有头部和尾部,分别指向最前面和最后的结点。
查询的时候按照索引在前半部分还是后半部分采取不同的遍历方式。
插入和删除需要在之前单向链表讨论情况的基础上,在头部插入的时候还需要对在头部插入第一个结点和其他头部插入情况分开讨论;在头部删除的时候也是一样,需要对只有一个结点进行头部删除和其他头部删除情况分开讨论。
public class DoubleLinkedList<E> extends AbstractList<E> {
private Node<E> head;
private Node<E> tail;
public static class Node<E> {
Node<E> last;
E element;
Node<E> next;
public Node(Node<E> last, E element, Node<E> next) {
this.last = last;
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("Node{last=");
if (last != null)
str.append(last.element);
else
str.append("null");
str.append(", element=");
str.append(element);
str.append(", ");
str.append("next=");
if (next != null)
str.append(next.element);
else
str.append("null");
str.append("}");
return str.toString();
}
@Override
protected void finalize() throws Throwable {
System.out.println("Node挂了");
}
}
@Override
public void clear() {
this.size = 0;
//JVM中只要有空间 没有被GC Roots对象指向, 就会被销毁
head = null;
tail = null;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
Node<E> node = getNode(index);
if (index == 0) { //在头部插入
Node<E> newNode = new Node<E>(null, element, node);
if (node == null || size == 0) { //插入第一个结点的时候
head = newNode;
tail = newNode;
} else {
node.last = newNode; //不能交换
head = newNode;
}
} else if (index == size) { //尾部插入
Node<E> newNode = new Node<E>(tail, element, null); //双向链表找最后结点不用遍历, 直接取就行
tail.next = newNode; //顺序不能交换
tail = newNode;
} else { //普通情况
Node<E> former = node.last;
Node<E> newNode = new Node<E>(former, element, node);
former.next = newNode;
node.last = newNode;
}
size++;
}
@Override
public void remove(int index) {
rangeCheck(index);
Node<E> node = getNode(index);
Node<E> former = node.last;
Node<E> latter = node.next;
if (index == 0) { //头部删除
if (size == 1) { //删除的时候只有一个结点
head = null;
tail = null;
} else {
latter.last = null;
head = latter;
}
} else if (index == size - 1) { //尾部删除
former.next = null;
tail = former;
} else {
//普通情况
former.next = latter;
latter.last = former;
}
size--;
}
@Override
public E set(int index, E element) {
rangeCheck(index);
Node<E> temp = getNode(index);
E old = temp.element;
temp.element = element;
return old;
}
@Override
public E get(int index) {
rangeCheck(index);
return getNode(index).element;
}
public Node<E> getNode(int index) {
Node<E> temp = null;
if (index < this.size >> 1) {
temp = head; //从头遍历
for (int i = 0; i < index; i++) {
temp = temp.next;
}
} else {
temp = tail; //从尾遍历
for (int i = 0; i < size - 1 - index; i++) {
temp = temp.last;
}
}
return temp;
}
@Override
public int indexOf(E element) {
Node<E> temp = head;
for (int i = 0; i < size; i++) {
if (temp.element.equals(element))
return i;
temp = temp.next;
}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("size=").append(size);
str.append(", head=");
str.append(head.element);
str.append(", tail=");
str.append(tail.element);
if (size != 0) {
Node temp = head;
str.append(", [");
for (int i = 0; i < size; i++) {
str.append(temp.toString());
if (i != size - 1) {
str.append(",");
}
temp = temp.next;
}
str.append("]");
}
return str.toString();
}
public static void main(String[] args) {
List<Object> list = new DoubleLinkedList<>(); //多态, 成员方法编译看左边, 运行看右边
list.add(new Person(23, "小杰"));
list.add(0, 66);
list.add(99.9);
list.add(list.size(), 100);
list.remove(0);
list.remove(list.size() - 1);
list.set(1, 666);
System.out.println(list.get(0));
System.out.println(list);
}
}