JavaLearn#(15)集合提升训练:手写ArrayList单链表LinkedListHashMapHashSet新一代并发集合类
Posted LRcoding
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn#(15)集合提升训练:手写ArrayList单链表LinkedListHashMapHashSet新一代并发集合类相关的知识,希望对你有一定的参考价值。
1. 手写ArrayList
1.1 ArrayList 底层原理细节
-
底层结构是一个长度可以动态增长的
数组(顺序表)
// 指向数组的引用 transient Object[] elementData; // transient修饰的变量,不参与序列化
数组特点:在内存中分配连续的空间,只存储数据,不存储地址信息
- 节省存储空间(无需存储地址和数据间的关系)、索引查询效率高(其它地址可以通过首地址计算得到)
- 插入和删除效率低,必须提前分配固定数量的空间,可能会导致空闲浪费,按照内容查询效率低
-
通过无参构造方法,创建对象时,JDK1.7初始长度
10
,JDK1.8初始长度为0
,在第一次添加元素时就进行扩容扩容到长度为10
(后续每次扩容1.5倍) -
提供了一个内部类
Itr
,实现了 Iterator 接口,用来对 ArrrayList 进行遍历
1.2 自定义:List、Iterator、异常处理
List
public interface List
/**
* 返回线性表的大小,即数据元素的个数
* @return
*/
int size();
/**
* 返回线性表中序号为 i 的数据元素
* @param i
* @return
*/
Object get(int i);
/**
* 如果线性表为空返回true,否则返回false
* @return
*/
boolean isEmpty();
/**
* 判断线性表中是否包含数据元素 e
* @param e
* @return
*/
boolean contains(Object e);
/**
* 返回数据元素 e 在线性表中的序号
* @param e
* @return
*/
int indexOf(Object e);
/**
* 将数据元素 e 插入到线性表中 i 号的位置
* @param i
* @param e
*/
void add(int i, Object e);
/**
* 将数据元素 e 插入到线性表的末尾
* @param e
*/
void add(Object e);
/**
* 删除线性表中序号为 i 的元素,并返回值
* @param i
* @return
*/
Object remove(int i);
/**
* 删除线性表中第一个与 e 相同的元素
* @param e
* @return
*/
boolean remove(Object e);
/**
* 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
* @param i
* @param e
* @return
*/
Object replace(int i, Object e);
/**
* 迭代List
* @return
*/
Iterator iterator();
Iterator
public interface Iterator<T>
/**
* 是否还有下一个元素
* @return
*/
boolean hasNext();
/**
* 下一个返回的元素值
* @return
*/
T next();
IndexOutOfBoundsException
public class IndexOutOfBoundsException extends RuntimeException
public IndexOutOfBoundsException()
public IndexOutOfBoundsException(String message)
super(message);
1.3 自定义:测试类
public class TestArrayList
public static void main(String[] args)
List list = new ArrayList();
list.add("1111");
list.add("aaaa");
list.add("bbbb");
list.add("3333");
list.add("2222");
list.add("1111");
list.add("aaaa");
list.add("bbbb");
list.add("3333");
list.add("2222");
// 添加第 11 个元素时,注意扩容
list.add("1111");
// 添加到指定位置,注意移动元素
list.add(3, "AAAA");
System.out.println(list.size());
System.out.println(list.isEmpty());
System.out.println(list.get(2));
System.out.println(list.contains("4444"));
System.out.println(list.indexOf("2222"));
// 注意判断查找 null 时
System.out.println(list.indexOf(null));
System.out.println(list.toString());
Iterator<String> iterator = list.iterator();
while (iterator.hasNext())
System.out.println(iterator.next());
// 光标已经走到最后面,还往后走,进行报错
System.out.println(iterator.next());
1.4 自定义:ArrayList
最重要的扩容底层:
ArrayList类
package com.lwclick.list;
import java.util.Arrays;
/**
* @author LIUWEI
* @className ArrayList
* @date 2021/9/27 22:42
* @description 手写的ArrayList
*/
public class ArrayList implements List
/**
* ArrayList底层是一个【长度可以动态增长】的数组,elementData是数组的引用
*/
private transient Object[] elementData;
/**
* 集合中存储了元素的个数,不是数组空间的容量length
* 增加、删除元素时,size的值都要发生变化
*/
private int size;
public ArrayList()
this(10);
public ArrayList(int initialCapacity)
elementData = new Object[initialCapacity];
@Override
public int size()
return size;
@Override
public Object get(int i)
if (i >= size || i < 0)
throw new IndexOutOfBoundsException("数组索引越界" + i);
return elementData[i];
@Override
public boolean isEmpty()
return size == 0;
@Override
public boolean contains(Object e)
return indexOf(e) >= 0;
@Override
public int indexOf(Object e)
int index = -1;
if (e == null) // 为空则比较引用
for (int i = 0; i < size; i++)
if (e == elementData[i])
return i;
else // 不为空则比较内容
for (int i = 0; i < size; i++)
if (e.equals(elementData[i]))
return i;
return index;
@Override
public void add(int index, Object e)
if (elementData.length == size)
grow();
// 添加到指定位置时,一定要移动元素,不然会覆盖
for (int i = size; i > index; i--)
elementData[i] = elementData[i - 1];
elementData[index] = e;
size++;
@Override
public void add(Object e)
// 如果数组已满,需要先扩容
if (elementData.length == size)
grow();
// 添加元素到最后:初始elementData为空的,size的值也为0,所以就是 size的位置
elementData[size] = e;
// 存储的元素进行 ++
size++;
private void grow()
// 1.新创建一个更大容量的数组
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 使用右移,是为了不出现小数(右移时注意优先级)
Object[] newArr = new Object[newCapacity];
// 2.将原来数组的元素,拷贝到新数组中,索引一一对应
for (int i = 0; i < oldCapacity; i++)
newArr[i] = elementData[i];
// 3.成员变量elementData指向扩容后的新数组
elementData = newArr;
// 可以使用一句话实现
// elementData = Arrays.copyOf(elementData, newCapacity);
@Override
public Object remove(int index)
if (index >= size || index < 0)
throw new IndexOutOfBoundsException("数组索引越界" + index);
Object e = elementData[index];
// 移动元素
int numMoved = size - index - 1;
if (numMoved > 0)
// System.arraycopy(源数组, 从源数组的起始位置开始, 拷贝到目标数组, 到目标数组的开始位置, 复制的个数)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
// 最后的元素指向 null,交给GC
elementData[--size] = null;
return e;
@Override
public boolean remove(Object e)
if (e == null) // 如果为空,比较地址
for (int i = 0; i < size; i++)
if (e == elementData[i])
fastRemove(i);
return true;
else
for (int i = 0; i < size; i++)
if (e.equals(elementData[i]))
fastRemove(i);
return true;
return false;
private void fastRemove(int index)
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
elementData[--size] = null;
@Override
public Object replace(int index, Object e)
if (index >= size || index < 0)
throw new IndexOutOfBoundsException("数组索引越界" + index);
if (e == null)
if (e != elementData[index])
Object oldValue = elementData[index];
elementData[index] = e;
return oldValue;
else
if (!e.equals(elementData[index]))
Object oldValue = elementData[index];
elementData[index] = e;
return oldValue;
return this;
@Override
public Iterator iterator()
return new Itr();
@Override
public String toString()
StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < size; i++)
builder.append(elementData[i] + ",");
if (size > 0)
builder.deleteCharAt(builder.length() - 1);
builder.append("]");
return builder.toString();
private class Itr<T> implements Iterator<T>
// 定义一个光标依次指向数组
int cursor = 0;
@Override
public boolean hasNext()
return cursor != size;
@Override
public T next()
if (cursor >= size)
throw new RuntimeException("元素已经遍历结束");
return (T)elementData[cursor++];
2. 单链表
2.1 单链表技能点
- 存储空间不连续
- 每个结点对应一个数据元素,结点由指针域和数据域组成
- 元素的逻辑关系通过结点间的链接关系体现,逻辑上相邻,物理上不一定相邻
- 按索引查询慢,存储空间大
- 插入、删除速度快
- 有元素才会分配空间,不会有闲置的结点
注:为了对空表、非空表的情况以及首元结点进行统一处理,常增加一个头结点
2.2 手写单链表
测试代码:
public static void main(String[] args)
//创建线性顺序表
List list = new SingleLinkedList();
//向末尾添加元素
list.add("11111");
list.add("aaaaa");
list.add("bbbbb");
list.add("33333");
list.add("22222");
// 添加到指定位置
list.add(3, "AAAAA");
//进行各种操作验证添加
System.out.println(list.size());
System.out.println(list.isEmpty());
System.out.println(list.get(2));
System.out.println(list.contains("44444"));
System.out.println(list.indexOf("22222"));
System.out.println(list.toString());
add 操作及 get 操作的底层思想:
List类:
public interface List
// 返回线性表的大小,即数据元素的个数。
int size();
// 返回线性表中序号为 i 的数据元素
Object get(int i);
// 如果线性表为空返回 true,否则返回 false。
boolean isEmpty();
// 判断线性表是否包含数据元素 e
boolean contains(Object e);
// 返回数据元素 e 在线性表中的序号
int indexOf(Object e);
// 将数据元素 e 插入到线性表中 i 号位置
void add(int i, Object e);
// 将数据元素 e 插入到线性表末尾
void add(Object e);
// 将数据元素 e 插入到元素 obj 之前
boolean addBefore(Object obj, Object e);
// 将数据元素 e 插入到元素 obj 之后
boolean addAfter(Object obj, Object e);
// 删除线性表中序号为 i 的元素,并返回之
Object remove(int i);
// 删除线性表中第一个与 e 相同的元素
boolean remove(Object e);
// 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
Object replace(int i以上是关于JavaLearn#(15)集合提升训练:手写ArrayList单链表LinkedListHashMapHashSet新一代并发集合类的主要内容,如果未能解决你的问题,请参考以下文章
JavaLearn#(16)多线程提升训练:生产者和消费者问题Lock锁ReadWriteLockBlockingQueuevolatile线程池线程同步练习
JavaLearn#(16)多线程提升训练:生产者和消费者问题Lock锁ReadWriteLockBlockingQueuevolatile线程池线程同步练习
JavaLearn # (10)集合List栈队列Set外部比较器哈希表