线性表ArrayList和LinkedList源码详解。
List
描述
线性表抽象接口,所有线性表应该在实现这个接口的基础上进行操作。
接口
package list; /** * Description: 线性表的接口,使用泛型保证类型 * * @ClassName: List * @author 过道 * @date 2018年8月13日 上午10:45:13 */ public interface ListInterface<T> { public void add(T newEntry); public void add(Integer newPosition, T newEntry); public T remove(int givePosition); public void clear(); public T replace(int givenPosition, T newEntry); public T getEntry(int givenPosition); public T[] toArray(); public boolean contains(T anEntry); public int getLength(); public boolean isEmpty(); }
ArrayList
1 接口
/** * 为了区分java默认的 ArrayList和我的list,所以命名为AList * 因为内部没有实现,所以很简单,就没有写注释 */ public class AList<T> implements ListInterface<T> { @Override public void add(T newEntry) { } @Override public void add(Integer newPosition, T newEntry) { } @Override public T remove(int givePosition) { return null; } @Override public void clear() { } @Override public T replace(int givenPosition, T newEntry) { return null; } @Override public T getEntry(int givenPosition) { return null; } @Override public T[] toArray() { return null; } @Override public boolean contains(T anEntry) { return false; } @Override public int getLength() { return 0; } @Override public boolean isEmpty() { return false; } }
2.实现途径
Array的意思就是数组,所以很简单可以猜测到这是使用数组实现List,为了知道数组中多少个有效位,使用一个int值保存。初始化时,要么用户给定数组容量,要么使用默认容量。
注:强烈建议在使用ArrayList或其他双倍扩容的容器时给定容量,因为这将大大节省扩容的时间。
public class AList<T> implements ListInterface<T> { //空数组,用来存放数据 T[] list = null; // 当前有效的数字个数 int numberOfEntries = 0; //如果用户不给定容量(强烈建议初始化时给定容量), 默认的数组容量 static final int DEFAULT_CAPACITY = 27; @SuppressWarnings("unchecked") public AList(int initialCapacity) { T[] tempList = (T[]) new Object[initialCapacity]; list = tempList; numberOfEntries = 0; } public AList() { this(DEFAULT_CAPACITY); } // 其余覆盖的方法 }
实现add方法
注意一下,我们使用下标从1开始,所以不要与ArrayList混淆(从0开始)。
注:ArrayList数组使用的是 1.5 倍扩容,即如果当前数组空间已满,我们申请双倍容量的数组,然后将原数组copy到新数组中,所以扩容是比较慢的。
Vector使用的是2倍扩容,不过我这里为了省事,使用了2倍扩容。
@Override public void add(T newEntry) { // 与ArrayList 不同,我们选择下标从 1 开始。 list[numberOfEntries + 1] = newEntry; numberOfEntries++; ensureCapacity(); //也可以选择重用其他方法 // add(numberOfEntries+1,newEntry); // 这样重用,能有效减少代码,但是理解上稍微有些困难。并且增加了许多无谓的判断,降低了效率(当然这无所谓) } /** * 指定位置进行添加某一个数字 * * @param newPosition * @param newEntry */ @Override public void add(Integer newPosition, T newEntry) { if ((newPosition >= 1) && (newPosition <= numberOfEntries + 1)) { if (newPosition <= numberOfEntries) { makeRoom(newPosition); // 将给定位置的数字及后续数字全部后移。 } list[newPosition] = newEntry; numberOfEntries++; ensureCapacity(); // 为下次添加获取足够的空间 } else { throw new IndexOutOfBoundsException("给定位置不合法,下标越界"); } } /** * @param newPosition */ private void makeRoom(Integer newPosition) { assert (newPosition >= 1) && (newPosition <= newPosition + 1); //断言,如果不满足断言,则报错,需要手动开启assert int newIndex = newPosition; int lastIndex = numberOfEntries; for (int index = 0; index < newIndex; index++) { list[index + 1] = list[index]; //给定位置之后的数字全部后移一位,将给定位置‘空’下来 } } /** * 倍扩容量 */ private void ensureCapacity() { int capacity = list.length - 1; if (numberOfEntries >= capacity) { int newCapacity = 2 * capacity; list = Arrays.copyOf(list, newCapacity + 1); } }
实现简单方法
只要看懂了前面的底层实现,那么对于下面三个方法的实现应该不会有问题。
@Override public int getLength() { return numberOfEntries; } @Override public boolean isEmpty() { return numberOfEntries == 0; } @Override public void clear() { list = null; //取消引用,等待GC回收 numberOfEntries = 0; }
remove方法
数组移除一个元素后,那么后续元素立刻跟进,弥补空隙。
注意:已经扩容后的容量不会被返还,也没有必要返还。
如果真想归还的话,:源码中提供了“trimToSize()”,当然具体应用因人而异。
@Override public T remove(int givePosition) { if ((givePosition >= 1) && (givePosition <= numberOfEntries)) { assert !isEmpty(); T result = list[givePosition]; if (givePosition < numberOfEntries) { removeGap(givePosition); // 移除后出现空隙,我们让之后的元素前移一位,弥补空隙 } return result; } return null; } /** * 给定位置为空隙,后续元素前进一位,补上空隙 */ private void removeGap(int givePosition) { assert (givePosition >= 1) && (givePosition < numberOfEntries); int removedIndex = givePosition; int lastIndex = numberOfEntries; for (int index = givePosition; index < lastIndex; index++) { list[index] = list[index + 1]; } }
tip: trimToSize的源码
/** * Trims the capacity of this <tt>ArrayList</tt> instance to be the * list's current size. An application can use this operation to minimize * the storage of an <tt>ArrayList</tt> instance. */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA // 这是个空数组 ---> {} : Arrays.copyOf(elementData, size); //吧数字copy进去,给一个size容量 } }
toArray方法的实现
注意:千万不要直接返回ArrayList底层的数组,我们应该新申请一个空间,并将元素逐个放入其中。如果返回list,将成为一个非常恐怖的事情。
封装性:其他类只能通过我们定义好的方法去访问或改变我们对象中的域,不然就是封装遭到了破坏.
@Override public T[] toArray() { /* 这里千万不能返回我们使用的T[] list * 因为用户可能会对返回数组进行修改, * 如果返回list,那么封装失败。 * 也就是说用户可以不使用我们提供的方法进行操作List,这是很恐怖的事情。 */ T[] result = (T[]) new Object[numberOfEntries]; for (int index = 0; index < numberOfEntries; index++) { result[index] = list[index + 1]; } return result; }
其余方法
@Override public T replace(int givenPosition, T newEntry) { if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) { assert !isEmpty(); T oldEntry = list[givenPosition]; list[givenPosition] = newEntry; return oldEntry; } //省去报错的部分 return null; } @Override public T getEntry(int givenPosition) { // 判断给定位置是否合法,不合法就报错。 if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) { assert !isEmpty(); return list[givenPosition]; } //else就报错,我省去此步 return null; } @Override public boolean contains(T anEntry) { boolean found = false; int index = 1; // 遍历寻找,找到一个就结束循环。 while (!found && (index <= numberOfEntries)) { if (anEntry.equals(list[index])) { found = true; } index++; } return found; }
全部方法实现源码
package list; import java.util.Arrays; /** * 为了区分java默认的 ArrayList和我的list,所以命名为ArrayList */ public class AList<T> implements ListInterface<T> { T[] list = null; int numberOfEntries = 0; static final int DEFAULT_CAPACITY = 27; @SuppressWarnings("unchecked") public AList(int initialCapacity) { T[] tempList = (T[]) new Object[initialCapacity]; list = tempList; numberOfEntries = 0; } public AList() { this(DEFAULT_CAPACITY); } @Override public void add(T newEntry) { // 与ArrayList 不同,我们选择下标从 1 开始。 list[numberOfEntries + 1] = newEntry; numberOfEntries++; ensureCapacity(); //也可以选择重用其他方法 // add(numberOfEntries+1,newEntry); // 这样重用,能有效减少代码,但是理解上稍微有些困难。并且增加了许多无谓的判断,降低了效率(当然这无所谓) } /** * 指定位置进行添加某一个数字 * * @param newPosition * @param newEntry */ @Override public void add(Integer newPosition, T newEntry) { if ((newPosition >= 1) && (newPosition <= numberOfEntries + 1)) { if (newPosition <= numberOfEntries) { makeRoom(newPosition); // 将给定位置的数字及后续数字全部后移。 } list[newPosition] = newEntry; numberOfEntries++; ensureCapacity(); // 为下次添加获取足够的空间 } else { throw new IndexOutOfBoundsException("给定位置不合法,下标越界"); } } /** * @param newPosition */ private void makeRoom(Integer newPosition) { assert (newPosition >= 1) && (newPosition <= newPosition + 1); //断言,如果不满足断言,则报错,需要手动开启assert int newIndex = newPosition; int lastIndex = numberOfEntries; for (int index = 0; index < newIndex; index++) { list[index + 1] = list[index]; //给定位置之后的数字全部后移一位,将给定位置‘空’下来 } } @Override public T remove(int givePosition) { if ((givePosition >= 1) && (givePosition <= numberOfEntries)) { assert !isEmpty(); T result = list[givePosition]; if (givePosition < numberOfEntries) { removeGap(givePosition); // 移除后出现空隙,我们让之后的元素前移一位,弥补空隙 } return result; } return null; } /** * 给定位置为空隙,进行弥补空隙, * * @param givePosition */ private void removeGap(int givePosition) { assert (givePosition >= 1) && (givePosition < numberOfEntries); int removedIndex = givePosition; int lastIndex = numberOfEntries; for (int index = givePosition; index < lastIndex; index++) { list[index] = list[index + 1]; } } @Override public void clear() { list = null; //取消引用,等待GC回收 numberOfEntries = 0; } @Override public T replace(int givenPosition, T newEntry) { if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) { assert !isEmpty(); T oldEntry = list[givenPosition]; list[givenPosition] = newEntry; return oldEntry; } return null; } @Override public T getEntry(int givenPosition) { if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) { assert !isEmpty(); return list[givenPosition]; } //else就报错,我省去此步 return null; } @Override public T[] toArray() { T[] result = (T[]) new Object[numberOfEntries]; for (int index = 0; index < numberOfEntries; index++) { result[index] = list[index + 1]; } return result; } @Override public boolean contains(T anEntry) { boolean found = false; int index = 1; while (!found && (index <= numberOfEntries)) { if (anEntry.equals(list[index])) { found = true; } index++; } return found; } @Override public int getLength() { return numberOfEntries; } @Override public boolean isEmpty() { return numberOfEntries == 0; } /** * 倍扩容量 */ private void ensureCapacity() { int capacity = list.length - 1; if (numberOfEntries >= capacity) { int newCapacity = 2 * capacity; list = Arrays.copyOf(list, newCapacity + 1); } } }
总结
如果说ArrayList 需要注意的地方
- 1.5倍扩容,思想很棒,但是比较浪费时间,所以程序员尽可能估算要使用的空间的大概上界,然后初始化时给定容量。
- toArray时,尽管底层有数组,但是千万不能返回底层的数组。
- clear时,直接将数组引用置为null,GC就可以回收到这块内存了。
1.5倍扩容:源代码:
int newCapacity = oldCapacity + (oldCapacity >> 1);//old + (old容量/ 2)
使用位移,更快。
LinkedList
实现接口
package list; /** * 为了区分LinkedList,所以命名为LList,但本质都是双向链表 * 下标从 1 开始,与java源码不同(以0开始) */ public class LList<T> implements ListInterface<T> { @Override public void add(T newEntry) { } @Override public void add(Integer newPosition, T newEntry) { } @Override public T remove(int givePosition) { return null; } @Override public void clear() { size = 0; first = null; } @Override public T replace(int givenPosition, T newEntry) { return null; } @Override public T getEntry(int givenPosition) { return null; } @Override public T[] toArray() { return null; } @Override public boolean contains(T anEntry) { return false; } @Override public int getLength() { return 0; } @Override public boolean isEmpty() { return false; } }
底层实现
明显使用链式,所以我们需要定义结点类Node。
其次,我们刚开始使用单向链表,利于实现与理解,之后会修改部分代码使其成了双向链表。并提供一些双向链表特有的操作。
public class LList<T> implements ListInterface<T> { int size; Node firstNode; // 链式实现无法给定容量,所以一个无参构造即可。 public LList() { size = 0; firstNode = null; } //省去覆盖方法 // 单向结点,只需要next和data就足够了 class Node { T data; Node next; public Node() { } public Node(T data) { this(data, null); } public Node(T data, Node next) { this.data = data; this.next = next; } } }
核心方法:add的实现
@Override public void add(T newEntry) { // 尾部位置进行添加 add(size, newEntry); } @Override public void add(Integer newPosition, T newEntry) { Node newNode = new Node(newEntry); if (newPosition == 1) { //头结点插入 newNode.next = firstNode; firstNode = newNode; } else if (newPosition > 1 && newPosition <= size) { Node beforeNode = firstNode; int count = 1; // 找到链表对应位置的前一位,进行插入操作 while (count != newPosition - 1) { beforeNode = beforeNode.next; count++; } // 插入元素 newNode.next = beforeNode.next; // 链接起来了 beforeNode.next = newNode; } else { throw new IndexOutOfBoundsException("给定位置越界"); } }
LinkedList全部源码
package list; /** * 为了区分LinkedList,所以命名为LList,但本质都是双向链表 * 下标从 1 开始,与java源码不同(以0开始) */ public class LList<T> implements ListInterface<T> { private int size; private Node firstNode; public LList() { size = 0; firstNode = null; } @Override public void add(T newEntry) { // 尾部位置进行添加 add(size, newEntry); } @Override public void add(Integer newPosition, T newEntry) { Node newNode = new Node(newEntry); if (newPosition == 1) { //头结点插入 newNode.next = firstNode; firstNode = newNode; } else if (newPosition > 1 && newPosition <= size) { Node beforeNode = firstNode; int count = 1; // 找到链表对应位置的前一位,进行插入操作 while (count != newPosition - 1) { beforeNode = beforeNode.next; count++; } // 插入元素 newNode.next = beforeNode.next; // 链接起来了 beforeNode.next = newNode; } else { throw new IndexOutOfBoundsException("给定位置越界"); } } @Override public T remove(int givePosition) { T result; if ((givePosition >= 1) && (givePosition <= size)) { assert !isEmpty(); if (givePosition == 1) { // 头结点的移除 result = firstNode.data; firstNode = firstNode.next; } else { Node nodeBefore = getNodeAt(givePosition - 1); Node nodeToRemove = nodeBefore.next; result = nodeBefore.data; Node nodeAfter = nodeToRemove.next; nodeBefore.next = nodeAfter; // 断开移除结点的两侧 } size--; return result; } else throw new IndexOutOfBoundsException("下标越界"); } private Node getNodeAt(int i) { if (i > 0 && i <= size) { Node curNode = firstNode; for (int j = 0; j < i; j++) curNode = curNode.next; return curNode; } else throw new IndexOutOfBoundsException("下标越界"); } @Override public void clear() { size = 0; firstNode = null; } @Override public T replace(int givenPosition, T newEntry) { // 判断和报错都交给 getNodeAt 方法去做 Node nodeToReplace = getNodeAt(givenPosition); T result = nodeToReplace.data; nodeToReplace.data = newEntry; return result; } @Override public T getEntry(int givenPosition) { return getNodeAt(givenPosition).data; } @Override public T[] toArray() { T[] result = (T[]) new Object[size]; Node curNode = firstNode; for (int i = 0; i < size; i++) { result[i] = curNode.data; } return null; } @Override public boolean contains(T anEntry) { Node curNode = firstNode; boolean found = false; while (!found && curNode != null) { if (curNode.data.equals(anEntry)) { found = true; } curNode = curNode.next; } return found; } @Override public int getLength() { return size; } @Override public boolean isEmpty() { boolean result = false; // 为了提供更多的报错信息,在这里使用断言,需要手动开启断言功能。且程序开发中不建议使用。 if (size == 0) { assert firstNode == null; result = true; } else { assert firstNode != null; result = false; } return result; } class Node { T data; Node next; public Node() { } public Node(T data) { this(data, null); } public Node(T data, Node next) { this.data = data; this.next = next; } } }
接下来只需要把单向链表改成双向链表即可,增加几个“getPrevNode(),addFirstNode()”之类的方法,修改add(),即可。