顺序表定义
顺序表是在计算机内中以数组的形式保存的线性表,线性表的顺序是指一组地址连续的存储单元。
顺序表的实现
顺序表API设计
| 类名 | MyArrayList |
|---|---|
| 成员变量 | private T[] elementData;//存储元素的数组 private int size; //长度 |
| 构造方法 | MyArrayList(int capacity);//创建容量为capacity的MyArrayList对象 |
| 成员方法 | public void clear();//清空 public boolean isEmpty();//判断线性表是否为null public int length();//获取线性表的长度 public T get(int i); //获取线性表中第i个元素的值 public void insert(T t);//向线性表插入元素t public void insert(int i,T t);//向线性表第i个插入元素t public T remove(int i);//删除并返回线性表中第i个数据元素 public int indexOf(T t);//返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返-1 |
代码实现
package com.black.cat.algorithm.array;
import java.util.Iterator;
/**
* @Author blackcat
* @create 2021/12/6 21:36
* @version: 1.0
* @description:顺序表API实现
*/
public class MyArrayList<T> implements Iterable<T> {
private T[] elementData;//存储元素的数组
private int size; //长度
//创建容量为capacity的MyArrayList对象
MyArrayList(int capacity) {
elementData = (T[]) new Object[capacity];
this.size = 0;
}
//清空
public void clear() {
//数组中的数据为null
for (int i = 0; i < size; i++)
elementData[i] = null;
//长度为0
this.size = 0;
}
//判断线性表是否为null
public boolean isEmpty() {
return size == 0;
}
//获取线性表的长度
public int length() {
return size;
}
//获取线性表中第i个元素的值
public T get(int i) {
if (i < 0 || i >= size) {
throw new IllegalArgumentException("Get failed. Index is illegal.");
}
return elementData[i];
}
//向线性表插入元素t
public void insert(T t) {
if (size == elementData.length) {
//扩容
resize(2 * elementData.length);
}
//当前位置
elementData[size] = t;
//size +1
size++;
}
//向线性表第i个插入元素t<br/>
public void insert(int i, T t) {
if (i < 0 || i > size)
throw new IllegalArgumentException("insert failed. Require index >= 0 and index <= size.");
if (size == elementData.length) {
//扩容
resize(2 * elementData.length);
}
//长度++
size++;
//i 后面的元素 往后移动i+1
for (int j = size; j > i; j--) {
elementData[j] = elementData[j - 1];
}
//i位置插入t
elementData[i] = t;
}
//删除并返回线性表中第i个数据元素
public T remove(int i) {
if (i < 0 || i >= size)
throw new IllegalArgumentException("Remove failed. Index is illegal.");
//获取第i的元素
T t = get(i);
//把i+1后面的元素往前移动一位
for (int j = i + 1; j < size; j++) {
elementData[j - 1] = elementData[j];
}
//长度减1
size--;
elementData[size] = null;
//缩容
//为了防止复杂度震荡和安全性 因为缩小到一定的时候容量可能为1
if (size == elementData.length / 4 && elementData.length / 2 != 0)
resize(elementData.length / 2);
return t;
}
//返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返-1
public int indexOf(T t) {
if (t == null) {
throw new IllegalArgumentException("indexOf failed. t is illegal.");
}
for (int i = 0; i < size; i++) {
if (t.equals(elementData[i])) {
return i;
}
}
return -1;
}
//扩容
private void resize(int newCapacity) {
T[] newData = (T[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = elementData[i];
}
elementData = newData;
}
@Override
public Iterator<T> iterator() {
return new Itr();
}
class Itr implements Iterator<T> {
private int cursor;
public Itr() {
this.cursor = 0;
}
@Override
public boolean hasNext() {
return cursor < size;
}
@Override
public T next() {
return elementData[cursor++];
}
}
public static void main(String[] args) {
//创建顺序表对象
MyArrayList<String> list = new MyArrayList<>(10);
// 测试插入
list.insert("姚明");
list.insert("科比");
list.insert("麦迪");
list.insert(1, "詹姆斯");
// 测试获取
String getResult = list.get(1);
System.out.println("获取索引1处的结果为:" + getResult);
for (String str:list) {
System.out.println(str);
}
// 测试删除
String removeResult = list.remove(0);
System.out.println("删除的元素是:" + removeResult);
System.out.println("删除后的元素");
for (String str:list) {
System.out.println(str);
}
// 测试清空
list.clear();
System.out.println("清空后的线性表中的元素个数为:" + list.length());
}
}
ArrayList的实现
1.概述
ArrayList 的底层是数组,相当于动态数组(通过System.arrayCopy()方法复制数组),支持随机访问。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2.继承关系
2.1 Serializable 标记接口
java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
序列化:将对象的数据写入到文件(写对象)
反序列化:将文件中对象的数据读取出来(读对象)
import java.io.*;
/**
* @Author blackcat
* @version: 1.0
* @description:序列化和反序列化
* 对象必须实现Serializable 才能写入
*/
public class SerializableTest {
public static void main(String[] args) throws Exception {
//调用写数据的方法
writeObject();
//调用读数据的方法
readObject();
}
//定义方法将对象数据写入到文件
private static void writeObject() throws IOException {
//创建对象操作流 --> 序列化 (将对象的数据写入到文件)
String path = System.getProperty("user.dir");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path+"\\obj.txt"));
//创建学生对象
Student s1 = new Student("张三",32);
//调用对象操作流写对象的方法,将对象的数据写入到文件
oos.writeObject(s1);
//关闭流
oos.close();
}
//定义方法将文件的数据读取出来
private static void readObject() throws IOException, ClassNotFoundException {
//创建对象操作流 --> 反序列化 (将数据从文件中读取出来)
String path = System.getProperty("user.dir");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path+"\\obj.txt"));
//调用方法读取一个对象
Student stu = (Student) ois.readObject();
//关闭流
ois.close();
//输出读取到对象的数据
System.out.println(stu);
}
}
class Student implements Serializable{
//姓名
private String name;
//年龄
private Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Student{name='");
sb.append(this.name);
sb.append("', age=");
sb.append(this.age);
sb.append("}");
return sb.toString();
}
}
2.2 Cloneable 标记接口
Cloneable ,即覆盖了函数clone(),能被克隆。
import java.util.ArrayList;
/**
* @Author blackcat
* @version: 1.0
* @description:clone 例子
*/
public class ArrayListClone {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("人生就是旅途");
list.add("也许终点和起点会重合");
list.add("但是一开始就站在起点等待终点");
list.add("那么其中就没有美丽的沿途风景和令人难忘的过往");
//调用方法进行克隆
Object o = list.clone();
System.out.println(o == list);
System.out.println(o);
System.out.println(list);
}
}
clone()源码
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
2.3 RandomAccess标记接口
RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。
/*As a rule of thumb, a
* <tt>List</tt> implementation should implement this interface if,
* for typical instances of the class, this loop:
* <pre>
* 随机访问
* for (int i=0, n=list.size(); i < n; i++)
* list.get(i);
* </pre>
* runs faster than this loop:
* <pre>
* 顺序访问
* for (Iterator i=list.iterator(); i.hasNext(); )
* i.next();
* </pre>
*/
//Collections
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
//采用for循环遍历
return Collections.indexedBinarySearch(list, key);
else
//采用迭代器遍历
return Collections.iteratorBinarySearch(list, key);
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Author blackcat
* @version: 1.0
* @description:随机访问与顺序访问的对比
*/
public class RandomAccessTest {
public static void main(String[] args) {
//创建ArrayList集合
List<String> list = new ArrayList<>();
//添加10W条数据
for (int i = 0; i < 100000; i++) {
list.add(i + "a");
}
System.out.println("----通过索引(随机访问:)----");
long startTime = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
//仅仅为了演示取出数据的时间,因此不对取出的数据进行打印
list.get(i);
}
long endTime = System.currentTimeMillis();
System.out.println("随机访问: " + (endTime - startTime));
System.out.println("----通过迭代器(顺序访问:)----");
startTime = System.currentTimeMillis();
Iterator<String> it = list.iterator();
while (it.hasNext()) {
//仅仅为了演示取出数据的时间,因此不对取出的数据进行打印
it.next();
}
endTime = System.currentTimeMillis();
System.out.println("顺序访问: " + (endTime - startTime));
}
}
//----通过索引(随机访问:)----
//随机访问: 4
//----通过迭代器(顺序访问:)----
//顺序访问: 5
2.4 AbstractList抽象类
该类提供实现List接口的骨架实现,以最小实现由随机存储支持接口所需的工作量。对于顺序访问数据应该使用AbstractSequentialList 优先于此类.
3. ArrayList源码分析
3.1 构造方法
| Constructor | Constructor描述 |
|---|---|
| ArrayList() | 无参构造函数 |
| ArrayList(int initialCapacity) | 创建一个初试容量的、空的ArrayList |
| ArrayList(Collection<? extends E> c) | 构造一个包含指定集合的元素的列表 |
public class ArrayList<E> {
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//一个空数组,当用户指定ArrayList容量为0时,返回该数组
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 一个空数组实例
* 当用户没有指定 ArrayList 的容量时(即调用无参构造函数),返回的是该数组==>刚创建一个
* ArrayList 时,其内数据量为 0。
* 当用户第一次添加元素时,该数组将会扩容,变成默认容量为 10(DEFAULT_CAPACITY) 的一个数组
* 它与EMPTY_ELEMENTDATA 的区别就是:该数组是默认返回的,而后者是在用户指定容量为0时返回
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//当前数据对象存放地方,当前对象不参与序列化
transient Object[] elementData;
//ArrayList实际存储的数据数量
private int size;
//继承于AbstractList
//集合数组修改次数的标识
protected transient int modCount = 0;
/**
* 无参构造函数:
* 创建一个空的 ArrayList,此时其内数组缓冲区 elementData = {}, 长度为0
* 当元素第一次被加入时,扩容至默认容量 10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 创建一个初试容量的、空的ArrayList
* @param initialCapacity 初始容量
* @throws IllegalArgumentException 当初试容量值非法(小于0)时抛出
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 创建一个包含collection的ArrayList
* @param c 要放入 ArrayList 中的集合,其内元素将会全部添加到新建的 ArrayList 实例中
* @throws NullPointerException 当参数 c 为 null 时抛出异常
*/
public ArrayList(Collection<? extends E> c) {
//把集合传化成Object[]数组
elementData = c.toArray();
//转化后的数组长度赋给当前ArrayList的size,并判断是否为0
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//c.toArray可能不会返回 Object[],可以查看 java 官方编号为 6260652 的 bug
if (elementData.getClass() != Object[].class)
// 若 c.toArray() 返回的数组类型不是 Object[],则利用 Arrays.copyOf();
//来构造一个大小为 size 的 Object[] 数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 替换空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
}
Arrays.copyof(char[] original, int newLength)
/*
*复制指定的数组,截断或填充空字符,以便复制具有指定的长度
* original:要复制的数组
*newLength:要返回的数组的长度
*/
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
/*
*将指定源数组的数组,从指定位置,复制到指定数组的指定位置,要赋值的数组元素数量等于length。
* src:源数组
* srcPos:源数组中的起始位置
* dest:目标数组
* destPos:目标数组的起始位置
* length:要赋值的数组元素数量
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
3.2 添加方法
| 方法名 | 描述 |
|---|---|
| public boolean add(E e) | 将指定的元素追加到此列表的末尾。 |
| public void add(int index, E element) | 在此列表中的指定位置插入指定的元素。 |
| public boolean addAll(Collection<?extends E> c) | 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。 |
| public boolean addAll(i nt index,Collection<? extends E> c) | 将指定集合中的所有元素插入到此列表中,从指定的位置 |
public class ArrayList<E> {
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);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//实际修改集合次数++
modCount++;
//判断最小容量 - 数组长度是否大于 0
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//核心扩容方法
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
//记录数组的实际长度
int oldCapacity = elementData.length;
//核心扩容算法 原容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断新容量 - 最小容量 是否小于 0
if (newCapacity - minCapacity < 0)
//还是将最小容量赋值给新容量
newCapacity = minCapacity;
//判断新容量-最大数组大小 是否>0,如果条件满足就计算出一个超大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 调用数组工具类方法,创建一个新数组,将新数组的地址赋值给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
-----------------------------------------------------------
public void add(int index, E element) {
//添加范围检查
rangeCheckForAdd(index);
//调用方法检验是否要扩容,且让增量++
ensureCapacityInternal(size + 1); // Increments modCount
//移动数组
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
---------------------------------------------------------
public boolean addAll(Collection<? extends E> c) {
//把集合的元素转存到Object类型的数组中
Object[] a = c.toArray();
//记录数组的长度
int numNew = a.length;
//调用方法检验是否要扩容,且让增量++
ensureCapacityInternal(size + numNew); // Increments modCount
//调用方法将a数组的元素拷贝到elementData数组中
System.arraycopy(a, 0, elementData, size, numNew);
//集合的长度+=a数组的长度
size += numNew;
//只要a数组的长度不等于0,即说明添加成功
return numNew != 0;
}
---------------------------------------------------------
public boolean addAll(int index, Collection<? extends E> c) {
//校验索引
rangeCheckForAdd(index);
//将数据源转成数组
Object[] a = c.toArray();
//记录数组的长度
int numNew = a.length;
//调用方法检验是否要扩容,且让增量++
ensureCapacityInternal(size + numNew); // Increments modCount
//numMoved:代表要移动元素的个数
int numMoved = size - index;
if (numMoved > 0)
// 第一次数组复制,从elementData中的index位置开始,复制到index + numNew位置上, //复制numMoved个元素
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 第二次数组复制,从a 数组中的第0个位置开始,复制到elementData第index位置上你,复制 //numNew个元素
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
//只要a数组的长度不等于0,即说明添加成功
return numNew != 0;
}
}
3.3 删除方法
public class ArrayList<E> {
public E remove(int index) {
//范围校验
rangeCheck(index);
//增量+1
modCount++;
//将index对应的元素赋值给 oldValue
E oldValue = elementData(index);
//计算集合需要移动元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//如果需要移动元素个数大于0,就使用arrayCopy方法进行拷贝
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将源集合最后一个元素置为null,尽早让垃圾回收机制对其进行回收
elementData[--size] = null; // clear to let GC do its work
//返回被删除的元素
return oldValue;
}
-------------------------------------------------
public boolean remove(Object o) {
//判断要删除的元素是否为null
if (o == null) {
//遍历集合
for (int index = 0; index < size; index++)
//判断集合的元素是否为null
if (elementData[index] == null) {
//如果相等,调用fastRemove
fastRemove(index);
return true;
}
} else {
//遍历集合
for (int index = 0; index < size; index++)
//如果相等,调用fastRemove
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
//增量++
modCount++;
//计算集合需要移动元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//如果需要移动元素个数大于0,就使用arrayCopy方法进行拷贝
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将源集合最后一个元素置为null,尽早让垃圾回收机制对其进行回收
elementData[--size] = null; // clear to let GC do its work
}
}
3.4 修改方法
public class ArrayList<E> {
public E set(int index, E element) {
//范围校验
rangeCheck(index);
//取出index对应的元素
E oldValue = elementData(index);
//将element直接覆盖index对应的元素
elementData[index] = element;
//返回被覆盖的元素
return oldValue;
}
}
3.5 获取方法
public class ArrayList<E> {
public E get(int index) {
//范围校验
rangeCheck(index);
return elementData(index);
}
}
3.6 迭代器方法
public class ArrayList<E> {
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
// 下一个要返回元素的索引
int cursor; // index of next element to return
// 最后一个返回元素的索引
int lastRet = -1; // index of last element returned; -1 if no such
//将实际修改集合次数赋值给预期修改次数
//在迭代的过程中,只要实际修改次数和预期修改次数不一致就会产生并发修改异常
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
//把下一个元素的索引赋值给i
int i = cursor;
//判断是否有元素
if (i >= size)
throw new NoSuchElementException();
//将集合底层存储数据的数组赋值给迭代器的局部变量 elementData
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//每次成功获取到元素,下一个元素的索引都是当前索引+1
cursor = i + 1;
//返回元素
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
//如果预期修改次数 和 实际修改次数不相等 就产生并发修改异常
//Fail-Fast 快速失败 遍历的同时不能进行插入
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
3.7 清空方法
public class ArrayList<E> {
public void clear() {
//实际修改集合次数++
modCount++;
//遍历集合,将集合每一个索引对应位置上的元素都置为null,尽早让其释放
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
//集合长度为0
size = 0;
}
}
3.8判断集合是否为空
public class ArrayList<E> {
public boolean isEmpty() {
return size == 0;
}
}
3.9 包含方法
public class ArrayList<E> {
public boolean contains(Object o) {
//调用indexOf方法进行查找
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
//如果元素是null,也进行遍历操作
//因为集合中有可能够会存储null
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//不存在返回-1
return -1;
}
}
参考资料
www.bilibili.com/video/BV1iJ… Java数据结构与java算法
segmentfault.com/a/119000000… ArrayList源码浅析
www.bilibili.com/video/BV1gE… ArrayList源码原理