foreach和Stringbuilder内存探究

本文主要研究Mono环境下的GC情况。一是对比for与foreach,发现foreach每次有40B的GC产生,根本原因是using语句,建议项目采用特定写法;二是研究字符串拼接,+号在几次连接中无GC,大量连接则产生大量GC,少量连接用+号,其余用StringBuilder并合理设置Buffer初值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ILSPY使用指南:

http://blog.ctrlxctrlv.net/unity-decompile-dll-ilspy-for-mac/

 

for 与 foreach 真相
目的
在 Mono 下,研究两种不同方式的 GC 情况。
环境
同简介中的环境
测试代码

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TestCodPerfomanceScript : MonoBehaviour
{

    // Use this for initialization List<int> mData = new List<int>();

    void Start()
    {
        mData.Add(1);   //可有 n 个元素,n 也可以为 0
    }

    void testForForeach_foreach()
    {
        foreach (var e in mData)
        {
            //Debug.Log(e);


        }
    }

    void testForForeach_for()
    {
        int len = mData.Count;
        for (int i = 0; i < len; i++)
        {
        }
    }

    void testForForeach_nonforeach()
    {
        var e = mData.GetEnumerator();
        while (e.MoveNext())
        {

        }
    }

    // Update is called once per frame
    void Update()
    {
        //testForForeach_foreach();
        //testForForeach_for(); testForForeach_nonforeach();
    }
}

测试结果

 

数据分析
反编译->C#代码


从代码中,我们可知 foreach 中,编译器加了不少代码。从这里应该是有发生过装箱的操作。从对 testForForeach_nonforeach 函数的反编译 C#代码来看,应该是 using 那块产生装箱操作。继续查看反编译的 IL 代码。

 

 

 

数据结论
可以显然可知,foreach 每次有 40B 的 GC 产生(这个是 Mono 的一个 bug)。其余两种方式不产生 GC。
产生 GC 的根本原因是使用了 using 语句。(GetEnumerator()返回值类型,在
using 中装箱了)
那么,我们写一段测试代码,在去验证一下:

void testForForeach_nonforeachUsing()
{
    using (var e = mData.GetEnumerator())
    {
        while (e.MoveNext())
        {

        }
    }
}

 得到的数据是确实是多了40B

意义
所以,在目前的项目中,foreach 还是有 GC 的。(不管是 IList 还是 ArrayList
都会有 GC)
具体见测试数据

建议项目中采用testForForeach_for()和testForForeach_nonforeach()的写法。尤其是在 update 或 LateUpdate 中。


在处理 Dictionary 遍历的时候,我们可以这样:

var enumerator = m_Dictionary.GetEnumerator();
while (enumerator.MoveNext())
{
    var element = enumerator.Current; element.Value.UpdateComponent(deltaTime);
}

 

字符串拼接真相
目的
在 Mono 下,研究两种常用的字符串拼接的内存与 GC 问题。

环境
同简介中的环境
测试代码

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;

public class TestCodPerfomanceStringBuilderScript : MonoBehaviour
{

    // Use this for initialization

    void Start()
    {
    }

    void testStringBuilder_plus()
    {
        string test = "abc" + "efg" + "h";
        int index = test.Length;
    }

    void testStringBuilder_StringBuilder()
    {
        StringBuilder testData = new StringBuilder(); 
        testData.Append("abc"); 
        testData.Append("efg"); 
        testData.Append("h");
        string test = testData.ToString();

    }

    // Update is called once per frame 
    void Update ()
    {
        testStringBuilder_plus();
        //testStringBuilder_StringBuilder();
    }
}

 测试结果

数据结论
不同的使用场景结果是不同的,+ 号在几次连接中没有产生 GC。而在大量的连接过程中,产生大量的 GC。
意义
连接次数只有几次(10 以内),此时应该直接用 + 号连接,不产生 GC。实际上,编译器已经做了优化。
其余的使用 StringBuilder , StringBuilder 内部 Buffer  的缺省值为 16 ,按 StringBuilder 的使用场景,Buffer 肯定得重新分配。经验值一般用 256 作
为 Buffer 的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定 Buffer 的初值。使用 new  StringBuilder(256) 就将 Buffer 的初始
长度设为了 256。

<think>首先,用户的问题是:“请问Java List forEach with StringBuilder append example”,意思是询问如何在Java中使用List的forEach方法StringBuilder的append方法来拼接字符串的例子。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式,但这里没有数学内容,所以不需要。 - 独立公式必须使用$$...$$格式,但这里没有公式。 - LaTeX语法正确:同样,没有数学内容。 - 使用中文回答:我必须用中文回复。 - 生成相关问题:在回答结束后,生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识:参考提供的引用,我需要引用相关内容。 用户意图:从上下文看,用户想了解如何使用Java中的`StringBuilder``List.forEach()`进行字符串拼接。参考引用中提到了效率问题,比如避免使用+号拼接字符串,因为它效率低。 引用内容: - [^1]: 提到了使用`list.forEach(System.out::println)``IntStream.range(1, 101).forEach(stringBuilder::append)`,展示了方法引用。 - [^2]: 强调了避免使用+号拼接字符串,使用StringBuilder提高效率。 - [^3]: 提到了Java的foreachhasNext,以及使用流操作拼接字符串。 - [^4]: 关于Excel导入导出,似乎不直接相关。 所以,我需要构建一个清晰的例子,展示如何用`List.forEach()``StringBuilder.append()`来拼接字符串。 回答结构: 1. 解释问题:简要介绍使用场景好处(如效率)。 2. 提供代码示例:给出Java代码示例。 3. 解释代码:解释关键部分。 4. 添加引用:自然地引用相关内容。 5. 生成相关问题:在最后添加相关问题。 确保回答真实可靠:基于Java标准库的知识。 完整回答: - 开头:直接回应问题。 - 主体:代码示例解释。 - 结尾:总结或提示。 - 相关问题:生成2-3个相关问题。 相关问题想法: 1. 如何优化字符串拼接性能? 2. 在Java中,List.forEach()传统for循环的区别? 3. StringBuilder的其他用法或常见错误? 现在,构建代码示例: 假设有一个字符串列表,我们想用逗号分隔拼接它们。 示例代码: ```java import java.util.List; import java.util.Arrays; public class StringBuilderExample { public static void main(String[] args) { List<String> list = Arrays.asList("Apple", "Banana", "Cherry"); StringBuilder sb = new StringBuilder(); // 使用 forEach StringBuilder append list.forEach(item -> sb.append(item).append(", ")); // 移除最后的逗号空格 if (sb.length() > 0) { sb.setLength(sb.length() - 2); // 移除 ", " } System.out.println(sb.toString()); // 输出: Apple, Banana, Cherry } } ``` 在引用中,提到了方法引用,所以可以添加一个版本使用`StringBuilder::append`,但`append`需要一个参数,所以需要调整。在`forEach`中,`item -> sb.append(item)` 可以写成 `sb::append`,但要注意,`forEach`接受一个Consumer,而`sb::append`是一个方法引用,需要匹配Consumer的参数类型。 在Java中,`List.forEach`接受一个Consumer,所以`item -> sb.append(item)` 等价于 `sb::append`,因为`StringBuilder.append(String)` 接受一个String参数。 但在我的例子中,我添加了额外的逗号,所以不能直接用方法引用。我可以先展示基本用法。 改进示例: ```java list.forEach(sb::append); // 这会直接追加每个元素,没有分隔符。 ``` 然后添加分隔符的版本。 为了完整,展示一个带分隔符的拼接。 参考引用[^3],提到了`String.join`,但用户指定了forEachStringBuilder。 最终代码: ```java List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry"); StringBuilder sb = new StringBuilder(); fruits.forEach(fruit -> { sb.append(fruit); sb.append(", "); }); // 移除多余的逗号空格 if (sb.length() > 0) { sb.delete(sb.length() - 2, sb.length()); } System.out.println(sb.toString()); // 输出: Apple, Banana, Cherry ``` 或者,使用更高效的方式,比如在追加前检查是否是最后一个元素。 但为了简单,保持当前。 现在,回答结构: 1. 介绍:解释为什么使用StringBuilderforEach。 2. 代码示例。 3. 解释。 4. 引用相关部分。 5. 生成问题。</think>### Java List forEachStringBuilder append 示例 在 Java 中,使用 `List.forEach()` 方法结合 `StringBuilder.append()` 进行字符串拼接是一种高效的方式,尤其适合处理大量数据。相比于直接使用 `+` 操作符拼接字符串(可能导致性能下降),`StringBuilder` 通过可变字符序列显著提升效率[^2]。下面我将逐步解释并提供一个完整示例。 #### 步骤说明 1. **创建 StringBuilder 对象**:初始化一个 `StringBuilder` 实例用于构建最终字符串。 2. **使用 List.forEach()**:遍历列表元素,对每个元素调用 `StringBuilder.append()` 追加内容。 3. **添加分隔符**:在元素间插入分隔符(如逗号),需注意处理末尾多余分隔符。 4. **输出结果**:调用 `toString()` 获取最终拼接的字符串。 #### 完整代码示例 ```java import java.util.Arrays; import java.util.List; public class ListStringBuilderExample { public static void main(String[] args) { // 示例列表:字符串元素 List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry"); // 创建 StringBuilder 对象 StringBuilder sb = new StringBuilder(); // 使用 forEach append 拼接字符串,添加逗号分隔符 fruits.forEach(fruit -> { sb.append(fruit); // 追加当前元素 sb.append(", "); // 追加分隔符 }); // 移除末尾多余的逗号空格 if (sb.length() > 0) { sb.delete(sb.length() - 2, sb.length()); // 删除最后两个字符(", ") } // 输出结果 System.out.println("拼接结果: " + sb.toString()); // 输出: Apple, Banana, Cherry } } ``` #### 关键点解释 - **效率优势**:`StringBuilder` 避免了 `+` 操作符创建中间字符串对象的开销,在处理 1 万条数据时可能节省数秒时间[^2]。 - **方法引用简化**:可以使用方法引用 `sb::append` 进一步简化代码(但需注意分隔符处理): ```java fruits.forEach(sb::append); // 直接追加元素,无分隔符 ``` - **分隔符处理**:示例中通过 `delete()` 移除末尾多余字符,也可在遍历时使用条件判断避免追加最后一个分隔符。 - **适用场景**:适合日志生成、SQL 语句拼接或大数据导出等任务[^4]。 > **注意**:在 Java 8+ 中,若仅需简单拼接(如逗号分隔),`String.join()` 或流式操作更简洁(例如 `String.join(", ", fruits)`),但 `StringBuilder` 在复杂或高性能场景更灵活[^3]。 #### 性能提示 - 避免在循环中使用 `+` 拼接字符串,尤其在处理长列表时,这可能导致大量临时对象性能下降[^2]。 - 对于线程安全场景,可替换为 `StringBuffer`,但通常 `StringBuilder` 更快。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值