大家好,我是大华! 很多朋友觉得"内存溢出"是高手才碰得到的问题。什么高并发、都是大流量系统才会遇到的烦恼。 其实很多内存溢出,不是项目大,有时候可能是因为集合用得太野了。
- 为什么
ArrayList
能把内存撑爆? HashMap
缓存为啥越积越多?- 为什么字符串拼接不能用
+
? - 大数据处理时,怎么避免一次性加载?
这篇文章来分享10个集合内存管理技巧。
1. ArrayList 别乱用
我一开始写爬虫,抓网页数据,往 ArrayList 里狂塞。
java
List<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
// 模拟抓数据
list.add(fetchDataFromWeb());
}
跑着跑着就崩了,为啥呢? 因为ArrayList
默认初始容量是10。存的第11个,它就得扩容,复制数组,然后重新分配内存。 10万条数据,内存肯定就会蹭蹭往上涨了。
解决办法:
java
// 估算个数量,直接定大小
List<String> list = new ArrayList<>(100000);
一次到位 场景: 数据量可预估的,比如读文件、批量处理订单。
2. 用完就扔,别让集合赖在内存里
写了个方法,统计某次考试的平均分。
java
public class ScoreTool {
private List<Integer> scores = new ArrayList<>();
public double getAverage() {
scores.clear();
// 假设这是从某处读来的成绩
for (int i = 0; i < 30; i++) {
scores.add(70 + i); // 模拟成绩 70~100
}
return scores.stream().mapToInt(Integer::intValue).average().orElse(0);
}
}
每次调用getAverage()
,都会往scores
里塞新数据。 虽然clear()
了,但这个scores
对象一直存在,GC回收不了。 调用1000 次,就存了1000批数据,内存越来越高。
解决办法:
java
public double getAverage() {
List<Integer> scores = new ArrayList<>();
for (int i = 0; i < 30; i++) {
scores.add(70 + i);
}
return scores.stream().mapToInt(Integer::intValue).average().orElse(0);
// 方法结束,scores 自动消失,内存回收
}
临时数据用局部变量,用完就走,就不占地方。
3. 别一次性读大文件
你想读一个存了1万个名字的txt文件。别这么写:
java
List<String> allNames = Files.readAllLines(Paths.get("names.txt")); // 全读进来
万一文件特别大,内存直接爆。
正确做法:
一行一行读,处理完就扔。
java
try (BufferedReader reader = Files.newBufferedReader(Paths.get("names.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("处理:" + line);
// 处理完就不管了,不用存起来
}
}
就像吃西瓜,一口一口吃,别整个塞嘴里。
4. 去重?用Set
你要从一堆名字里去掉重复的。别这么写:
java
List<String> unique = new ArrayList<>();
String[] names = {"张三", "李四", "张三", "王五"};
for (String name : names) {
if (!unique.contains(name)) { // 每次都要从头找一遍
unique.add(name);
}
}
contains
要一个个比,数据变多时会很慢。 一行解决:
java
Set<String> unique = new HashSet<>(Arrays.asList(names));
Set
本身就是不重复的,直接去重,又快又省事。
5. 拼字符串,用StringBuilder
你想把几个单词拼成一句话。 别这么干:
java
String sentence = "";
String[] words = {"我", "是", "大", "华"};
for (String word : words) {
sentence += word; // 每次都新建一个字符串
}
Java
的字符串是" immutable "(不可变),+=实际是不断创建新对象,内存爆炸。
正确写法:
java
StringBuilder sb = new StringBuilder();
for (String word : words) {
sb.append(word);
}
String sentence = sb.toString();
StringBuilder
就像一个可伸缩的盒子,往里塞东西不换盒子,更加省内存。
6. 数字别new,直接用
java
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println(a == b); // false!两个不同的对象
你new了两个 10,它们是两个不同的"盒子",只是装的东西一样。
推荐写法:
java
Integer a = 10;
Integer b = 10;
System.out.println(a == b); // true!同一个"盒子"
Java
会缓存 -128 到 127 的数字,直接用更省内存。
7. 用EnumSet
记状态
比如你定义了考试状态:PASSED
, FAILED
, PENDING
。 你想记哪些状态已经处理过了。别用HashSet
:
java
Set<Status> processed = new HashSet<>();
processed.add(Status.PASSED);
改用:
java
Set<Status> processed = EnumSet.noneOf(Status.class);
processed.add(Status.PASSED);
EnumSet
内部用二进制位表示,一个int
就能存32个状态,超级省内存。
8. 转数组,记得说清楚类型
java
List<String> list = Arrays.asList("a", "b", "c");
Object[] arr = list.toArray(); // 得到 Object 数组
String[] strArr = (String[]) arr; // 强转可能出错
安全写法:
java
String[] arr = list.toArray(new String[0]);
告诉系统:我要的是 String 数组,别给我 Object。
9. 读文件后,记得关
java
// 错误示范:忘了关!
BufferedReader reader = new BufferedReader(new FileReader("names.txt"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println("名字: " + line + ", 长度: " + line.length());
}
// 没有 reader.close();
正确做法: Java
有个超好用的语法:try-with-resources
它能保证:只要用它打开的资源,用完自动关,哪怕中间出错也不怕。
java
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} // 自动关闭
关键点:
BufferedReader
写在try (...)
里- 它实现了
AutoCloseable
接口,系统知道它能自动关 - 大括号一结束,自动调
close()
- 即使中间报错,也会关
总结
ArrayList
提前定大小- 临时数据用局部变量
- 临时缓存用
WeakHashMap
- 大文件别全读
- 去重用
Set
- 拼接用
StringBuilder
Integer
别new
- 枚举用
EnumSet
- 转数组说清类型
- 流用完记得关
赶紧改改,让你的Java
程序又快又稳!
我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《Elasticsearch 太重?来看看这个轻量级的替代品 Manticore Search》
《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》
《别学23种了!Java项目中最常用的6个设计模式,附案例》