基礎
-
ArrayList
:順序列表,它是Array
的增強版,也稱動態數組,提供了動態的增加和減少數組,如果你閱讀過它的源碼,你會發現它內部就是採用數組來存儲數據,並且動態擴容數組的長度,在日常開發中被廣泛使用。 -
LinkedList
:線性列表,但是和ArrayList
不同的是,它採用了雙向鏈表的數據結構,既然是鏈表的結構,那麼就不需要考慮它的容量問題。 - 各自的優缺點:
-
ArrayList
採用數組的結構,在添加和刪除操作上的效率低,需要大量移動數組中的元素,但是在查找的效率高; -
LinkedList
採用了鏈表的結構,在查找的方面效率極低,添加和刪除的方面效率高; -
LinkedList
佔用內存比ArrayList
更高,因爲它的結點不僅村粗了數據,並且存儲了前一節點和後一節點的引用。
-
制定手寫方案:
- 兩種列表都需要有:添加、刪除、查找、清空、判空、循環遍歷和獲取大小的共有的方法;
-
LinkedList
添加元素的方法需要區分類型:頭部添加,尾部添加,指定下標添加;ArrayList
也需要有添加整個數組的方法; -
LinkedList
在本次的方案中採用的是單向鏈表而不是雙向鏈表的結構。
手寫ArrayList:
import java.util.Arrays;
import java.util.function.Consumer;
class ArrayList<E> {
// 用於保存空列表的引用
private static final Object[] EMPTY_ELEMENTDATA = {};
private Object[] _data;
private int _size;
private int _realLength;
// 列表默認的長度
private static final int LEN = 20;
public ArrayList(int initLen) {
if (initLen == 0) {
this._data = EMPTY_ELEMENTDATA;
} else if (initLen > 0) {
this._data = new Object[initLen];
} else {
throw new IllegalArgumentException("init length is error");
}
this._size = initLen;
this._realLength = 0;
}
public ArrayList() {
this(LEN);
}
/**
* 追加一個元素
*
* @param element 元素
*/
public void add(E element) {
checkLen();
_data[_realLength++] = element;
}
/**
* 指定下標插入
*
* @param index 下標
* @param element 元素
*/
public void add(int index, E element) {
checkIndex(index);
checkLen();
System.arraycopy(_data, index, _data, index + 1, _realLength - index);
_data[index] = element;
_realLength++;
}
/**
* 添加一個數組
*
* @param elements 數組元素
*/
public void addAll(E[] elements) {
for (E e : elements) {
checkLen();
_data[_realLength++] = e;
}
}
/**
* 獲取指定下標的元素
*
* @param index 下標
* @return 下標對應的元素
*/
@SuppressWarnings("unchecked")
public E get(int index) {
checkIndex(index);
return (E) _data[index];
}
/**
* 刪除指定下標的元素
*
* @param index 下標
* @return 刪除的元素
*/
@SuppressWarnings("unchecked")
public E remove(int index) {
checkIndex(index);
Object delete = _data[index];
System.arraycopy(_data, index + 1, _data, index, _realLength - index - 1);
_data[--_realLength] = null;
return (E) delete;
}
/**
* 判斷是否包含某個元素
*
* @param element 元素
* @return 包含返回true
*/
public boolean contain(E element) {
if (isEmpty()) return false;
else {
for (Object e : _data) {
if (e == element) {
return true;
}
}
return false;
}
}
/**
* 循環遍歷列表
*
* @param consumer Consumer對象
*/
public void forEach(Consumer<E> consumer) {
if (isEmpty()) {
return;
}
for (Object object : _data) {
if (object != null)
consumer.accept((E) object);
}
}
/**
* 清空列表元素
*/
public void clear() {
for (int i = 0; i < _realLength; i++) {
_data[i] = null;
}
_realLength = 0;
}
/**
* 檢查下標是否越界
*
* @param index 下標
*/
private void checkIndex(int index) {
if (index < 0 || index > _realLength) {
throw new IndexOutOfBoundsException();
}
}
public boolean isEmpty() {
return _realLength == 0;
}
public int size() {
return _realLength;
}
/**
* 檢查是否要擴容數組
*
* @return true 表示需要擴容
*/
private void checkLen() {
if (_realLength >= _size) {
grow();
}
/**
* 數組的擴容,擴容大小爲原先的2倍
*/
private void grow() {
// 擴容後size也要變化
_size = _size * 2;
_data = Arrays.copyOf(_data, _size);
}
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(1, 2);
arrayList.add(3);
arrayList.add(1, 4);
System.out.print("原始數據:");
arrayList.forEach(element -> System.out.print(element + " "));
System.out.println();
arrayList.remove(1);
System.out.print("移除index=1的元素:");
arrayList.forEach(element -> System.out.print(element + " "));
System.out.println();
System.out.println("list has {1}: " + arrayList.contain(1));
System.out.println("list has {10}: " + arrayList.contain(10));
}
}
// 原始數據:1 4 2 3
// 移除index=1的元素:1 2 3
// list has {1}: true
// list has {10}: false
如上所述,在組織一個 ArrayList
的過程中,我們特別注意以下幾點:
- 既然採用數組,那麼就要注意它的容量問題,做好擴容的工作;
- 使用數組的過程中,千萬別忘記處理數組越界問題;
- 在刪除固定的元素後,別忘記了手動置空下。
實現一個簡單的 ArrayList
還是挺簡單的,自己動手寫一遍會對 ArrayList
有另一番見解,快去動手敲一敲吧。
手寫LinkedList:
import java.util.function.Consumer;
class LinkedList<E> {
// 列表的長度
private int _size;
// 頭結點
private Node<E> _first;
// 尾結點
private Node<E> _last;
public LinkedList() {
_size = 0;
}
/**
* 在尾巴插入一個結點
*
* @param element 插入的數據
*/
public void addLast(E element) {
final Node<E> l = _last;
final Node<E> newNode = new Node<>(element, null);
_last = newNode;
if (l == null) {
// 如果之前的尾部元素爲空,那說明列表爲空,插入元素後,首尾應該都是newNode
_first = newNode;
} else {
l.next = newNode;
}
_size++;
}
/**
* 在頭部插入元素
*
* @param element 插入的數據
*/
public void addFirst(E element) {
final Node<E> f = _first;
final Node<E> newNode = new Node<>(element, _first);
_first = newNode;
if (f == null) {
// 如果之前的頭部元素爲空,那說明列表爲空,插入元素後,首尾應該都是newNode
_last = newNode;
} else {
newNode.next = f;
}
_size++;
}
/**
* 添加一個元素,默認在尾巴添加
*
* @param element 元素值
*/
public void add(E element) {
addLast(element);
}
/**
* 指定下標插入元素
*
* @param index 下標
* @param element 元素值
*/
public void add(int index, E element) {
// 特殊處理頭尾
if (index == 0) {
addFirst(element);
return;
}
if (index == size() - 1) {
addLast(element);
return;
}
Node<E> newNode = new Node<>(element, null);
Node<E> pre = _first;
Node<E> next = _first.next;
for (int i = 1; i < size() - 1; i++) {
if (i == index) {
newNode.next = next;
pre.next = newNode;
_size++;
}
pre = next;
next = next.next;
}
}
/**
* 獲取指定下標的元素值
*
* @param index 下標
* @return 值
*/
public E get(int index) {
checkIndex(index);
Node<E> f = _first;
for (int i = 0; i < size(); i++) {
if (i == index) {
return f.data;
} else {
f = f.next;
}
}
return null;
}
/**
* 移除指定下標的元素
*
* @param index 下標
* @return 返回移除的元素
*/
public E remove(int index) {
checkIndex(index);
Node<E> f = _first;
Node<E> l = _first;
// 需特殊處理頭結點
if (index == 0) {
E element = f.data;
f = f.next;
_first = f;
_size--;
return element;
}
for (int i = 0; i < size(); i++) {
if (i == index) {
E element = f.data;
l.next = f.next;
_size--;
f.next = null;
f.data = null;
return element;
} else {
l = f;
f = f.next;
}
}
return null;
}
/**
* 檢測是否包含某個元素
*
* @param element 包含的對象
* @return 包含返回true
*/
public boolean contain(E element) {
if (!isEmpty()) {
Node<E> f = _first;
for (int i = 0; i < size(); i++) {
if (f.data == element) return true;
f = f.next;
}
return false;
} else {
return false;
}
}
/**
* 遍歷整個列表
*
* @param consumer Consumer對象
*/
public void forEach(Consumer<E> consumer) {
if (isEmpty()) {
return;
}
Node<E> f = _first;
while (f != null) {
E e = f.data;
consumer.accept(e);
f = f.next;
}
}
/**
* 清空
*/
public void clear() {
for (Node<E> x = _first; x != null; ) {
Node<E> next = x.next;
x.data = null;
x.next = null;
x = next;
}
_first = _last = null;
_size = 0;
}
/**
* 列表的長度
*
* @return 長度值
*/
public int size() {
return _size;
}
/**
* 判斷列表是否爲空
*
* @return 爲空返回true
*/
public boolean isEmpty() {
return _size == 0;
}
/**
* 檢查下標是否越界
*
* @param index 下標
*/
private void checkIndex(int index) {
if (index < 0 || index > _size) {
throw new IndexOutOfBoundsException();
}
}
/**
* 鏈表節點
*
* @param <E> 數據類型
*/
static class Node<E> {
E data;
Node<E> next;
Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(1);
linkedList.addFirst(0);
linkedList.add(2);
linkedList.add(3);
System.out.print("原始數據: ");
linkedList.forEach(integer -> System.out.print(integer + " "));
System.out.println();
int remove = linkedList.remove(0);
System.out.println("remove data is: " + remove);
System.out.print("刪除index爲0的數據: ");
linkedList.forEach(integer -> System.out.print(integer + " "));
System.out.println();
linkedList.add(1, 10);
System.out.print("在index=1的地方添加數據10: ");
linkedList.forEach(integer -> System.out.print(integer + " "));
System.out.println();
System.out.println("list has {1}: " + linkedList.contain(1));
System.out.println("list has {100}: " + linkedList.contain(100));
}
}
// 原始數據: 0 1 2 3
// remove data is: 0
// 刪除index爲0的數據: 1 2 3
// 在index=1的地方添加數據10: 1 10 2 3
// list has {1}: true
// list has {10}: true
LinkedList
的實現過程中,比 ArrayList
要多出了幾個操作,畢竟是鏈表的結構,插入元素可以分別在頭部和尾部插入數據,對於熟悉鏈表結構的大佬來說一點,這點一點不成問題,不熟悉的小佬可以藉此機會熟悉一下鏈表的操作。下面說一下幾點注意的地方吧:
- 對於第一次插入數據的時候,無論實在頭部還是尾部,一定要注意對
_first
和_last
的處理; - 在指定位置添加和刪除元素的時候,一定要處理好前節點和後結點之間的聯繫,並且在刪除的操作中別忘記了將刪除後的節點數據置空。
如果本文章你發現有不正確或者不足之處,歡迎你在下方留言或者掃描下方的二維碼留言也可!