插入
在研究动态数组的插入实现,如果是将该插入元素插入到数组末尾,也就是尾插法
public class Test1 {
int size=0;//有效元素个数
int capacity=8;//容量
int [] array=new int[capacity];
//尾插法
public void addLast(int element){
array[size]=element;
size++;
}
在实现了尾插法之后,我们想实现可以进行在数组里面各个位置插入的方法
我们的思路是先将原数组进行拷贝,从要插入的index开始将之后的所有元素进行复制,复制到塔本身index+1的下标位置,然后再把要插入的元素放在空出来的index上
public void add(int index,int element){
if(index>=0&&index<size){
System.arraycopy(array,index,array,index+1,size-index);
//第一个参数是被拷贝的数组,第二个参数是要拷贝的开始值的下标,第三个是被复制到的数组,第四个是被复制到的数组开始赋值的初始下标,第五个参数是复制的长度
array[size]=element;
size++;
}else if(index==size){//如果相等,就执行尾插法
array[size]=element;
size++;
}
}
注意:arraycopy的参数(被拷贝的数组,开始拷贝的起始坐标,被复制的数组,被复制到的数组开始复制的初始下标,复制的长度)
我们对于上述代码进行简化
public void add(int index,int element){
if(index>=0&&index<=size){
System.arraycopy(array,index,array,index+1,size-index);
//第一个参数是被拷贝的数组,第二个参数是要拷贝的开始值的下标,第三个是被复制到的数组,第四个是被复制到的数组开始赋值的初始下标,第五个参数是复制的长度
}
array[index]=element;
size++;
}
我们再进行思考,如何将第一步写的尾插法更加高效,只要将add方法写进尾插法,将size作为要插入的下标参数即可
public void addLast(int element){
//array[size]=element;
//size++;
add(size,element);
}
遍历
在遍历的方法中,我们可以通过简单的循环打印数组进行打印,但功能比较单一,如果需要存储数据或者上传数据库的话我们就需要更加灵活的操作比如 函数值接口作为参数,这样在调用方法的时候可以根据自己的需求完成功能的实现
函数式接口
因为我们不需要返回值,在函数式接口中,Consumer方法不需要返回值,
public int get(int index){
return array[index];
}
public void forEach(Consumer<Integer> consumer){
for(int i=0;i<size;i++){
consumer.accept(array[i]);
}
}
我们对于该代码进行调用测试,
public static void main(String[] args) {
Test1 test1=new Test1();
test1.addLast(1);
test1.addLast(2);
test1.addLast(3);
test1.addLast(4);
test1.addLast(5);
test1.addLast(6);
test1.add(2,2);
test1.forEach((element)->{
System.out.println(element);
});
}
我们来看运行结果
迭代器(实现Lterable接口)
我们继承Iterable接口,在其的泛型里面使用Integer,然后实现他的抽象方法iterator
在这个方法中,hasNext是判断查找元素是否有下一个元素,next是用来返回有下一个元素时候的数组的元素,比如size=1的时候 i=0,因为始终i都是<size的
我们来看方法实现
public class Test1 implements Iterable<Integer>{
int size=0;//有效元素个数
int capacity=8;//容量
int [] array=new int[capacity];
@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++];
}
};
}
}
我们再来看如何去调用这个方法
for (Integer element:test1){
System.out.println(element);
}
我们用增强for循环调用该方法,Integer是需要遍历得到的数据类型,因为实现了接口,所以会自动调用Lterator接口的抽象方法,在每次循环的时候调用hasnext()和next()方法,进行遍历。我们来看运算结果
流
我们通过流IntStream对动态数组进行遍历存储
public IntStream stream(){
return IntStream.of(Arrays.copyOfRange(array,0,size));//确定有效范围,不会返回无效元素
//return IntStream.of(array);
}
调用代码如下
test1.stream().forEach(element -> {
System.out.println(element);
});
最终返回结果与前面两种相同
删除
思路和之前的插入类似,我们将删除的元素后面存在的有效元素复制之后并且前移,再讲size--,即删除了最后一个元素,代码如下
public int remove(int index){
int removed=array[index];
if (index < size - 1) {
System.arraycopy(array,index+1,array,index,size-index-1);
}
System.arraycopy(array,index+1,array,index,size-index-1);
size--;
return removed;
}
我们可以观察arraycopy里面的参数,为什么长度是size-index-1
我们来看如图,假设我们要移除index=2的元素,我们需要复制的长度是2 (后面的元素4,5)。
所以长度为size-index-1,而if条件中的判断作用,是判断删除的元素是否为最后一个元素,为最后一个元素的时候不再需要进行复制,直接删除就可以。
我们进行调用测试
public static void main(String[] args) {
Test1 test1=new Test1();
test1.addLast(1);
test1.addLast(2);
test1.addLast(3);
test1.addLast(4);
test1.addLast(5);
test1.addLast(6);
/*for (Integer element:test1){
System.out.println(element);
}*/
/*test1.stream().forEach(element -> {
System.out.println(element);
});*/
int removed=test1.remove(2);
assertEquals(3,removed);
}
得出的结果没有断言失败,说明正确
扩容
关于扩容的实现思路也很简单,在size=capacity的时候将原数组复制到一个容量更大的数组,里面就行
我们来进行代码实现,我们将扩容功能先写在add方法中。
public void add(int index,int element){
//容量检查
if(size==capacity){
capacity+=capacity>>>1;
int[] newArray=new int[capacity];
System.arraycopy(array,0,newArray,0,size);
array=newArray;
}
//添加逻辑
if(index>=0&&index<=size){
System.arraycopy(array,index,array,index+1,size-index);
//第一个参数是被拷贝的数组,第二个参数是要拷贝的开始值的下标,第三个是被复制到的数组,第四个是被复制到的数组开始赋值的初始下标,第五个参数是复制的长度
}
array[index]=element;
size++;
}
我们下一步可以把这个扩容方法进行封装单独写出来
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++;
}
private void checkandgrow() {
//容量检查
if(size==capacity){
capacity+=capacity>>>1;
int[] newArray=new int[capacity];
System.arraycopy(array,0,newArray,0,size);
array=newArray;
}
}
我们可以继续优化简化我们的代码
我们可以写一种懒人写法来使我们的空间复杂度降低,也就是在一开始设置array为空数组
然后将创建数组的方法在add里面的扩容方法实现,也就是加一个初始判断。
int size=0;//有效元素个数
int capacity=8;//容量
int [] array={};
增加初始判断进行扩容,即在size=0的时候直接将数组扩容
private void checkAndGrow() {
if(size==0){
array=new int[capacity];
}else if(size==capacity){
capacity+=capacity>>>1;
int[] newArray=new int[capacity];
System.arraycopy(array,0,newArray,0,size);
array=newArray;
}
}
以上就是动态数组中的添加,插入,遍历,删除,扩容的讲解,谢谢大家浏览。
关于里面涉及对的函数式编程和流的操作,后期会持续更新