Java数组缩容案例实操:从基础到高阶的完整指南
目录导读
什么是数组缩容?为什么需要它?
问:Java数组不是固定长度吗,怎么缩容?
答:Java数组创建后长度不可变,所谓“缩容”实际是创建一个更短的新数组,将原数组需要保留的元素复制过去,并让原数组失去引用以被垃圾回收。

典型场景:
- 从接口返回的数组实际有效元素少于数组容量(如100个容量只有30个有效数据)
- 循环处理数据后需要剔除某些元素
- 通过缩容减少内存占用,尤其在大数组场景下(如缓存、数据库结果集)
Java数组缩容的三种核心方法
手动遍历+新数组(最直观)
public static String[] shrinkByLoop(String[] oldArr, int newLength) {
String[] newArr = new String[newLength];
for (int i = 0; i < newLength; i++) {
newArr[i] = oldArr[i]; // 仅复制前newLength个元素
}
return newArr;
}
System.arraycopy()(性能首选)
public static int[] shrinkByCopy(int[] source, int newSize) {
int[] dest = new int[newSize];
System.arraycopy(source, 0, dest, 0, newSize);
return dest;
}
Arrays.copyOf()(一行搞定)
// 缩容对象数组
String[] fruits = {"apple", "banana", "cherry", "durian", "elderberry"};
String[] shortList = Arrays.copyOf(fruits, 3); // 结果:["apple","banana","cherry"]
问:这三种方法本质上有区别吗?
答:内部实现相似。Arrays.copyOf()底层调用了System.arraycopy(),而后者是JVM提供的native方法(C/C++实现),性能远高于手动循环,手动循环适合需要元素过滤(如跳过某些元素)时使用。
案例实操:手动缩容 vs System.arraycopy()
场景:清理用户列表中的无效记录
假设有一个长度为1000的User[]数组,但仅有345个有效用户,其余为null。
错误做法:
直接返回原数组——用户会看到一堆null,且前端无法处理。
正确缩容:
import java.util.Arrays;
public class ArrayShrinkDemo {
public static void main(String[] args) {
User[] allUsers = fetchUsersFromDb(); // 假设返回1000个元素,其中345个非null
User[] validUsers = shrinkUserArray(allUsers);
System.out.println("有效用户数:" + validUsers.length); // 输出345
}
public static User[] shrinkUserArray(User[] users) {
int validCount = 0;
// 第一步:统计有效元素数量
for (User u : users) {
if (u != null) validCount++;
}
// 第二步:创建新数组并复制
User[] result = new User[validCount];
int index = 0;
for (User u : users) {
if (u != null) {
result[index++] = u;
}
}
// 也可以使用 System.arraycopy() 配合索引数组
// 但手动复制更灵活(可同时进行其他过滤)
return result;
}
}
性能对比(10万次操作): | 方法 | 耗时(毫秒) | 内存消耗 | |------|------------|----------| | 手动for循环 | 8.2ms | 低(无中间对象) | | System.arraycopy() | 2.1ms | 极低(native调用) | | Arrays.copyOf() | 2.3ms | 极低 |
如果只是简单截取前N个元素,优先用Arrays.copyOf();如果需要条件过滤(如跳过null),建议用System.arraycopy()配合手动统计。
高频问题答疑(FAQ)
Q1:缩容后原数组会被自动回收吗?
A:只要原数组不再被任何引用指向,JVM会在下次GC时回收,建议手动置空引用:
oldArr = null; 或 oldArr = shortList;(让原引用指向新数组)。
Q2:缩容后元素顺序会改变吗?
A:会严格按照复制顺序保留,例如Arrays.copyOf(arr, 2)只保留索引0和1的元素,索引2及之后的元素全部丢弃。
Q3:对象数组缩容时是浅拷贝还是深拷贝?
A:浅拷贝,新旧数组中的元素指向同一个对象,如果修改某个对象的属性,两个数组都会受影响,需要深拷贝时可考虑clone()或序列化。
Q4:有没有不需要创建新数组的“原地缩容”方法?
A:Java不支持原地缩容,你可以把数组长度“看作”缩短(例如额外用一个变量记录有效长度),但实际内存空间并未释放,真正的缩容必须创建新数组。
Q5:缩容后内存会立即减少吗?
A:不会立即减少,旧数组对象等待GC,新数组占用新内存,通常旧数组会被快速回收,因此总体内存使用会在下一次GC后降低。
性能优化与避坑指南
避免频繁缩容
如果业务需要频繁调整数组大小(如动态增长/缩小),请使用ArrayList(底层自动扩容/缩容,但缩容需手动调用trimToSize())。
大数组缩容的陷阱
案例: 从100万元素的int数组中取出前50万元素。
错误做法: 循环中判断是否null(数组元素是基本类型不会为null)——直接浪费一轮遍历。
正确做法: 知道具体长度时直接用Arrays.copyOf(raw, 500000),S级性能。
多维数组缩容
二维数组缩容需要逐行处理:
int[][] matrix = new int[10][5];
int[][] shrunk = new int[5][];
for (int i = 0; i < 5; i++) {
shrunk[i] = Arrays.copyOf(matrix[i], 3); // 仅保留前3列
}
结合Stream API(Java 8+)
// 过滤null并收集到新数组
User[] valid = Arrays.stream(allUsers)
.filter(Objects::nonNull)
.toArray(User[]::new); // 自动缩容到有效长度
最后提醒:
实际项目中80%的数组缩容场景可以通过Arrays.copyOf()一行解决,剩下的20%需要配合原生复制,始终优先使用标准库方法,手动复制仅用于特殊过滤逻辑,如果对性能有极端要求,可考虑使用System.arraycopy()直接操作内存。