由浅到深带你详谈Java实现数组扩容的三种方式【建议收藏】

目录

    • 1.新建一个数组,把原来数组的内容搬到新数组中。
    • 2.使用system.arraycopy()
    • 3.使用java.util.Arrays.copyOf()

      1.新建一个数组,把原来数组的内容搬到新数组中。

        这种方法实现的思路是:先新建一个数组(前提条件是长度得比原来的长),然后把原来数组的内容搬到新数组中.

      案例分析:

      public static void main(String[] args) {// 利用函数的方法进行数组的扩充
      	// 定义一个小型的数组
      	int[] a = { 1, 2, 3, 4, 5 };
      	// 调用扩容函数
      	a= expand1(a);
      	// 测试是否扩容完成,输出此时数组a中的值
      	for (int i = 0; i < a.length; i++) {System.out.println("数组元素:" + a[i]);
      	}
      }
      // 扩容函数1,
      public static int[] expand1(int a[]) {// 定义一个新数组b,并为其赋值长度为数组a的二倍
      	int b[] = new int[a.length * 2];
      	// 将数组a的元素循环遍历到数组b中
      	for (int i = 0; i < a.length; i++) {b[i] = a[i];
      	}
      	System.out.println("数组扩容方法1:" + Arrays.toString(b));
      	return b;
      }
      

      输出结果:

      数组扩容方法1:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

      数组元素:1

      数组元素:2

      数组元素:3

      数组元素:4

      数组元素:5

      数组元素:0

      数组元素:0

      数组元素:0

      数组元素:0

      数组元素:0

      2.使用system.arraycopy()

      常用作数组的扩容,如ArrayList底层数组的扩容

      方法解析:

      public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
      

        通过上面的源码可以看到,arraycopy它是一个静态本地方法,由虚拟机实现,效率自然比用java一个个复制高。

        将指定源数组中的数组从指定位置开始复制到目标数组的指定位置。 阵列组件的一个子序列被从通过引用的源阵列复制src被引用的目标阵列dest 。 复制的组件数等于length参数。 在位置的部件srcPos通过srcPos+length-1源阵列中的被复制到的位置destPos通过destPos+length-1分别,目的地阵列。

      大白话总结:

        从源数组src取元素,范围为下标srcPos到srcPos+length-1,取出共length个元素,存放到目标数组中,存放位置为下标destPos到destPos+length-1。简单说,就是数组间的复制。

      参数解释:

      • src 源数组;
      • srcPos 源数组中的起始位置;
      • dest 目标数组;
      • destPos 目标数组中的起始位置;
      • length 要复制的数组元素的数量

        注意事项:

        • 1.若src和dest参数引用相同的数组对象,则执行复制,就好像位置 srcPos 到 srcPos + length - 1 的组件首次复制到具有 length 组件的临时数组,然后临时数组的内容被复制到位置 destPos 通过目标数组的 destPos + length - 1 。
        • 2.若 src 是 null ,则抛java.lang.NullPointerException异常,并且不修改目标阵列。案例演示如下:
          public static void main(String[] args) {int[] src = { 1, 2, 3, 4 };
          	int[] dest = null;
          	System.arraycopy(src, 0, dest, 0, 4);//抛异常:java.lang.NullPointerException
          	for (Object o : dest) {System.out.println(o);
          	}
          }
          

          解析:

          src源数组共有4个元素,索引是0-3,

          dest目标数组为null,

          当执行System.arraycopy(src, 0, dest, 0, 4);时,

          第一步:由于dest为null,arraycopy方法检查到null操作,所以就会抛空指针异常。(如果你想深入理解,可以去了解一下arraycopy的底层源码。)

          • 3.srcPos 的设置主要看 destPos 和 length ,若 srcPos 该位置往后的元素少于要复制的数组元素的数量(即 srcPos + length 的长度已经超过源数组元素的数量/个数,不是长度/索引;或者 destPos + length 的长度已经超过目标数组元素的数量/个数,不是长度/索引;),则会抛java.lang.ArrayIndexOutOfBoundsException异常。案例演示如下:
            //src.length < srcPos + length
            public static void main(String[] args) {String[] src = { "aa", "bb", "cc", "dd" };
            	String[] dest = new String[] { "a", "b", "c", "d", "e" };
            	System.arraycopy(src, 1, dest, 1, 4);//抛异常java.lang.ArrayIndexOutOfBoundsException
            	for (Object o : dest) {System.out.println(o);
            	}
            }
            

            解析:

            src源数组共有4个元素,索引是0-3,

            dest目标数组共有5个元素,索引是0-4,

            当执行System.arraycopy(src, 1, dest, 1, 4);时,

            第一步:srcPos为1,即从源数组(src)中,从下标1开始取,

            第二步:length为4,即取4个元素,也就是src[1]-src[4],即从“bb”之后要取4个元素,这时源数组(src)只有“bb”、“cc”、“dd” 3个元素,明显不够4个元素,所以就会抛数组索引越界异常。


            //dest.length < destPos + length
            public static void main(String[] args) {String[] src = { "aa", "bb", "cc", "dd" };
            	String[] dest = new String[] { "a", "b", "c", "d", "e" };
            	System.arraycopy(src, 0, dest, 2, 4);//抛异常java.lang.ArrayIndexOutOfBoundsException
            	for (Object o : dest) {System.out.println(o);
            	}
            }
            

            解析:

            src源数组共有4个元素,索引是0-3,

            dest目标数组共有5个元素,索引是0-4,

            当执行System.arraycopy(src, 0, dest, 2, 4);时,

            第一步:srcPos为0,即从源数组(src)中,从下标0开始取,

            第二步:length为4,即取4个元素,也就是src[0]-src[3],即从“aa”之后要取4个元素;分别是“aa”、“bb”、“cc”、“dd” 4个元素

            第三步:把从源数组(src)取出的数(“aa”、“bb”、“cc”、“dd” 4个元素),按顺序,存放到目标数组(dest)中,从下标2开始存,存4个,也就是dest[2]-dest[5],由于dest目标数组最大索引为4,只能存放“aa”、“bb”、“cc”,存不下“dd”,即不够容量存放4个数,所以就会抛数组索引越界异常。

            • 4.若 dest 是 null ,则抛java.lang.NullPointerException异常,同上/参考2。
            • 5.destPos 的设置主要看 srcPos 和 length ,若 destPos 该位置容纳不下要复制的数组元素(即 destPos + length 的长度已经超过目标数组元素的数量/个数,不是长度/索引;或者 srcPos + length 的长度已经超过源数组元素的数量/个数,不是长度/索引;),则会抛java.lang.ArrayIndexOutOfBoundsException异常。同上/参考3。
            • 6.length 的设置主要看 srcPos 和 destPos,若 length 超出源数组中的起始位置之后的元素数量(即 srcPos 往后的元素数量/个数,不是长度/索引 )或者 length 超出目标数组中的起始位置的元素数量(即 destPos 往后的元素数量/个数,不是长度/索引),则会抛java.lang.ArrayIndexOutOfBoundsException异常。
              // length > srcPos.length - srcPos[index]
              public static void main(String[] args) {String[] src = { "aa", "bb", "cc", "dd" };
              	String[] dest = new String[] { "a", "b", "c", "d", "e" };
              	System.arraycopy(src, 2, dest, 2, 3);// 抛异常java.lang.ArrayIndexOutOfBoundsException
              	for (Object o : dest) {System.out.println(o);
              	}
              }
              

              解析:

              src源数组共有4个元素,索引是0-3,

              dest目标数组共有5个元素,索引是0-4,

              当执行System.arraycopy(src, 2, dest, 2, 3);时,

              第一步:srcPos为2,即从源数组(src)中,从下标2开始取,

              第二步:length为3,即取3个元素,也就是src[2]-src[4],即从“cc”之后要取3个元素,这时源数组(src)只有“cc”、“dd” 2个元素,明显不够3个元素,所以就会抛数组索引越界异常。


              // length > destPos.length - destPos[index]
              public static void main(String[] args) {String[] src = { "aa", "bb", "cc", "dd" };
              	String[] dest = new String[] { "a", "b", "c", "d", "e" };
              	System.arraycopy(src, 2, dest, 4, 2);// 抛异常java.lang.ArrayIndexOutOfBoundsException
              	for (Object o : dest) {System.out.println(o);
              	}
              }
              

              解析:

              src源数组共有4个元素,索引是0-3,

              dest目标数组共有5个元素,索引是0-4,

              当执行System.arraycopy(src, 2, dest, 4, 2);时,

              第一步:srcPos为2,即从源数组(src)中,从下标2开始取,

              第二步:length为2,即取2个元素,也就是src[2]-src[3],即从“cc”之后要取2个元素,分别是“cc”、“dd” 2个元素;

              第三步:把从源数组(src)取出的数(“cc”、“dd” 2个元素),按顺序,存放到目标数组(dest)中,从下标4开始存,存2个,也就是dest[4]-dest[5],由于dest目标数组最大索引为4,只能存放“cc”,存不下“dd”,即不够容量存放2个数,所以就会抛数组索引越界异常。

              • 7.当srcPos 、destPos 和 length 任意一个参数小于0时,则会抛java.lang.ArrayIndexOutOfBoundsException异常。
                //srcPos < 0 || destPos < 0 || length < 0
                public static void main(String[] args) {String[] src = { "aa", "bb", "cc", "dd" };
                	String[] dest = new String[] { "a", "b", "c", "d", "e" };
                	System.arraycopy(src, -1, dest, 1, 1);//抛异常java.lang.ArrayIndexOutOfBoundsException
                	for (Object o : dest) {System.out.println(o);
                	}
                }
                

                解析:

                src源数组共有04个元素,索引是0-3,

                dest目标数组共有5个元素,索引是0-4,

                当执行System.arraycopy(src, -1, dest, 1, 1);时,

                第一步:srcPos为-1,不在索引0-3范围内,所以就会抛数组索引越界异常。

                📌补充点:

                • 8.当源数组和目标数组两组数据的元素类型不匹配时,则会抛java.lang.ArrayStoreException异常,并且不修改目标数组。
                  public static void main(String[] args) {int[] src = { 1, 2, 3, 4 };
                  	Object[] dest = new Object[3];
                  	System.arraycopy(src, 0, dest, 0, 3);//抛异常java.lang.ArrayStoreException
                  	for (Object o : dest) {System.out.println(o);
                  	}
                  }
                  

                  public static void main(String[] args) {Object[] src = { 1, 2, 3, 4 };
                  	int[] dest = new int[3];
                  	System.arraycopy(src, 0, dest, 0, 3);// 抛异常java.lang.ArrayStoreException
                  	for (Object o : dest) {System.out.println(o);
                  	}
                  }
                  

                    若源数组src的元素为目标数组dest的元素的子类时,是可以复制的,特殊地,如int不是Object的子类(或者说,不存在继承的概念),所以,上述代码中,如果把int[] src = {1, 2, 3, 4};改为Integer[] src = {1, 2, 3, 4};,是不会报错的。

                  public static void main(String[] args) {Integer[] src = { 1, 2, 3, 4 };
                  	Object[] dest = new Object[3];
                  	System.arraycopy(src, 0, dest, 0, 3);
                  	for (Object o : dest) {System.out.println(o);
                  	}
                  }
                  

                  输出结果:

                  1

                  2

                  3


                  public static void main(String[] args) {Object[] src = { 1, 2, 3, 4 };
                  	Integer[] dest = new Integer[3];
                  	System.arraycopy(src, 0, dest, 0, 3);
                  	for (Object o : dest) {System.out.println(o);
                  	}
                  }
                  

                  输出结果:

                  1

                  2

                  3


                  总结:

                  抛java.lang.ArrayStoreException异常(由于类型不匹配, src阵列中的元素无法存储到 dest阵列中),常见的就是:

                  • src参数引用的对象不是数组。
                  • dest参数引用的对象不是数组。
                  • src参数和dest参数引用其组件类型为不同基元类型的数组。
                  • src参数引用具有基本组件类型的数组,而dest参数引用具有引用组件类型的数组。
                  • src参数引用具有引用组件类型的数组,而dest参数引用具有基本组件类型的数组。

                    抛java.lang.ArrayIndexOutOfBoundsException异常(如果复制会导致访问数组边界外的数据),常见的就是:

                    • srcPos论点是否定的。
                    • destPos参数为负数。
                    • length参数为负数。
                    • srcPos+length大于src.length ,源数组的长度。
                    • destPos+length大于dest.length ,目标数组的长度。

                      抛java.lang.NullPointerException异常,常见的就是:

                      • src或 dest 是 null 。

                        案例分析:

                        public static void main(String[] args) {// 利用函数的方法进行数组的扩充
                        	// 定义一个小型的数组
                        	int[] a = { 1, 2, 3, 4, 5 };
                        	// 调用扩容函数
                        	a= expand2(a);
                        	// 测试是否扩容完成,输出此时数组a中的值
                        	for (int i = 0; i < a.length; i++) {System.out.println("数组元素:" + a[i]);
                        	}
                        }
                        // 数组扩容方法2,利用系统函数arraycopy进行扩容
                        public static int[] expand2(int a[]) {int[] b = new int[a.length * 2];
                        	// 系统函数进行扩容,将a[]的值赋值到b[]中,共a.length个长度。
                        	System.arraycopy(a, 0, b, 0, a.length);
                        	System.out.println("数组扩容方法2:" + Arrays.toString(b));
                        	return b;
                        }
                        

                        输出结果:

                        数组扩容方法2:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

                        数组元素:1

                        数组元素:2

                        数组元素:3

                        数组元素:4

                        数组元素:5

                        数组元素:0

                        数组元素:0

                        数组元素:0

                        数组元素:0

                        数组元素:0

                        3.使用java.util.Arrays.copyOf()

                        方法解析:

                          Arrays.copyof是用于数组进行复制时常使用的方法,本身在Arrays类里面,而之所以能这么使用而不用创建对象在于该方法本身由static修饰,被static修饰的方法可以在该类创建对象前载入jvm。

                          Arrays.copyof有众多的重载方法,以最典型的Arrays.copyOf(srcArray, length);为例进行分析,源码如下:

                        public static long[] copyOf(long[] original, int newLength) { long[] copy = new long[newLength];
                            System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
                            return copy;
                        }
                        

                        通过上面的代码可以看出,其本质是调用了System.arraycopy方法。

                        • 1.先产生一个新的数组
                        • 2.调用arraycopy方法
                        • 3.返回产生的新数组

                          参数解释:

                          • original 源数组
                          • newLength 新扩容长度

                            注意事项:

                              当我们将原来的数组进行扩容的时候,调用该方法产生了一个新的数组,而将扩容后的数组赋值给原来的数组的时候,原数组指向新产生的数组,但其原数组的内容依然在内存中,等待jvm回收,在这段时间中其实是造成了内存的浪费,所以使用该方法尽管简便,实际上有一定的不足。

                            案例分析:

                            public static void main(String[] args) {// 利用函数的方法进行数组的扩充
                            	// 定义一个小型的数组
                            	int[] a = { 1, 2, 3, 4, 5 };
                            	// 调用扩容函数
                            	a= expand3(a);
                            	// 测试是否扩容完成,输出此时数组a中的值
                            	for (int i = 0; i < a.length; i++) {System.out.println("数组元素:" + a[i]);
                            	}
                            }
                            // 数组扩容方法3,利用系统函数copyOf进行扩容
                            public static int[] expand3(int a[]) {int[] b = java.util.Arrays.copyOf(a, a.length * 2);
                            	System.out.println("数组扩容方法3:" + Arrays.toString(b));
                            	return b;
                            }
                            

                            输出结果:

                            数组扩容方法3:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

                            数组元素:1

                            数组元素:2

                            数组元素:3

                            数组元素:4

                            数组元素:5

                            数组元素:0

                            数组元素:0

                            数组元素:0

                            数组元素:0

                            数组元素:0

                            Java数组扩容的原理:
                            • Java数组对象的大小是固定不变的,数组对象是不可扩容的。
                            • 利用数组复制方法可以变通的实现数组扩容。
                            • System.arraycopy()可以复制数组。
                            • Arrays.copyOf()可以简便的创建数组副本。
                            • 创建数组副本的同时将数组长度增加就变相的实现了数组的扩容。
                              拓展练习:

                              例1:手动实现单一类型数组扩容,要求使用Scanner的方式,在控制台手动输入一个数(即每次在原数组的基础上加一位),并将该数加到原数组中。

                              public static void main(String[] args) { Scanner c = new Scanner(System.in);
                                  int[] a = { 20, 41, 61 };
                                  do { int[] b = new int[a.length + 1];
                                      for (int i = 0; i < a.length; i++) { b[i] = a[i];
                                      }
                                      System.out.println("请增加-个数");
                                      int add = c.nextInt();
                                      b[a.length] = add;
                                      a = b;
                                      System.out.println("扩容后: ");
                                      for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " ");
                                      }
                                      System.out.println("要继续添加吗? ");
                                      char m = c.next().charAt(0);
                                      if (m == 'n') { System.out.println("你退出了");
                                          break;
                                      }
                                  } while (true);
                              }
                              

                              输出结果:

                              请增加-个数

                              88

                              扩容后:

                              20 41 61 88 要继续添加吗?

                              y

                              请增加-个数

                              99

                              扩容后:

                              20 41 61 88 99 要继续添加吗?

                              y

                              请增加-个数

                              66

                              扩容后:

                              20 41 61 88 99 66 要继续添加吗?

                              n

                              你退出了


                              例2:已知有一组数组{ 1, 2, 3, 4, 5 },若想在后面增加自定的数字,如6,效果如下{ 1, 2, 3, 4, 5, 6 },该怎么实现?

                              public static void main(String[] args) {int[] arr = new int[] { 1, 2, 3, 4, 5 };				
                              	System.out.println(Arrays.toString(addElementToArray(arr, 6)));
                              }
                              public static int[] addElementToArray(int[] arr, int num) {int[] result = new int[arr.length + 1];
                              	for (int i = 0; i < arr.length; i++) {result[i] = arr[i];
                              	}
                              	result[result.length - 1] = num;
                              	return result;
                              }
                              

                              输出结果:

                              [1, 2, 3, 4, 5, 6]


                              进阶练习

                                在讲解到System.arraycopy()的用法时,这里也顺便聊一聊它所涉及到的深拷贝和浅拷贝。

                              📌深拷贝和浅拷贝

                              • 当数组为一维数组,且元素为基本类型或String类型时,属于深拷贝,即原数组与新数组的元素不会相互影响。
                              • 当数组为多维数组,或一维数组中的元素为引用类型时,属于浅拷贝,原数组与新数组的元素引用指向同一个对象。

                                注意:

                                • 这里说的影响,是两个数组拷贝后对应的元素,并不一定是下标对应;
                                • String的特殊是因为它的不可变性
                                  1.一维数组,元素为基本类型
                                  public static void main(String[] args) {String str1 = "aa";
                                  	String str2 = "bb";
                                  	String str3 = "cc";
                                  	String str4 = "dd";
                                  	String[] src = { str1, str2, str3, str4 };
                                  	String[] dest = new String[4];
                                  	System.arraycopy(src, 0, dest, 0, 4);
                                  	System.out.println("改变前");
                                  	print("src = ", src);
                                  	print("dest = ", dest);
                                  	src[0] = "abcd";//改变源数组的第0个元素
                                  	System.out.println("改变后");
                                  	print("src = ", src);
                                  	print("dest = ", dest);
                                  }
                                  private static void print(String string, String[] arr) {System.out.print(string);
                                  	for (String str : arr) {System.out.print(str + " ");
                                  	}
                                  	System.out.println();
                                  }
                                  

                                  输出结果:

                                  改变前

                                  src = aa bb cc dd

                                  dest = aa bb cc dd

                                  改变后

                                  src = abcd bb cc dd

                                  dest = aa bb cc dd

                                  得出结论:

                                    通过上面的代码执行结果可以看到,当源数组的第0个元素发生改变后,并不会影响到目标数组,这就是深拷贝。

                                  2.一维数组,元素为引用类型
                                  public static void main(String[] args) {People p1 = new People(11, "A");
                                  	People p2 = new People(12, "B");
                                  	People p3 = new People(13, "C");
                                  	People p4 = new People(14, "D");
                                  	People[] src = new People[] { p1, p2, p3, p4 };
                                  	People[] dest = new People[4];
                                  	System.arraycopy(src, 0, dest, 0, 4);
                                  	System.out.println("改变前");
                                  	print("src = ", src);
                                  	print("dest = ", dest);
                                  	src[0].setAge(888);
                                  	src[0].setName("ZZZ");
                                  	System.out.println("改变后");
                                  	print("src = ", src);
                                  	print("dest = ", dest);
                                  }
                                  private static void print(String string, People[] arr) {System.out.print(string);
                                  	for (People p : arr) {System.out.print(p + ", ");
                                  	}
                                  	System.out.println();
                                  }
                                  

                                  People类

                                  public class People {private int age;
                                  	private String name;
                                  	// 有参构造方法
                                  	public People(int age, String name) {this.age = age;
                                  		this.name = name;
                                  	}
                                  	@Override
                                  	public String toString() {return "People [age=" + age + ", name=" + name + "]";
                                  	}
                                  	public int getAge() {return age;
                                  	}
                                  	public void setAge(int age) {this.age = age;
                                  	}
                                  	public String getName() {return name;
                                  	}
                                  	public void setName(String name) {this.name = name;
                                  	}
                                  }
                                  

                                  输出结果:

                                  改变前

                                  src = People [age=11, name=A], People [age=12, name=B], People [age=13, name=C], People [age=14, name=D],

                                  dest = People [age=11, name=A], People [age=12, name=B], People [age=13, name=C], People [age=14, name=D],

                                  改变后

                                  src = People [age=888, name=ZZZ], People [age=12, name=B], People [age=13, name=C], People [age=14, name=D],

                                  dest = People [age=888, name=ZZZ], People [age=12, name=B], People [age=13, name=C], People [age=14, name=D],

                                  得出结论:

                                    通过上面的代码执行结果可以看到,当源数组发生改变后,目标数组也会跟着发生改变,这就是浅拷贝。

                                  3.多维数组
                                  public static void main(String[] args) {int[] arr1 = { 1, 2 };
                                  	int[] arr2 = { 3, 4 };
                                  	int[] arr3 = { 5, 6 };
                                  	int[] arr4 = { 7, 8 };
                                  	int[][] src = new int[][] { arr1, arr2, arr3, arr4 };
                                  	int[][] dest = new int[4][];
                                  	System.arraycopy(src, 0, dest, 0, 4);
                                  	System.out.println("改变前");
                                  	print("src = ", src);
                                  	print("dest = ", dest);
                                  	src[0][0] = 99;//改变源数组的第0个元素
                                  	System.out.println("改变后");
                                  	print("src = ", src);
                                  	print("dest = ", dest);
                                  }
                                  // 简单输出二维int数组的方法
                                  private static void print(String string, int[][] arr) {System.out.print(string);
                                  	for (int[] a : arr) {for (int i : a) {System.out.print(i + " ");
                                  		}
                                  		System.out.print(",");
                                  	}
                                  	System.out.println();
                                  }
                                  

                                  输出结果:

                                  改变前

                                  src = 1 2 ,3 4 ,5 6 ,7 8 ,

                                  dest = 1 2 ,3 4 ,5 6 ,7 8 ,

                                  改变后

                                  src = 99 2 ,3 4 ,5 6 ,7 8 ,

                                  dest = 99 2 ,3 4 ,5 6 ,7 8 ,

                                  得出结论:

                                    通过上面的代码执行结果可以看到,当源数组发生改变后,目标数组也会跟着发生改变,这就是浅拷贝。

                                  总结:

                                    只有数组为一维数组,并且元素为基本类型或String类型时,才是深拷贝,其它都属于浅拷贝。

                                  拷贝的两层含义,对应了浅拷贝和深拷贝的概念,做了第一层,就是浅拷贝,做到第二层,就是深拷贝。
                                  • 浅拷贝:将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。

                                  • 深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”。

                                      很容易可以想到,浅拷贝比深拷贝要更快,但是,从拷贝的意义上来看,浅拷贝相较于深拷贝,要欠缺一点,即一个是效率问题,一个是内存占用问题。

                                    创作不易,感谢您的点赞与支持。