一、基本类型动态数组的实现
线性结构
几个概念
- e1即索引为0的是首节点,索引为n的是尾节点或尾元素
- e1是e2的前驱节点
- e2是e1的后继节点
线性表:数组,链表,队列,哈希表
Java中的数组是一种顺序存储数据的线性表,元素的内存地址是连续的,且数组的容量是在创建时就已经确定的,且无法修改的。那么如何创建一个动态数组?
动态数组实现
首先创建一个maven项目,增加test依赖,用于对接口或者方法进行单元测试
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
动态数组肯定也是一个数组,也是基于数组的,所以成员变量包含一个数组elements以及数组中元素的数量size, 新建动态数组BasicArrayList,包含成员变量的定义,构造方法,toString()等,先设定动态数组只存放int类型的基本数据
public class BasicArrayList {
private int size;
private int[] elements;
private static final int DEFAULT_CAPATICY = 10;
public ArrayList(int capaticy){
// 如果capaticy < 10 就使用默认的容量,否则使用自定义的容量
capaticy = (capaticy < DEFAULT_CAPATICY) ? DEFAULT_CAPATICY : capaticy;
elements = new int[capaticy];
}
public ArrayList(){
elements = new int[DEFAULT_CAPATICY];
}
@Override
public String toString() {
return "ArrayList{" +
"size=" + size +
", elements=" + Arrays.toString(elements) +
'}';
}
}
需要实现的方法如下:
void clear(); //清除所有元素
int size(); //返回数组中元素的数量
boolean isEmpty(); //判断数组是否为空
boolean contains(T element); //判断数组是否包含
void add(T element); // 在数组尾部添加元素
T get(int index); // 获取指定索引的元素
T set(int index, T element); // 设置指定位置的元素,并返回原来该位置的元素
void add(int index, T element); //在指定位置插入元素
T remove(int index); // 删除指定位置的元素,并返回该元素
int indexOf(T element); //获取指定元素的索引
先实现简单的方法size(),isEmpty(),get(int index)
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public int get(int index){
// 需要对index进行校验
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
}
return elements[index];
}
新建一个测试类BasicArrayListTest,对size()和isEmpty进行测试
public class BasicArrayListTest {
@Test
public void size() {
BasicArrayList arrayList = new BasicArrayList(10);
Assert.assertEquals(0,arrayList.size());
}
@Test
public void isEmpty() {
BasicArrayList arrayList = new BasicArrayList(10);
Assert.assertTrue(arrayList.isEmpty());
}
}
实现set(),indexOf(),contains()和clear()方法
public int set(int index, int element){
// 对index进行校验
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
}
int old = elements[index];
elements[index] = element;
return old;
}
// 定义一个常量,当indexOf()方法找不到元素时返回
privare static int final ELEMENT_NOT_FOUND = -1;
public int indexOf(int element){
// 遍历所有元素
for (int i = 0; i < size; i++) {
if (elements[i] == element) return i;
}
return ELEMENT_NOT_FOUND;
}
public boolean contains(int element){
// 调用indexOf方法查看返回值是否为-1
return indexOf(element) != ELEMENT_NOT_FOUND;
}
public void clear(){
size = 0;
}
remove()方法实现
要注意删除成功后size减少一
public int remove(int index){
// 首先判断index
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
}
int old = elements[index];
// 只遍历需要挪动的部分,不需要遍历整个数组
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
size --;
return old;
}
add方法,默认添加到数组末尾,先不考虑容量的问题
public void add(int element){
elements[size++] = element;
}
测试以上实现的方法
public class BasicArrayListTest {
BasicArrayList arrayList = null;
@Before
public void init(){
arrayList = new BasicArrayList();
arrayList.add(1);
arrayList.add(2);
arrayList.add(4);
arrayList.add(3);
arrayList.add(8);
arrayList.add(5);
arrayList.add(7);
arrayList.add(6);
}
@Test
public void size() {
Assert.assertEquals(8,arrayList.size());
System.out.println("动态数组arrayList的size为:" + arrayList.size());
}
@Test
public void isEmpty() {
Assert.assertFalse(arrayList.isEmpty());
System.out.println("动态数组arrayList是否为空:" + arrayList.isEmpty());
}
@Test
public void get() {
int i = arrayList.get(1);
Assert.assertEquals(2,arrayList.get(1));
System.out.println("arrayList中索引为1的元素是:" + arrayList.get(1));
}
@Test
public void set() {
System.out.println("修改前," + arrayList.toString());
arrayList.set(1,10);
System.out.println("将索引1位置的元素替换为10," + arrayList.toString());
}
@Test
public void indexOf() {
Assert.assertEquals(7,arrayList.indexOf(6));
System.out.println("索引6位置的元素为:" + arrayList.indexOf(6));
}
@Test
public void contains() {
Assert.assertTrue(arrayList.contains(1));
System.out.println("是否包含元素1:" + arrayList.contains(1));
}
@Test
public void clear() {
System.out.println("清空前," + arrayList.toString());
arrayList.clear();
System.out.println("清空后,获取索引0的元素" + arrayList.get(1));
}
@Test
public void remove() {
System.out.println("删除前," + arrayList.toString());
arrayList.remove(1);
System.out.println("删除索引1的元素," + arrayList.toString());
}
@Test
public void add() {
System.out.println(arrayList.toString());
arrayList.add(9);
System.out.println(arrayList.toString());
}
}
add()方法重写,在具体位置增加元素
// 在具体位置增加元素
public void add(int index, int element){
// 首先判断索引,index可以等于size,相当于在末尾添加元素
if (index < 0 || index > size){
throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
}
// 要在指定索引增加元素,要现将这个索引位置及之后的元素往后移动一个索引,腾出位置
// 挪动元素,从最后一个开始挪动,否则后一个元素会被前一个元素覆盖
for (int i = size - 1; i > index; i--) {
elements[i + 1] = elements[i];
}
elements[index] = element;
// 增加size
size ++;
}
测试
@Test
public void add() {
System.out.println(arrayList.toString());
arrayList.add(9);
System.out.println(arrayList.toString());
arrayList.add(2,11);
System.out.println(arrayList.toString());
}
0表示数组中空闲的位置
add(int element)可以优化为
public void add(int element){
// 数组末尾添加元素
add(size,element);
}
解决数组的致命弱点-无法动态扩容
如果数组的容量快被占满,则需要向内存申请一块空间用来保存数据,让变量指向新的内存空间,原来的内存空间没有变量指向,将会被回收,回收前需要将原数组中的数据拷贝到新的数组中,并且新数组的容量相应扩展2倍或者一个根据使用情况适合的倍数
增加一个扩容函数
// 扩容方式一,使用指定扩容,方法中自行作判断何时需要扩容,调用时传入扩容的大小
private void expansionCapacity(int newCapacity){
int oldCapacity = elements.length;
if (oldCapacity >= size + 1) return;
// 创建一个新的容量的数组
// newCapacity = oldCapacity + (oldCapacity >> 1);
int[] newElements = new int[newCapacity];
// 拷贝元素
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
// 指向新的内存空间
elements = newElements;
System.out.println("扩容成功,由" + oldCapacity + "扩展至" + newCapacity);
}
在add(int index, int element)方法中判断完index之后加入该方法
// 在具体位置增加元素
public void add(int index, int element){
// 首先判断索引,index可以等于size,相当于在末尾添加元素
if (index < 0 || index > size){
throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
}
// 如果容量到达临界点会自动扩容,容量扩为原来的1.5倍
expansionCapacity(elements.length + (elements.length >> 1));
// 挪动元素,从最后一个开始挪动,否则后一个元素会被前一个元素覆盖
for (int i = size - 1; i > index; i--) {
elements[i + 1] = elements[i];
}
elements[index] = element;
// 增加size
size ++;
}
再次进行优化
将判断索引抽取为工具类中的静态方法,新建utils包,增加工具类CheckUtils
public class CheckUtils {
private static void outOfBoundary(int index,int size){
throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
}
public static void checkIndex(int index, int size){
if (index < 0 || index >= size){
outOfBoundary(index, size);
}
}
public static void checkIndex4Add(int index, int size){
if (index < 0 || index > size){
outOfBoundary(index, size);
}
}
}
最终BasicArrayList的代码为
public class BasicArrayList {
// 元素的个数
private int size;
// 数组
private int[] elements;
private static final int DEFAULT_CAPATICY = 10;
private static final int ELEMENT_NOT_FOUND = -1;
public BasicArrayList(int capaticy){
// 如果capaticy < 10 就使用默认的容量,否则使用自定义的容量
capaticy = (capaticy < DEFAULT_CAPATICY) ? DEFAULT_CAPATICY : capaticy;
elements = new int[capaticy];
}
public BasicArrayList(){
elements = new int[DEFAULT_CAPATICY];
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public int get(int index){
CheckUtils.checkIndex(index,size);
return elements[index];
}
public int set(int index, int element){
CheckUtils.checkIndex(index,size);
int old = elements[index];
elements[index] = element;
return old;
}
public int indexOf(int element){
for (int i = 0; i < size; i++) {
if (elements[i] == element) return i;
}
return ELEMENT_NOT_FOUND;
}
public boolean contains(int element){
return indexOf(element) != ELEMENT_NOT_FOUND;
}
// 清空即无法访问任何一个元素
public void clear(){
size = 0;
}
public int remove(int index){
// 首先判断index
CheckUtils.checkIndex(index,size);
int old = elements[index];
// 只遍历需要挪动的部分,不需要遍历整个数组
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
size --;
return old;
}
public void add(int element){
// 数组末尾添加元素
add(size,element);
}
// 在具体位置增加元素
public void add(int index, int element){
// 首先判断索引,index可以等于size,相当于在末尾添加元素
CheckUtils.checkIndex4Add(index,size);
// 增加一个元素,如果超过容量就扩容
// 如果容量到达临界点会自动扩容,扩容1.5倍
expansionCapacity(elements.length + (elements.length >> 1));
// 挪动元素,从最后一个开始挪动,否则后一个元素会被前一个元素覆盖
for (int i = size - 1; i > index; i--) {
elements[i + 1] = elements[i];
}
elements[index] = element;
// 增加size
size ++;
}
// 方法中自行作判断,调用时传入扩大的容量
private void expansionCapacity(int newCapacity){
int oldCapacity = elements.length;
if (oldCapacity >= size + 1) return;
// 创建一个新的容量的数组
// newCapacity = oldCapacity + (oldCapacity >> 1);
int[] newElements = new int[newCapacity];
// 拷贝元素
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
// 指向新的内存空间
elements = newElements;
System.out.println("扩容成功,由" + oldCapacity + "扩展至" + newCapacity);
}
private void ensureCapacity(int capacity){
int oldCapacity = elements.length;
if (oldCapacity >= capacity) return;
// 创建一个新的容量的数组
int newCapacity = oldCapacity + (oldCapacity >> 1);
int[] newElements = new int[newCapacity];
// 拷贝元素
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
// 指向新的内存空间
elements = newElements;
System.out.println("扩容成功,由" + oldCapacity + "扩展至" + newCapacity);
}
@Override
public String toString() {
return "ArrayList{" +
"size=" + size +
", elements=" + Arrays.toString(elements) +
'}';
}
}
测试add
@Test
public void add() {
System.out.println(arrayList.toString());
arrayList.add(9);
System.out.println(arrayList.toString());
arrayList.add(2,11);
System.out.println(arrayList.toString());
arrayList.add(12);
System.out.println(arrayList.toString());
}
二、使用泛型
为了让动态数组能够存放多种类型的数据,有必要使用泛型进行改造,使用T表示泛型,创建泛型数组时使用new Object[],然后进行强转,使用T[]来接收,因为Object是所有类的父类,即
T[] elements = (T[]) new Object[];
新建 entity 包,增加一个实体类 Porsche,包含一个 name 属性,getter 方法,setter 方法,toString 方法及有参和无参构造方法
public class Porsche {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Porsche(String name) {
this.name = name;
}
public Porsche() {
}
}
对象内存管理
复制 BasicArrayList,并重命名为 ArrayList,将 int 修改为泛型 T,动态数组保存对象其实是保存的对象访问地址
重构 clear 方法,清空意味着将数组中存放的内存地址的指向全部指向 null。
// 清空即无法访问任何一个元素
public void clear(){
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
remove 方法,需要将最后一个元素中内存地址的指向指为 null
public T remove(int index){
// 首先判断index
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
}
T old = elements[index];
// 只遍历需要挪动的部分,不需要遍历整个数组
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
size --;
// 最后一个指向清空
elements[size] = null;
return old;
}
重构后 ArrayList 的代码为
public class ArrayList<T> {
// 元素的个数
private int size;
// 数组
private T[] elements;
private static final int DEFAULT_CAPATICY = 10;
private static final int ELEMENT_NOT_FOUND = -1;
public ArrayList(int capaticy){
// 如果capaticy < 10 就使用默认的容量,否则使用自定义的容量
capaticy = (capaticy < DEFAULT_CAPATICY) ? DEFAULT_CAPATICY : capaticy;
elements = (T[]) new Object[capaticy];
}
public ArrayList(){
elements = (T[]) new Object[DEFAULT_CAPATICY];
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public T get(int index){
CheckUtils.checkIndex(index,size);
return elements[index];
}
public T set(int index, T element){
CheckUtils.checkIndex(index,size);
T old = elements[index];
elements[index] = element;
return old;
}
public int indexOf(T element){
if (element == null){
for (int i = 0; i < size; i++) {
if (elements[i] == null);
return i;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) return i;
}
}
return ELEMENT_NOT_FOUND;
}
public boolean contains(T element){
return indexOf(element) != ELEMENT_NOT_FOUND;
}
// 清空即无法访问任何一个元素
public void clear(){
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
public T remove(int index){
// 首先判断index
CheckUtils.checkIndex(index,size);
T old = elements[index];
// 只遍历需要挪动的部分,不需要遍历整个数组
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
size --;
// 最后一个指向清空
elements[size] = null;
return old;
}
public void add(T element){
// 数组末尾添加元素
add(size,element);
}
// 在具体位置增加元素
public void add(int index, T element){
// 首先判断索引,index可以等于size,相当于在末尾添加元素
CheckUtils.checkIndex4Add(index,size);
// 如果容量到达临界点会自动扩容
expansionCapacity(elements.length + (elements.length >> 1));
// 挪动元素,从最后一个开始挪动,否则后一个元素会被前一个元素覆盖
for (int i = size; i > index; i--) {
elements[i] = elements[i-1];
}
elements[index] = element;
// 增加size
size ++;
}
private void expansionCapacity(int newCapacity){
int oldCapacity = elements.length;
if (oldCapacity >= size + 1) return;
// 创建一个新的容量的数组
// newCapacity = oldCapacity + (oldCapacity >> 1);
T[] newElements = (T[]) new Object[newCapacity];
// 拷贝元素
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
// 指向新的内存空间
elements = newElements;
System.out.println("扩容成功,由" + oldCapacity + "扩展至" + newCapacity);
}
@Override
public String toString() {
return "ArrayList{" +
"size=" + size +
", elements=" + Arrays.toString(elements) +
'}';
}
}
对于 indexOf 方法,需要判断对象是否相等,因此需要重写对象的 equals 方法,否则没有哪两个对象的内存地址是相同的,Porsche 实体类重写 equals 方法。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Porsche porsche = (Porsche) o;
return name.equals(porsche.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
对于存放 null 的处理,indexOf 方法需要进行判空操作
public int indexOf(T element){
if (element == null){
for (int i = 0; i < size; i++) {
if (elements[i] == null);
return i;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) return i;
}
}
return ELEMENT_NOT_FOUND;
}
测试 ArrayList
public class ArrayListTest {
ArrayList<Porsche> arrayList = null;
@Before
public void init(){
arrayList = new ArrayList<>();
arrayList.add(new Porsche("Porsche 718"));
arrayList.add(new Porsche("550Spyder"));
arrayList.add(new Porsche("Macan"));
arrayList.add(new Porsche("Taycan"));
arrayList.add(new Porsche("Cayenne"));
arrayList.add(new Porsche("Panamera"));
arrayList.add(new Porsche("Porsche 911"));
arrayList.add(new Porsche("Cayman"));
}
@Test
public void size() {
Assert.assertEquals(8,arrayList.size());
System.out.println("动态数组arrayList的size为:" + arrayList.size());
}
@Test
public void isEmpty() {
Assert.assertFalse(arrayList.isEmpty());
System.out.println("动态数组arrayList是否为空:" + arrayList.isEmpty());
}
@Test
public void get() {
Porsche porsche = arrayList.get(1);
Assert.assertEquals(new Porsche("550Spyder"),arrayList.get(1));
System.out.println("arrayList中索引为1的元素是:" + arrayList.get(1));
}
@Test
public void set() {
System.out.println("修改前," + arrayList.toString());
arrayList.set(1,new Porsche("Taycan 2021"));
System.out.println("将索引1位置的元素替换为" + new Porsche("Taycan 2021") + "," + arrayList.toString());
}
@Test
public void indexOf() {
Assert.assertEquals(3,arrayList.indexOf(new Porsche("Taycan")));
System.out.println("索引6位置的元素为:" + arrayList.indexOf(new Porsche("Taycan")));
}
@Test
public void contains() {
Assert.assertTrue(arrayList.contains(new Porsche("Taycan")));
System.out.println("是否包含" + new Porsche("Taycan") + ":" + arrayList.contains(new Porsche("Taycan")));
}
@Test
public void clear() {
System.out.println("清空前," + arrayList.toString());
arrayList.clear();
System.out.println("清空后,获取索引0的元素" + arrayList.get(1));
}
@Test
public void remove() {
System.out.println("删除前," + arrayList.toString());
arrayList.remove(1);
System.out.println("删除索引1的元素," + arrayList.toString());
}
@Test
public void add() {
System.out.println(arrayList.toString());
arrayList.add(new Porsche("Boxster"));
System.out.println(arrayList.toString());
arrayList.add(2,new Porsche("Taycan 2020"));
System.out.println(arrayList.toString());
arrayList.add(new Porsche("Taycan 2022"));
System.out.println(arrayList.toString());
}
}
执行 add 方法
至此,自定义动态数组 ArrayList 完结 🎉