ArrayList是个动态数组, 实际上我们就是实现一个简单的动态数组
占用空间
Java 中数组结构为(以int型数组为例):
- 8 字节markword(记录对象hashcode,垃圾回收时的分代年龄)
- 4 字节 class 指针 (压缩 class 指针的情况 作用去找到对应的类型)
- 4 字节 数组大小 (决定了数组最大容量式2^32)
- 数组元素 + 对齐字节(java中所有对象大小都是8字节的整数倍,不足时要补足)
java数组本身是一个对象,所以主要是由对象头与数据组成
例如
int[] array = {1, 2, 3, 4, 5};
的大小为 40 个字节,组成如下
8 + 4 + 4 + 5*4 + 4(alignment)
代码如下:
package com.itheima.datastructure;
// 动态数组
import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.stream.IntStream;
public class DynamicArray implements Iterable<Integer> {
private int size = 0; //逻辑大小
private int capacity = 8; //容量
private int[] array = {}; //用到再创建
//添加一个值,在array逻辑大小的最后一位
public void addLast(int element) {
//添加逻辑之前 考虑扩容
checkAndGrow();
// array[size] =element;
// size ++;
add(size, element);
}
//扩容及检查越界逻辑
private void checkAndGrow() {
// 创建
if (size == 0) {
array = new int[capacity];
}
else if(size == capacity) {
//进行扩容, Java中(1.5倍) 还有0.618倍 或者翻倍:2
capacity += capacity >> 1;
int[] newArray = new int[capacity];
System.arraycopy(array, 0, newArray, 0, size);
// >> 比 + 优先级高
// capacity = capacity + capacity >> 1;
array = newArray;
}
}
//插入值(假设有效范围内)
public void add(int index, int element) {
//添加逻辑之前 考虑扩容
checkAndGrow();
if(index >= 0 && index< size) {
System.arraycopy(array, index, array, index + 1, size - index);
}
array[index] = element;
size++;
}
//按索引查询
public int get(int index) { //[0..size)
return array[index];
}
//Consumer 是一个返回void 的函数式接口
// 1.遍历方式一 函数式接口
public void forEach1(Consumer<Integer> consumer) {
for (int i = 0; i < size; i++) {
// System.out.println(array[i]);
consumer.accept(array[i]);
// 提供array[i]
// 返回 void
}
}
//2.遍历方式二 迭代器遍历
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
int i = 0;
@Override
public boolean hasNext() { //有没有下一个元素 有 返回真
return i < size;
}
@Override
public Integer next() { // 返回当前元素,并移动到下一个元素
return array[i++];
}
};
}
//3. 遍历方式三 流
public IntStream stream() {
return IntStream.of(Arrays.copyOfRange(array, 0, size)); //[0,size)
}
//删除 (假设有效范围内)
public int remove(int index) { //[0,size)有效区间
int removed = array[index];
if(index < size - 1) {
// System.arraycopy(要copy的数组, 从哪个开始copy, copy到哪个数组,copy到这个数组的那个位置, copy几个);
System.arraycopy(array, index + 1, array,index, size - index - 1);
}
size--;
return removed;
}
}
解释:
这个是整体结构
三个属性:
- private int size = 0; //逻辑大小
- private int capacity = 8; //容量
- private int[] array = {};
这里size表示的是已经用了的容量大小,capacity 带代表总共的容量大小(size + 没用的容量 =capacity)
checkAndGrow方法
这个代码做了两件事:
- 判断数组是否存在(if (size == 0))不存在则创建一个容量为capacity的类型为int的数组;
- 判断是否越界(else if(size == capacity))越界的话就将数组扩容。
解释一下arraycopy方法:
- System.arraycopy(array, 0, newArray, 0, size)
System.arraycopy(要copy的数组, 从哪个开始copy, copy到哪个数组,copy到这个数组的哪个位置, copy几个);
具体怎么扩容的根据代码分析:
//扩容及检查越界逻辑
private void checkAndGrow() {
// 创建
if (size == 0) {
array = new int[capacity];
}
else if(size == capacity) {
//进行扩容, Java中(1.5倍) 还有0.618倍 或者翻倍:2
capacity += capacity >> 1;
int[] newArray = new int[capacity];
System.arraycopy(array, 0, newArray, 0, size);
// >> 比 + 优先级高
// capacity = capacity + capacity >> 1;
array = newArray;
}
}
添加方法
这里有两个方法分代码中有解释作用:
//添加一个值,在array逻辑大小的最后一位
public void addLast(int element) {
//添加逻辑之前 考虑扩容
checkAndGrow();
// array[size] =element;
// size ++;
add(size, element);
}
//插入值(假设有效范围内)
public void add(int index, int element) {
//添加逻辑之前 考虑扩容
checkAndGrow();
if(index >= 0 && index< size) {
System.arraycopy(array, index, array, index + 1, size - index);
}
array[index] = element;
size++;
}
遍历方法(三种)
- 方式一:利用了函数式接口来实现,目的可以让用户重写accept方法,然后把逻辑传回该方法
- 方式二:利用迭代器遍历,用了一个Iterator<Integer>Iterator<Integer>Iterator<Integer> 接口(点进去可以发现是个接口所以return了两个方法,这个应用看不懂的可以先去看一下匿名内部类);
- 方式三:流实现,IntStream.of方法创建有限元流,流又可以通过其中的forEach方法来实现遍历;
//Consumer 是一个返回void 的函数式接口
// 1.遍历方式一 函数式接口
public void forEach1(Consumer<Integer> consumer) {
for (int i = 0; i < size; i++) {
// System.out.println(array[i]);
consumer.accept(array[i]);
// 提供array[i]
// 返回 void
}
}
//2.遍历方式二 迭代器遍历
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
int i = 0;
@Override
public boolean hasNext() { //有没有下一个元素 有 返回真
return i < size;
}
@Override
public Integer next() { // 返回当前元素,并移动到下一个元素
return array[i++];
}
};
}
//3. 遍历方式三 流
public IntStream stream() {
return IntStream.of(Arrays.copyOfRange(array, 0, size)); //[0,size)
}
测试遍历代码:
//遍历方式一
dynamicArray.forEach1(System.out::println);
System.out.println("================ ");
//遍历方式二
for (Integer element : dynamicArray) {
System.out.println(element);
}
//遍历方式三
System.out.println("================ ");
dynamicArray.stream().forEach(System.out::println);
方式一测试的三种写法:
// 1
dynamicArray.forEach1(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
// 2
dynamicArray.forEach1(e ->System.out.println(e));
// 3
dynamicArray.forEach1(System.out::println);
三种方法都效果都一样;
删除
//删除 (假设有效范围内)
public int remove(int index) { //[0,size)有效区间
int removed = array[index];
if(index < size - 1) {
System.arraycopy(array, index + 1, array,index, size - index - 1);
}
size--;
return removed;
}
注意:这里的插入与删除都式假设有效范围内(意思就是在[0,size)范围内,如果超出的话插入与删除都为一种异常情况,需要自己完善判断处理)。