Java下得到ArrayList<String>占用内存大小的方法

刚需:压测时需要占用较大的内存,但使用ArrayList<String>时,无法获得每次调用占用的内存大小.

问题:Java中List的实际申请长度并不是size()取到的长度,而且,每次String申请的内存地址空间和主机有关,诸多因素导致计算估值并不是很准确

参考方案如下

代码

Runtime r = Runtime.getRuntime();
long startRAM = r.freeMemory();
List<String> listRAM = new ArrayList<>();
int loopTimes = 1000000;
String string = "摘抄自出师表…………太长了,不粘在这里";
for(int i = 0;i < loopTimes;i++) {
	listRAM.add(string);
}
long endRAM = r.freeMemory();
Field f = ArrayList.class.getDeclaredField("elementData");
f.setAccessible(true);
Object[] o = (Object[]) f.get(listRAM);

 方案1 使用Runtime内存管理类

String result = "测试RAM结束,测试占用内存空间约为 : " + (startRAM - endRAM);
System.out.println(result);

 方案2 反射的方式查询ArrayList的实际申请长度,然后按照每个字符串申请了2字节进行计算

result = "测试RAM结束,测试占用内存空间约为 : " + ((long)o.length * (long)string.length() * 2);
System.out.println(result);

 方案3 反射的方式查询ArrayList的实际申请长度,然后取字符串的字节数组长度计算

result = "测试RAM结束,测试占用内存空间约为 : " + ((long)o.length * (long)string.getBytes().length);
System.out.println(result);

结果(不同的方案之间差异较大):

测试RAM结束,测试占用内存空间约为 : 14016896
测试RAM结束,测试占用内存空间约为 : 1801351734
测试RAM结束,测试占用内存空间约为 : 2702027601

补充:

使用ArrayList创建List数组时,实际长度由elementData进行维护,可使用反射的方法得到List申请的实际长度。

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

分析:

方案一:r.freeMemory()方法可以得到当前内存的使用情况,此处取的是空余内存。因此两次空余内存做差,即可得到本次调用占用的内存。但也是模糊数值,仅作为参考,因为不能保证没有其他内存竞争。

方案二:查Java手册可知,Java遵循utf16原则,字符串中的每个字符均占用16位,即2个字节,因此直接乘2。

方案三:获取字符串的byte数组,用其长度直接相乘。而getBytes()方法底层实际是调用了StringCoding.encode(value, 0, value.length)方法,通过判断当前JVM的编码方式,进行字符解析,若未查到编码方式,将按照UTF-8进行编码。

r.totalMemory();
// 得到的输出结果为:126877696

博主此处选用的是第一种方案,因为和参考值比较接近。孰优孰劣,大大们可自行分析。

<think>好的,我现在需要回答用户关于阿里巴巴EasyExcel库中`alibaba.excel.metadata`的`head`为什么是`List<List<String>>`的问题。用户之前询问过关于嵌套List的不可变性的问题,所以可能对集合结构有一定了解,但需要更具体的解释。 首先,我应该回顾一下EasyExcel的基本用法和功能。EasyExcel是阿里巴巴开源的用于处理Excel文件的Java库,它支持读写大数据量的Excel文件,并且内存占用较低。在读写Excel时,通常需要定义表头和数据体。这里的`head`应该是指表头的定义。 接下来,我需要理解为什么`head`被设计为`List<List<String>>`类型。首先,这可能与Excel表头的复杂性有关。简单的单行表头可以用一个`List<String>`表示,但实际应用中可能有合并单元格、多级表头的情况,这时候就需要嵌套的列表结构来表示层次关系。 例如,一个多级表头可能如下: ``` | 主要信息 | 详细信息 | | 姓名 | 年龄 | 地址 | 电话 | ``` 这种情况下,第一层表头是["主要信息", "详细信息"],而"主要信息"下又分为["姓名", "年龄"],"详细信息"下分为["地址", "电话"]。这样的结构用`List<List<String>>`可以更好地表示层级关系,每个内部List代表一列的表头路径。 另外,可能考虑到某些列需要合并多个单元格,或者表头有多行的情况。例如,两行表头: ``` | 基本信息 | 基本信息 | 联系方式 | | 姓名 | 年龄 | 电话 | ``` 这种情况下,外层List的每个元素代表一列,每个内部List包含该列在不同行中的表头内容。例如,第一列的`List<String>`可能是["基本信息", "姓名"],第二列是["基本信息", "年龄"],第三列是["联系方式", "电话"]。 这样设计的好处是灵活,能够处理各种复杂的表头结构。每个内部List中的字符串顺序表示表头的层级,从最上层到最下层。在生成Excel时,可以根据这些信息合并单元格,创建多级表头。 同时,这种结构也方便数据与表头的映射。例如,在读取Excel时,可以通过匹配数据列与表头的路径来确定数据的归属。在写入时,开发者可以自由定义复杂的表头结构,而库则负责正确渲染到Excel文件中。 可能还需要考虑与其他数据结构的兼容性。例如,使用注解方式配置表头时,可能内部会将这些注解转换为`List<List<String>>`的结构,以保持统一的数据处理逻辑。 此外,用户之前的问题涉及到嵌套List的不可变性,但在这里可能更多的是结构设计的考虑,而不是不可变性。不过,如果用户有疑问,可能需要进一步解释,但当前问题更关注结构而非可变性。 总结来说,`List<List<String>>`的设计是为了支持多级表头和复杂表头结构,每个内部List表示一列的表头层级,外层List包含所有列的表头信息。这种结构提供了足够的灵活性和表达能力,以满足不同场景下的需求。</think>### **分步解析:为什么 `alibaba.excel.metadata` 的 `head` 是 `List<List<String>>`** --- #### **1. 核心结论** `List<List<String>>` 的设计是为了支持 **多级表头** 和 **复杂表头结构**。每个内层 `List<String>` 表示 **一列的表头层级路径**,而外层 `List` 则包含所有列的表头定义。 --- #### **2. 多级表头示例** 假设需要生成如下复杂表头: ```plaintext | 基本信息 | 联系方式 | | 姓名 | 年龄 | 电话 | 地址 | ``` 对应的 `head` 结构为: ```java List<List<String>> head = Arrays.asList( Arrays.asList("基本信息", "姓名"), // 第一列的表头层级 Arrays.asList("基本信息", "年龄"), // 第二列的表头层级 Arrays.asList("联系方式", "电话"), // 第三列的表头层级 Arrays.asList("联系方式", "地址") // 第四列的表头层级 ); ``` - **每个内层 `List`**:描述一列的表头路径(如 `["基本信息", "姓名"]`)。 - **外层 `List`**:汇总所有列的表头路径。 --- #### **3. 设计原理** | **场景** | **数据结构** | **说明** | |-------------------------|--------------------------|-------------------------------------------------------------------------| | **单级表头** | `List<List<String>>` | 内层每个 `List` 仅含一个元素(如 `["姓名"]`)。 | | **多级表头** | `List<List<String>>` | 内层 `List` 按层级顺序排列(如 `["大类", "子类"]`)。 | | **合并单元格** | 相同路径的多列 | 若多列的路径前缀相同,EasyExcel 会自动合并表头(如 `["基本信息"]` 跨两列合并)。 | --- #### **4. 实际应用** ```java // 定义复杂表头 List<List<String>> head = new ArrayList<>(); head.add(Arrays.asList("主标题", "子标题1")); // 第一列的表头 head.add(Arrays.asList("主标题", "子标题2")); // 第二列的表头 head.add(Arrays.asList("独立标题")); // 第三列的表头(单级) // 写入Excel时,EasyExcel会根据路径自动合并表头单元格: // - "主标题" 合并前两列 // - "独立标题" 单独为一列 ``` --- #### **5. 与其他方案的对比** | **方案** | **局限性** | **`List<List<String>>` 的优势** | |------------------------|---------------------------------------|-----------------------------------------| | **单层 `List<String>`** | 无法表示多级表头或合并单元格。 | 通过路径层级直接描述复杂结构。 | | **自定义注解** | 灵活性较低,难以动态生成复杂表头。 | 动态构建表头结构,适合编程式配置。 | --- #### **6. 底层实现逻辑** - **写入Excel时**: EasyExcel 会遍历 `head` 的每个内层 `List`,解析路径层级,按层级深度生成多行表头,并自动合并相同路径前缀的单元格。 - **读取Excel时**: 若使用多级表头,读取的数据可以通过表头路径精准匹配列(例如根据 `["基本信息", "姓名"]` 定位列)。 --- #### **7. 代码示例:动态生成表头** ```java // 动态构建多级表头(例如根据数据库字段的父子关系) List<List<String>> dynamicHead = new ArrayList<>(); for (Field field : fields) { List<String> path = new ArrayList<>(); path.add(field.getParentCategory()); // 父级分类 path.add(field.getName()); // 字段名称 dynamicHead.add(path); } // 写入Excel EasyExcel.write("output.xlsx") .head(dynamicHead) .sheet() .doWrite(data); ``` --- #### **8. 常见问题解答** - **问:单级表头是否需要使用 `List<List<String>>`?** 答:需要。单级表头可以简化为每个内层 `List` 仅包含一个字符串(例如 `["姓名"]`)。 - **问:如何跳过某列的表头?** 答:在 `head` 中不定义该列路径即可(例如不添加对应的内层 `List`)。 --- ### **总结** `List<List<String>>` 的设计通过 **路径层级** 的抽象,完美适配了 Excel 多级表头、合并单元格等复杂场景。这种结构: 1. **灵活性高**:支持动态生成任意层级的表头。 2. **表达力强**:清晰描述列与表头的关系。 3. **兼容性好**:统一处理单级、多级表头的读写逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值