刚学 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个真实场景让你代码更优雅》

相关推荐
㳺三才人子2 小时前
初探 Flask
后端·python·flask·html
星栈独行2 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下2 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.2 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易3 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0073 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶3 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl4 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬4 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【62】时光旅行(Time-Travel)
java·人工智能·spring