Day26 手撕各种集合底层源码(一)
一、手撕ArrayList底层源码
1、概念: ArrayList
的底层实现是基于数组的动态扩容结构。
2、思路:
1.研究继承关系
2.研究属性
3.理解创建集合的过程 – 构造方法的底层原理
4.研究添加元素的过程
3、关键源码:
成员变量:
transient Object[] elementData; // 用于存储元素的数组
private int size; // ArrayList中元素的数量
构造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 初始容量为0的空数组
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity]; // 指定初始容量的数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA; // 初始容量为0的空数组
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
添加元素方法(add):
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量足够
elementData[size++] = e; // 将元素添加到数组末尾
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 默认容量为10
}
ensureExplicitCapacity(minCapacity); // 确保容量足够
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0) {
grow(minCapacity); // 扩容
}
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity); // 数组扩容
}
4、举例:
public static void main(String[] args) {
//ArrayList<String> list = new ArrayList<>();
ArrayList<String> list = new ArrayList<>(10000);
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
}
二、手撕LinkedList底层源码
1、概念: LinkedList
的底层实现是基于双向链表的数据结构。
2、思路:
1.研究继承关系
2.研究属性
3.理解创建集合的过程 – 构造方法的底层原理
4.研究添加元素的过程
3、关键源码:
节点定义:
private static class Node<E> {
E item; // 节点元素
Node<E> next; // 后继节点
Node<E> prev; // 前驱节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
成员变量:
transient int size = 0; // LinkedList中元素的数量
transient Node<E> first; // 链表的头节点
transient Node<E> last; // 链表的尾节点
添加元素方法(add):
public boolean add(E e) {
linkLast(e); // 将元素添加到链表末尾
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null); // 创建新节点
last = newNode; // 将新节点设置为尾节点
if (l == null) {
first = newNode; // 如果链表为空,将新节点设置为头节点
} else {
l.next = newNode; // 将前尾节点的后继节点指向新节点
}
size++; // 元素数量加1
}
删除元素方法(remove):
public E remove() {
return removeFirst(); // 删除链表的第一个节点
}
public E removeFirst() {
final Node<E> f = first;
if (f == null) {
throw new NoSuchElementException();
}
return unlinkFirst(f); // 删除第一个节点
}
E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next; // 将下一个节点设置为头节点
if (next == null) {
last = null; // 如果下一个节点为空,将尾节点置空
} else {
next.prev = null; // 将下一个节点的前驱节点置空
}
size--; // 元素数量减1
return element;
}
4、举例:
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("小浩");
list.add("小威");
list.add("小刘");
}
三、手撕Stack底层源码
1、概念: Java中的Stack
类是基于动态数组实现的后进先出(LIFO)栈结构。然而,需要注意的是,Java官方推荐使用Deque
接口的实现类ArrayDeque
来代替Stack
类,因为Deque
接口提供了更完善的栈操作方法,并且在性能上更优秀。
2、关键源码:
成员变量:
private transient Object[] elementData; // 用于存储栈元素的数组
private int elementCount; // 栈中元素的数量
基本方法:
public E push(E item) {
addElement(item); // 将元素压入栈顶
return item;
}
public synchronized E pop() {
E obj;
int len = size();
obj = peek(); // 获取栈顶元素
removeElementAt(len - 1); // 移除栈顶元素
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0) {
throw new EmptyStackException();
}
return elementAt(len - 1); // 获取栈顶元素
}
扩容方法:
java复制代码private void ensureCapacity(int minCapacity) {
if (minCapacity - elementData.length > 0) {
grow(minCapacity); // 扩容
}
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity); // 数组扩容
}
3、举例:
import java.util.Stack;
public class Test01 {
/**
* 知识点:手撕Stack底层源码
*/
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("aaa");
stack.push("bbb");
stack.push("ccc");
stack.push("ddd");
}
}
四、单向链表
1、概念: 单向链表(Singly Linked List)是一种常见的链表数据结构,它由节点组成,每个节点包含数据和指向下一个节点的指针。单向链表的最后一个节点指向空值(null),表示链表的结束。
2、特点:
- 单向遍历:只能从头到尾遍历链表,无法反向遍历。
- 插入和删除:在已知节点的情况下,可以方便地进行节点的插入和删除操作。
- 空间开销:相对于数组,单向链表需要额外的空间来存储指针。
3、应用场景:
- 需要频繁的插入和删除操作,且不需要反向遍历的场景。
- 需要在已知节点的情况下进行插入和删除操作的场景。
4、关键源码:
成员变量:
private transient Object[] elementData; // 用于存储栈元素的数组
private int elementCount; // 栈中元素的数量
基本方法:
public E push(E item) {
addElement(item); // 将元素压入栈顶
return item;
}
public synchronized E pop() {
E obj;
int len = size();
obj = peek(); // 获取栈顶元素
removeElementAt(len - 1); // 移除栈顶元素
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0) {
throw new EmptyStackException();
}
return elementAt(len - 1); // 获取栈顶元素
}
扩容方法:
private void ensureCapacity(int minCapacity) {
if (minCapacity - elementData.length > 0) {
grow(minCapacity); // 扩容
}
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity); // 数组扩容
}
5、举例:
import java.util.Iterator;
public class Test01 {
/**
* 知识点:实现单向链表
*/
public static void main(String[] args) {
UnidirectionalLinkedList<String> list = new UnidirectionalLinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("eee");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String element = it.next();
System.out.println(element);
}
}
}
public class UnidirectionalLinkedList<E> {
private Node<E> first;
private Node<E> last;
private int size;
public void add(E e){
Node<E> node = new Node<>(e, null);
if(first == null){
first = node;
}else{
last.next = node;
}
last = node;
size++;
}
public Iterator<E> iterator(){
return new Itr();
}
public class Itr implements Iterator<E>{
private int cursor;
private Node<E> node = first;
@Override
public boolean hasNext() {
return cursor != size;
}
@Override
public E next() {
E item = node.item;
node = node.next;
cursor++;
return item;
}
}
public static class Node<E>{
E item;
Node<E> next;
public Node(E item, Node<E> next) {
this.item = item;
this.next = next;
}
}
}
五、双向链表
1、概念: 双向链表(Doubly Linked List)是一种常见的链表数据结构,每个节点包含两个指针,分别指向前一个节点和后一个节点。与单向链表相比,双向链表可以支持双向遍历,提供了更多的灵活性 。
2、特点:
- 双向遍历:可以从头到尾或者从尾到头遍历链表,提供了更多的遍历方式。
- 插入和删除:在已知节点的情况下,可以更方便地进行节点的插入和删除操作。
- 空间开销:相对于单向链表,双向链表需要额外的空间来存储前驱节点的指针。
3、应用场景:
- 需要频繁的插入和删除操作,且需要双向遍历的场景。
- 需要在已知节点的情况下进行插入和删除操作的场景。
4、基本操作:
- 插入节点:在给定节点后或前插入新节点,需要更新前后节点的指针。
- 删除节点:删除给定节点,同样需要更新前后节点的指针。
- 遍历:可以从头到尾或者从尾到头遍历链表,获取节点的值或执行其他操作。
5、代码理解:
import java.util.Iterator;
import com.qf.bidirectional_linked_list.BidirectionalLinkedList.Node;
public class Test01 {
/**
* 知识点:实现双向链表
*/
public static void main(String[] args) {
BidirectionalLinkedList<String> list = new BidirectionalLinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("eee");
//正序遍历
Iterator<String> it = list.iterator();
while(it.hasNext()){
String element = it.next();
System.out.println(element);
}
System.out.println("-----------------------");
//倒序遍历
Node<String> node = list.getLast();
while(node != null){
System.out.println(node.item);
node = node.prev;
}
}
}
public class BidirectionalLinkedList<E> {
private Node<E> first;
private Node<E> last;
private int size;
public void add(E e){
Node<E> l = last;
Node<E> node = new Node<>(l,e, null);
if(first == null){
first = node;
}else{
last.next = node;
}
last = node;
size++;
}
public Node<E> getLast() {
return last;
}
public Iterator<E> iterator(){
return new Itr();
}
public class Itr implements Iterator<E>{
private int cursor;
private Node<E> node = first;
@Override
public boolean hasNext() {
return cursor != size;
}
@Override
public E next() {
E item = node.item;
node = node.next;
cursor++;
return item;
}
}
public static class Node<E>{
Node<E> prev;
E item;
Node<E> next;
public Node(Node<E> prev,E item, Node<E> next) {
this.prev = prev;
this.item = item;
this.next = next;
}
}
}
LinkedList理解图: