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

相关推荐
老马啸西风5 小时前
敏感词性能提升14倍优化全过程 v0.29.0
java
武子康5 小时前
大数据-90 Spark RDD容错机制:Checkpoint原理、场景与最佳实践 容错机制详解
大数据·后端·spark
花花无缺5 小时前
python自动化-pytest-标记
后端·python
Cloud-Future6 小时前
Spring MVC 处理请求的流程
java·spring·mvc
小扳6 小时前
SpringBootWeb 篇-深入了解 ThreadLocal 存在内存泄漏问题
java·开发语言·spring boot·面试
Villiam_AY6 小时前
使用 chromedp 高效爬取 Bing 搜索结果
后端·爬虫·golang
CryptoPP6 小时前
跨境金融数据对接实践:印度NSE/BSE股票行情API集成指南
开发语言·后端·金融
lxsy6 小时前
spring-ai-alibaba-deepresearch 学习(十三)——ResearcherNode
java·源码分析·deepresearch·ai-alibaba
ShineWinsu6 小时前
对于单链表相关经典算法题:206. 反转链表及876. 链表的中间结点的解析
java·c语言·数据结构·学习·算法·链表·力扣