刚学 Java 就被内存溢出劝退?这 10 个集合内存管理技巧救了我!

大家好,我是大华! 很多朋友觉得"内存溢出"是高手才碰得到的问题。什么高并发、都是大流量系统才会遇到的烦恼。 其实很多内存溢出,不是项目大,有时候可能是因为集合用得太野了。

  • 为什么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()
  • 即使中间报错,也会关

总结

  1. ArrayList提前定大小
  2. 临时数据用局部变量
  3. 临时缓存用WeakHashMap
  4. 大文件别全读
  5. 去重用Set
  6. 拼接用StringBuilder
  7. Integernew
  8. 枚举用EnumSet
  9. 转数组说清类型
  10. 流用完记得关

赶紧改改,让你的Java程序又快又稳!

我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《Elasticsearch 太重?来看看这个轻量级的替代品 Manticore Search》

《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》

《别学23种了!Java项目中最常用的6个设计模式,附案例》

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《Vue3+TS设计模式:5个真实场景让你代码更优雅》

相关推荐
星浩AI4 分钟前
Google 官方发布:让你的 AI 编程助手"边写、边看、边调",像人类开发者一样工作
人工智能·后端·开源
焰火19995 分钟前
[Java]自定义重试工具类
java
喵了个Code26 分钟前
Spring Boot 3 + Spring Security + OAuth2 + Gateway企业级认证授权平台实现
后端
开心猴爷31 分钟前
除了 Perfdog,如何在 Windows 环境中完成 iOS App 的性能测试工作
后端
SuperherRo1 小时前
JAVA攻防-Shiro专题&断点调试&有key利用链&URL&CC&CB&原生反序列化&加密逻辑
java·shiro·反序列化·有key·利用链·原生反序列化·加密逻辑
桦说编程1 小时前
简单方法实现子任务耗时统计
java·后端·监控
爱笑的眼睛111 小时前
超越可视化:降维算法组件的深度解析与工程实践
java·人工智能·python·ai
盖世英雄酱581362 小时前
物品超领取损失1万事故复盘(一)
java·后端
凌览2 小时前
别再死磕 Nginx!http-proxy-middleware 低配置起飞
前端·后端