
本文介绍如何使用 Map<String, List<String>> 结构,结合文件名+序号作为唯一键,按原始顺序逐行比对两次进程内存映射输出中相同位置条目的虚拟地址是否一致,解决传统 HashMap 键冲突导致的错位匹配问题。
本文介绍如何使用 `map
在 Linux 进程内存分析场景中(如解析 /proc/[pid]/maps),同一共享库(如 /lib/libm-2.31.so)可能在多次运行中映射到多个不同内存段,且各段顺序固定但地址可变。若仅用文件路径作为 Map 键(如 HashMap<String, String>),会导致所有同名条目被覆盖或无法区分——例如输出中三次出现 /lib/libm-2.31.so,但实际对应 3 个不同内存区域(r-xp、---p、r--p 等)。此时简单键值匹配会丢失位置语义,造成误判。
正确做法是:为每个文件路径附加其在原始输出中的逻辑索引(从 1 开始),构建形如 /lib/libm-2.31.so#1、/lib/libm-2.31.so#2 的唯一键,并将对应内存地址(如 b3e74000-b3ec5000)按执行批次顺序存入 List<String>。这样既保留原始行序,又支持跨批次精准比对。
以下为完整实现:
import java.util.*;
import java.util.stream.Collectors;
public class MemoryMapComparator {
public static void main(String[] args) {
String output1 = "b3e74000-b3ec5000 r-xp 00000000 fb:00 1653 /lib/libm-2.31.so
" +
"b3ec5000-b3ed4000 ---p 00051000 fb:00 1653 /lib/libm-2.31.so
" +
"b3ee7000-b3ee8000 r--p 00050000 fb:00 1653 /lib/libm-2.31.so
" +
"b3ed5000-b3ed6000 rw-p 00051000 fb:00 1653 /lib/libm-2.31.so";
String output2 = "
" +
"b3e74000-b3ec5000 r-xp 00000000 fb:00 1653 /lib/libm-2.31.so
" +
"b3ed8000-b3ee7000 ---p 00051000 fb:00 1653 /lib/libm-2.31.so
" +
"b3ee7000-b3ee8000 r--p 00050000 fb:00 1653 /lib/libm-2.31.so
" +
"b3ee8000-b3ee9000 rw-p 00051000 fb:00 1653 /lib/libm-2.31.so";
Map<String, List<String>> resultMap = getMemoryAddressWithFileName(new String[]{output1, output2});
System.out.println("=== 内存地址比对结果 ===");
for (Map.Entry<String, List<String>> entry : resultMap.entrySet()) {
List<String> addresses = entry.getValue();
if (addresses.size() == 2) {
String addr1 = addresses.get(0);
String addr2 = addresses.get(1);
boolean unchanged = addr1.equals(addr2);
System.out.printf("条目 %s: 第一次='%s' | 第二次='%s' → %s%n",
entry.getKey(), addr1, addr2, unchanged ? "✅ 未变化" : "⚠️ 已变化");
}
}
}
/**
* 解析多组内存映射输出,按行序构建 {filename#index → [addr1, addr2]} 映射
* @param responses 两组原始输出字符串(如 output1, output2)
* @return 键为带序号的文件路径,值为对应内存地址列表(按输入顺序排列)
*/
public static Map<String, List<String>> getMemoryAddressWithFileName(String[] responses) {
Map<String, List<String>> map = new LinkedHashMap<>();
for (String response : responses) {
Scanner scanner = new Scanner(response);
int index = 1;
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.isEmpty()) continue;
// 使用正则分割:以一个或多个空白符 + 开头为 '/' 的位置为界
String[] parts = line.split("\s+(?=/)", 2);
if (parts.length < 2) continue;
String addressPart = parts[0].trim(); // 如 "b3e74000-b3ec5000"
String filePath = parts[1].trim(); // 如 "/lib/libm-2.31.so"
// 构建唯一键:路径 + '#' + 当前行序号(从1开始)
String key = filePath + "#" + index++;
// 提取纯地址(去除后续空格及权限字段)
String address = addressPart.split("\s+")[0];
// 自动初始化列表并添加地址
map.computeIfAbsent(key, k -> new ArrayList<>()).add(address);
}
scanner.close();
}
return map;
}
}关键设计说明:
立即学习“Java免费学习笔记(深入)”;
- ✅ 严格保序:使用 LinkedHashMap 确保遍历顺序与输入行序完全一致;
- ✅ 索引绑定:#n 后缀使同名文件的每一行成为独立键,避免 HashMap 的键覆盖问题;
- ✅ 鲁棒解析:正则 \s+(?=/) 精准切分地址段与路径段,兼容多空格和制表符;
- ✅ 批量扩展:方法接受 String[] 参数,天然支持 N 次运行对比(只需调整循环逻辑);
- ⚠️ 注意事项:
- 输入必须保证两组输出的行数与语义顺序一致(即第 i 行均描述同一类内存段);
- 若某次输出缺失某行,对应 List 长度将小于预期,需增加空值检查;
- 生产环境建议封装为工具类,配合日志与异常处理提升健壮性。
该方案直击问题本质——内存映射的位置敏感性,是系统级 Java 工具开发中处理结构化文本比对的经典范式。


















