Java数组扩容的三大小结
作者:白.都
方案1:新建数组
这种方法新建的数组必须要比原先的长度要长,然后将原来的数组内容移到新的数组中
<!--more--> int[] a = {1, 2, 3, 4, 5}; // 创建新数组,长度为源数组的两倍 int[] b = new int[a.length * 2]; // 将旧数组内容复制到新数组 for (int i = 0; i < a.length; i++) { b[i] = a[i]; } // 新数组内容赋值给源数组 a = b; // 打印结果 System.out.println(Arrays.toString(a));
输出结果
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
方案2:Arrays.copyOf
int[] a = {1, 2, 3, 4, 5}; // 第一个参数是拷贝的数组,第二个参数是扩容长度,且返回一个新数组 a = Arrays.copyOf(a, a.length * 2); // 打印结果 System.out.println(Arrays.toString(a));
输出结果
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
Arrays.copyof是用于数组进行复制时常使用的方法,本身在Arrays类里面,而之所以能这么使用而不用创建对象在于该方法本身由static修饰,被static修饰的方法可以在该类创建对象前载入jvm。
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方法。
先产生一个新的数组然后调用arraycopy方法最后在返回产生的新数组。但是我们进行数组扩容的时候禅城了新数组,但是原数组依然存在,造成了内存的浪费。
方案3:System.arraycopy
int[] a = {1, 2, 3, 4, 5}; // 定义新数组,长度为源数组的两倍 int[] b = new int[a.length * 2]; /** * src 需要拷贝的源数组 * srcPos 源数组中的起始位置 * dest 目标数组 * destPos 目标数组中的起始位置 * length 要复制的数组元素数量 */ System.arraycopy(a, 0, b, 0, a.length); // 新数组内容赋值给原数组 a = b; // 打印结果 System.out.println(Arrays.toString(a));
输出结果
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
arraycopy源码
/** * @param src the source array. 源数组 * @param srcPos starting position in the source array. 要复制的源数组的起始位置 * @param dest the destination array. 目标数组 * @param destPos starting position in the destination data. 目标数组的起始位置 * @param length the number of array elements to be copied. 要复制的长度 * @throws IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * 如果复制会导致数据的访问超出数组边界。 * 则会报IndexOutOfBoundsException索引越界异常 * @throws ArrayStoreException if an element in the <code>src</code> array * could not be stored into the <code>dest</code> array * because of a type mismatch. * 如果由于类型不匹配而无法将src数组中的元素存储到dest数组中。 * 则会报 ArrayStoreException数组存储异常 * @throws NullPointerException if either <code>src</code> or * <code>dest</code> is <code>null</code>. * 如果src或dest为null。 * 则会报NullPointerException空指针异常 */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
System.arraycopy()的几种常用方法(普通for循环和arraycopy方法对比)
1. 从旧数组拷贝到新数组
//从旧数组拷贝到新数组 for (int i=0;i<size;i++){ arrayNew[i]=array[i]; } System.arraycopy(array, 0, arrayNew, 0, array.length);
2. 从左向右循环,逐个元素向左挪一位。
//从左向右循环,逐个元素向左挪一位。 for (int i = index; i < size - 1; i++) { array[i] = array[i + 1]; } System.arraycopy(array, index + 1, array, index, size - 1 - index);
3. 从右向左循环,逐个元素向右挪一位。
//从右向左循环,逐个元素向右挪一位。 for (int i = size - 1; i >= index; i--) { array[i + 1] = array[i]; } System.arraycopy(array, index, array, index + 1, size - index);
System.arraycopy()深层理解
深复制还是浅复制
先说结论 :
- 当数组为一维数组,且元素为基本类型或String类型时,属于深拷贝,即原数组与新数组的元素不会相互影响。
- 当数组为多维数组,或一维数组中的元素为引用类型时,属于浅拷贝,原数组与新数组的元素引用指向同一个对象。
引用对象
构建一个User类型源数组,复制后至target数组,比较第一个元素的内存地址,判断结果是相同的,证明为浅复制;后修改target数组数组的随机元素,发现原来的值也变了。
public class SystemArrayCopyTestCase { public static void main(String[] args) { User[] users = new User[] { new User(1), new User(2), new User(3) };// 初始化对象数组 User[] target = new User[users.length];// 新建一个目标对象数组 System.arraycopy(users, 0, target, 0, users.length);// 实现复制 System.out.println("源对象与目标对象的物理地址是否一样:" + (users[0] == target[0] ? "浅复制" : "深复制")); //浅复制 target[0].setId(5); System.out.println("修改目标对象的属性值后源对象users:"); for (User user : users) { System.out.println(user); } } } class User { }
System.arraycopy() 在拷贝数组的时候,采用的使用潜复制,复制结果是一维的引用变量传递给新数组的一维数组,修改新数组时,会影响原来的数组。
一维数组和多维数组
将一维数组作源数组,进行拷贝,产生target数组;然后修改target数组中的元素,新数组没变,证明是值拷贝,修改新数组不会影响原来的值。
将多维数组作源数组,进行拷贝至目标数组,修改至目标数组的元素,新数组也变了,说明是二者是相同的引用,而这时改变其中任何一个数组的元素的值,其实都修改了共同数组元素的值,所以原数组和新数组的元素值都一样了
线程是否安全(摘自网络)
System.ayyaycopy是不安全的。
public class ArrayCopyThreadSafe { private static int[] arrayOriginal = new int[1024 * 1024 * 10]; private static int[] arraySrc = new int[1024 * 1024 * 10]; private static int[] arrayDist = new int[1024 * 1024 * 10]; private static ReentrantLock lock = new ReentrantLock(); private static void modify() { for (int i = 0; i < arraySrc.length; i++) { arraySrc[i] = i + 1; } } private static void copy() { System.arraycopy(arraySrc, 0, arrayDist, 0, arraySrc.length); } private static void init() { for (int i = 0; i < arraySrc.length; i++) { arrayOriginal[i] = i; arraySrc[i] = i; arrayDist[i] = 0; } } private static void doThreadSafeCheck() throws Exception { for (int i = 0; i < 100; i++) { System.out.println("run count: " + (i + 1)); init(); Condition condition = lock.newCondition(); new Thread(new Runnable() { @Override public void run() { lock.lock(); condition.signalAll(); lock.unlock(); copy(); } }).start(); lock.lock(); // 这里使用 Condition 来保证拷贝线程先已经运行了. condition.await(); lock.unlock(); Thread.sleep(2); // 休眠2毫秒, 确保拷贝操作已经执行了, 才执行修改操作. modify(); if (!Arrays.equals(arrayOriginal, arrayDist)) { throw new RuntimeException("System.arraycopy is not thread safe"); } } } public static void main(String[] args) throws Exception { doThreadSafeCheck(); } }
这个例子的具体操作是:
arrayOriginal 和 arraySrc 初始化时是相同的, 而 arrayDist 是全为零的.
启动一个线程运行 copy() 方法来拷贝 arraySrc 到 arrayDist 中.
在主线程执行 modify() 操作, 修改 arraySrc 的内容. 为了确保 copy() 操作先于 modify() 操作, 我使用 Condition, 并且延时了两毫秒, 以此来保证执行拷贝操作(即System.arraycopy) 先于修改操作.
根据第三点, 如果 System.arraycopy 是线程安全的, 那么先执行拷贝操作, 再执行修改操作时, 不会影响复制结果, 因此 arrayOriginal 必然等于 arrayDist; 而如果 System.arraycopy 是线程不安全的, 那么 arrayOriginal 不等于 arrayDist.
run count: 1
Exception in thread "main" java.lang.RuntimeException: System.arraycopy is not thread safe
at ArrayCopyThreadSafe.doThreadSafeCheck(ArrayCopyThreadSafe.java:54)
at ArrayCopyThreadSafe.main(ArrayCopyThreadSafe.java:60)
结论 :System.ayyaycopy是不安全的。
System.arraycopy()和for()相比谁更高效
当测试数组的范围比较小的时候,两者相差的时间无几,当测试数组的长度达到百万级别,System.arraycopy的速度优势就开始体现了,根据对底层的理解,System.arraycopy是对内存直接进行复制,减少了for循环过程中的寻址时间,从而提高了效能。
到此这篇关于Java数组扩容的三大小结的文章就介绍到这了,更多相关Java数组扩容内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!