Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >.NET性能优化-使用ValueStringBuilder拼接字符串

.NET性能优化-使用ValueStringBuilder拼接字符串

作者头像
用户9127601
发布于 2022-06-09 13:45:17
发布于 2022-06-09 13:45:17
53700
代码可运行
举报
文章被收录于专栏:dotNET编程大全dotNET编程大全
运行总次数:0
代码可运行

前言

这一次要和大家分享的一个Tips是在字符串拼接场景使用的,我们经常会遇到有很多短小的字符串需要拼接的场景,在这种场景下及其的不推荐使用String.Concat也就是使用+=运算符。 目前来说官方最推荐的方案就是使用StringBuilder来构建这些字符串,那么有什么更快内存占用更低的方式吗?那就是今天要和大家介绍的ValueStringBuilder

ValueStringBuilder

ValueStringBuilder不是一个公开的API,但是它被大量用于.NET的基础类库中,由于它是值类型的,所以它本身不会在堆上分配,不会有GC的压力。 微软提供的ValueStringBuilder有两种使用方式,一种是自己已经有了一块内存空间可供字符串构建使用。这意味着你可以使用栈空间,也可以使用堆空间甚至非托管堆的空间,这对于GC来说是非常友好的,在高并发情况下能大大降低GC压力。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 构造函数:传入一个Span的Buffer数组
public ValueStringBuilder(Span<char> initialBuffer);

// 使用方式:
// 栈空间
var vsb = new ValueStringBuilder(stackalloc char[512]);
// 普通数租
var vsb = new ValueStringBuilder(new char[512]);
// 使用非托管堆
var length = 512;
var ptr = NativeMemory.Alloc((nuint)(512 * Unsafe.SizeOf<char>()));
var span = new Span<char>(ptr, length);
var vsb = new ValueStringBuilder(span);
.....
NativeMemory.Free(ptr); // 非托管堆用完一定要Free

另外一种方式是指定一个容量,它会从默认的ArrayPoolchar对象池中获取缓冲空间,因为使用的是对象池,所以对于GC来说也是比较友好的,千万需要注意,池中的对象一定要记得归还。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 传入预计的容量
public ValueStringBuilder(int initialCapacity)  
{  
    // 从对象池中获取缓冲区
    _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);  
    ......
}

那么我们就来比较一下使用+=StringBuilderValueStringBuilder这几种方式的性能吧。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 一个简单的类
public class SomeClass  
{  
    public int Value1; public int Value2; public float Value3;  
    public double Value4; public string? Value5; public decimal Value6;  
    public DateTime Value7; public TimeOnly Value8; public DateOnly Value9;  
    public int[]? Value10;  
}
// Benchmark类
[MemoryDiagnoser]  
[HtmlExporter]  
[Orderer(SummaryOrderPolicy.FastestToSlowest)]  
public class StringBuilderBenchmark  
{  
    private static readonly SomeClass Data;  
    static StringBuilderBenchmark()  
    {  
        var baseTime = DateTime.Now;  
        Data = new SomeClass  
        {  
            Value1 = 100, Value2 = 200, Value3 = 333,  
            Value4 = 400, Value5 = string.Join('-', Enumerable.Range(0, 10000).Select(i => i.ToString())),  
            Value6 = 655, Value7 = baseTime.AddHours(12),  
            Value8 = TimeOnly.MinValue, Value9 = DateOnly.MaxValue,  
            Value10 = Enumerable.Range(0, 5).ToArray()  
        };  
    }

    // 使用我们熟悉的StringBuilder
    [Benchmark(Baseline = true)]  
    public string StringBuilder()  
    {  
        var data = Data;  
        var sb = new StringBuilder();  
        sb.Append("Value1:"); sb.Append(data.Value1);  
        if (data.Value2 > 10)  
        {  
            sb.Append(" ,Value2:"); sb.Append(data.Value2);  
        }  
        sb.Append(" ,Value3:"); sb.Append(data.Value3);  
        sb.Append(" ,Value4:"); sb.Append(data.Value4);  
        sb.Append(" ,Value5:"); sb.Append(data.Value5);  
        if (data.Value6 > 20)  
        {  
            sb.Append(" ,Value6:"); sb.AppendFormat("{0:F2}", data.Value6);  
        }  
        sb.Append(" ,Value7:"); sb.AppendFormat("{0:yyyy-MM-dd HH:mm:ss}", data.Value7);  
        sb.Append(" ,Value8:"); sb.AppendFormat("{0:HH:mm:ss}", data.Value8);  
        sb.Append(" ,Value9:"); sb.AppendFormat("{0:yyyy-MM-dd}", data.Value9);  
        sb.Append(" ,Value10:");  
        if (data.Value10 is null or {Length: 0}) return sb.ToString();  
        for (int i = 0; i < data.Value10.Length; i++)  
        {  
            sb.Append(data.Value10[i]);  
        }  
  
        return sb.ToString();  
    }

    // StringBuilder使用Capacity
    [Benchmark]  
    public string StringBuilderCapacity()  
    {  
        var data = Data;  
        var sb = new StringBuilder(20480);  
        sb.Append("Value1:"); sb.Append(data.Value1);  
        if (data.Value2 > 10)  
        {  
            sb.Append(" ,Value2:"); sb.Append(data.Value2);  
        }  
        sb.Append(" ,Value3:"); sb.Append(data.Value3);  
        sb.Append(" ,Value4:"); sb.Append(data.Value4);  
        sb.Append(" ,Value5:"); sb.Append(data.Value5);  
        if (data.Value6 > 20)  
        {  
            sb.Append(" ,Value6:"); sb.AppendFormat("{0:F2}", data.Value6);  
        }  
        sb.Append(" ,Value7:"); sb.AppendFormat("{0:yyyy-MM-dd HH:mm:ss}", data.Value7);  
        sb.Append(" ,Value8:"); sb.AppendFormat("{0:HH:mm:ss}", data.Value8);  
        sb.Append(" ,Value9:"); sb.AppendFormat("{0:yyyy-MM-dd}", data.Value9);  
        sb.Append(" ,Value10:");  
        if (data.Value10 is null or {Length: 0}) return sb.ToString();  
        for (int i = 0; i < data.Value10.Length; i++)  
        {  
            sb.Append(data.Value10[i]);  
        }  
  
        return sb.ToString();  
    }  

    // 直接使用+=拼接字符串
    [Benchmark]  
    public string StringConcat()  
    {  
        var str = "";  
        var data = Data;  
        str += ("Value1:"); str += (data.Value1);  
        if (data.Value2 > 10)  
        {  
            str += " ,Value2:"; str += data.Value2;  
        }  
        str += " ,Value3:"; str += (data.Value3);  
        str += " ,Value4:"; str += (data.Value4);  
        str += " ,Value5:"; str += (data.Value5);  
        if (data.Value6 > 20)  
        {  
            str += " ,Value6:"; str += data.Value6.ToString("F2");  
        }  
        str += " ,Value7:"; str += data.Value7.ToString("yyyy-MM-dd HH:mm:ss");  
        str += " ,Value8:"; str += data.Value8.ToString("HH:mm:ss");  
        str += " ,Value9:"; str += data.Value9.ToString("yyyy-MM-dd");  
        str += " ,Value10:";  
        if (data.Value10 is not null && data.Value10.Length > 0)  
        {  
            for (int i = 0; i < data.Value10.Length; i++)  
            {  
                str += (data.Value10[i]);  
            }     
        }  
  
        return str;  
    }  
  
    // 使用栈上分配的ValueStringBuilder
    [Benchmark]  
    public string ValueStringBuilderOnStack()  
    {  
        var data = Data;  
        Span<char> buffer = stackalloc char[20480];  
        var sb = new ValueStringBuilder(buffer);  
        sb.Append("Value1:"); sb.AppendSpanFormattable(data.Value1);  
        if (data.Value2 > 10)  
        {  
            sb.Append(" ,Value2:"); sb.AppendSpanFormattable(data.Value2);  
        }  
        sb.Append(" ,Value3:"); sb.AppendSpanFormattable(data.Value3);  
        sb.Append(" ,Value4:"); sb.AppendSpanFormattable(data.Value4);  
        sb.Append(" ,Value5:"); sb.Append(data.Value5);  
        if (data.Value6 > 20)  
        {  
            sb.Append(" ,Value6:"); sb.AppendSpanFormattable(data.Value6, "F2");  
        }  
        sb.Append(" ,Value7:"); sb.AppendSpanFormattable(data.Value7, "yyyy-MM-dd HH:mm:ss");  
        sb.Append(" ,Value8:"); sb.AppendSpanFormattable(data.Value8, "HH:mm:ss");  
        sb.Append(" ,Value9:"); sb.AppendSpanFormattable(data.Value9, "yyyy-MM-dd");  
        sb.Append(" ,Value10:");  
        if (data.Value10 is not null && data.Value10.Length > 0)  
        {  
            for (int i = 0; i < data.Value10.Length; i++)  
            {  
                sb.AppendSpanFormattable(data.Value10[i]);  
            }     
        }  
  
        return sb.ToString();  
    }
    // 使用ArrayPool 堆上分配的StringBuilder
    [Benchmark]  
    public string ValueStringBuilderOnHeap()  
    {  
        var data = Data;  
        var sb = new ValueStringBuilder(20480);  
        sb.Append("Value1:"); sb.AppendSpanFormattable(data.Value1);  
        if (data.Value2 > 10)  
        {  
            sb.Append(" ,Value2:"); sb.AppendSpanFormattable(data.Value2);  
        }  
        sb.Append(" ,Value3:"); sb.AppendSpanFormattable(data.Value3);  
        sb.Append(" ,Value4:"); sb.AppendSpanFormattable(data.Value4);  
        sb.Append(" ,Value5:"); sb.Append(data.Value5);  
        if (data.Value6 > 20)  
        {  
            sb.Append(" ,Value6:"); sb.AppendSpanFormattable(data.Value6, "F2");  
        }  
        sb.Append(" ,Value7:"); sb.AppendSpanFormattable(data.Value7, "yyyy-MM-dd HH:mm:ss");  
        sb.Append(" ,Value8:"); sb.AppendSpanFormattable(data.Value8, "HH:mm:ss");  
        sb.Append(" ,Value9:"); sb.AppendSpanFormattable(data.Value9, "yyyy-MM-dd");  
        sb.Append(" ,Value10:");  
        if (data.Value10 is not null && data.Value10.Length > 0)  
        {  
            for (int i = 0; i < data.Value10.Length; i++)  
            {  
                sb.AppendSpanFormattable(data.Value10[i]);  
            }     
        }
  
        return sb.ToString();  
    }
      
}

结果如下所示。

从上图的结果中,我们可以得出如下的结论。

  • 使用StringConcat是最慢的,这种方式是无论如何都不推荐的。
  • 使用StringBuilder要比使用StringConcat快6.5倍,这是推荐的方法。
  • 设置了初始容量的StringBuilder要比直接使用StringBuilder快25%,正如我在你应该为集合类型设置初始大小一样,设置初始大小绝对是相当推荐的做法。
  • 栈上分配的ValueStringBuilderStringBuilder要快50%,比设置了初始容量的StringBuilder还快25%,另外它的GC次数是最低的。
  • 堆上分配的ValueStringBuilderStringBuilder要快55%,他的GC次数稍高与栈上分配。 从上面的结论中,我们可以发现ValueStringBuilder的性能非常好,就算是在栈上分配缓冲区,性能也比StringBuilder快25%。

源码解析

ValueStringBuilder的源码不长,我们挑几个重要的方法给大家分享一下,部分源码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 使用 ref struct 该对象只能在栈上分配
public ref struct ValueStringBuilder
{
    // 如果从ArrayPool里分配buffer 那么需要存储一下
    // 以便在Dispose时归还
    private char[]? _arrayToReturnToPool;
    // 暂存外部传入的buffer
    private Span<char> _chars;
    // 当前字符串长度
    private int _pos;

    // 外部传入buffer
    public ValueStringBuilder(Span<char> initialBuffer)
    {
        // 使用外部传入的buffer就不使用从pool里面读取的了
        _arrayToReturnToPool = null;
        _chars = initialBuffer;
        _pos = 0;
    }

    public ValueStringBuilder(int initialCapacity)
    {
        // 如果外部传入了capacity 那么从ArrayPool里面获取
        _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);
        _chars = _arrayToReturnToPool;
        _pos = 0;
    }

    // 返回字符串的Length 由于Length可读可写
    // 所以重复使用ValueStringBuilder只需将Length设置为0
    public int Length
    {
        get => _pos;
        set
        {
            Debug.Assert(value >= 0);
            Debug.Assert(value <= _chars.Length);
            _pos = value;
        }
    }

    ......

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Append(char c)
    {
        // 添加字符非常高效 直接设置到对应Span位置即可
        int pos = _pos;
        if ((uint) pos < (uint) _chars.Length)
        {
            _chars[pos] = c;
            _pos = pos + 1;
        }
        else
        {
            // 如果buffer空间不足,那么会走
            GrowAndAppend(c);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Append(string? s)
    {
        if (s == null)
        {
            return;
        }

        // 追加字符串也是一样的高效
        int pos = _pos;
        // 如果字符串长度为1 那么可以直接像追加字符一样
        if (s.Length == 1 && (uint) pos < (uint) _chars .Length)
        {
            _chars[pos] = s[0];
            _pos = pos + 1;
        }
        else
        {
            // 如果是多个字符 那么使用较慢的方法
            AppendSlow(s);
        }
    }

    private void AppendSlow(string s)
    {
        // 追加字符串 空间不够先扩容
        // 然后使用Span复制 相当高效
        int pos = _pos;
        if (pos > _chars.Length - s.Length)
        {
            Grow(s.Length);
        }

        s
#if !NETCOREAPP
                .AsSpan()
#endif
            .CopyTo(_chars.Slice(pos));
        _pos += s.Length;
    }

    // 对于需要格式化的对象特殊处理
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void AppendSpanFormattable<T>(T value, string? format = null, IFormatProvider? provider = null)
        where T : ISpanFormattable
    {
        // ISpanFormattable非常高效
        if (value.TryFormat(_chars.Slice(_pos), out int charsWritten, format, provider))
        {
            _pos += charsWritten;
        }
        else
        {
            Append(value.ToString(format, provider));
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void GrowAndAppend(char c)
    {
        // 单个字符扩容在添加
        Grow(1);
        Append(c);
    }

    // 扩容方法
    [MethodImpl(MethodImplOptions.NoInlining)]
    private void Grow(int additionalCapacityBeyondPos)
    {
        Debug.Assert(additionalCapacityBeyondPos > 0);
        Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos,
            "Grow called incorrectly, no resize is needed.");

        // 同样也是2倍扩容,默认从对象池中获取buffer
        char[] poolArray = ArrayPool<char>.Shared.Rent((int) Math.Max((uint) (_pos + additionalCapacityBeyondPos),
            (uint) _chars.Length * 2));

        _chars.Slice(0, _pos).CopyTo(poolArray);

        char[]? toReturn = _arrayToReturnToPool;
        _chars = _arrayToReturnToPool = poolArray;
        if (toReturn != null)
        {
            // 如果原本就是使用的对象池 那么必须归还
            ArrayPool<char>.Shared.Return(toReturn);
        }
    }

    // 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Dispose()
    {
        char[]? toReturn = _arrayToReturnToPool;
        this = default; // 为了安全,在释放时置空当前对象
        if (toReturn != null)
        {
            // 一定要记得归还对象池
            ArrayPool<char>.Shared.Return(toReturn);
        }
    }
}

从上面的源码我们可以总结出ValueStringBuilder的几个特征:

  • 比起StringBuilder来说,实现方式非常简单。
  • 一切都是为了高性能,比如各种Span的用法,各种内联参数,以及使用对象池等等。
  • 内存占用非常低,它本身就是结构体类型,另外它是ref struct,意味着不会被装箱,不会在堆上分配。

适用场景

ValueStringBuilder是一种高性能的字符串创建方式,针对于不同的场景,可以有不同的使用方式。 1.非常高频次的字符串拼接的场景,并且字符串长度较小,此时可以使用栈上分配的ValueStringBuilder。 大家都知道现在ASP.NET Core性能非常好,在其依赖的内部库UrlBuilder中,就使用栈上分配,因为栈上分配在当前方法结束后内存就会回收,所以不会造成任何GC压力。

2.非常高频次的字符串拼接场景,但是字符串长度不可控,此时使用ArrayPool指定容量的ValueStringBuilder。比如在.NET BCL库中有很多场景使用,比如动态方法的ToString实现。从池中分配虽然没有栈上分配那么高效,但是一样的能降低内存占用和GC压力。

3. 非常高频次的字符串拼接场景,但是字符串长度可控,此时可以栈上分配和ArrayPool分配联合使用,比如正则表达式解析类中,如果字符串长度较小那么使用栈空间,较大那么使用ArrayPool。

需要注意的场景

1.在async\await中无法使用ValueStringBuilder。原因大家也都知道,因为ValueStringBuilderref struct,它只能在栈上分配,async\await会编译成状态机拆分await前后的方法,所以ValueStringBuilder不好在方法内传递,不过编译器也会警告。

2.无法将ValueStringBuilder作为返回值返回,因为在当前栈上分配,方法结束后它会被释放,返回它将指向未知的地址。这个编译器也会警告。

3.如果要将ValueStringBuilder传递给其它方法,那么必须使用ref传递,否则发生值拷贝会存在多个实例。这个编译器不会警告,但是你必须非常注意。

4. 如果使用栈上分配,那么Buffer大小控制在5KB内比较稳妥,至于为什么需要这样,后面有机会在讲一讲。

总结

今天和大家分享了一下高性能几乎无内存占用的字符串拼接结构体ValueStringBuilder,在大多数的场景还是推荐大家使用。但是要非常注意上面提到的的几个场景,如果不符合条件,那么大家还是可以使用高效的StringBuilder来进行字符串拼接。

本文源码链接: https://github.com/InCerryGit/BlogCode-Use-ValueStringBuilder

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 dotNET编程大全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C# 高效率创建字符串类(StringBuilder)
因为String类型代表不可变字符串,所以无法对当前String类型实例进行处理.所以FCL提供了System.Text.StringBuilder类型,它可以接受字符串和字符作为参数,并对其进行高效动态处理,最终返回String对象.so,你可以将StringBuilder想象成对String字符串进行各种骚操作的特殊的构造器.
郑小超.
2018/08/01
1.4K0
C# 高效率创建字符串类(StringBuilder)
Leetcode#557. Reverse Words in a String III(反转字符串中的单词 III)
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
武培轩
2018/09/28
4400
源码上看 .NET 中 StringBuilder 拼接字符串的实现
前几天写了一篇StringBuilder与TextWriter二者之间区别的文章(链接)。当时提了一句没有找到相关源码,于是随后有很多热心人士给出了相关的源码链接(链接),感谢大家。这几天抽了点时间查看了下StringBuilder是如何动态构造字符串的,发现在.NET Core中字符串的构建似乎和我原先猜想的并不完全一样,故此写了这篇文章,如有错误,欢迎指出。
Jlion
2022/04/07
8970
源码上看 .NET 中 StringBuilder 拼接字符串的实现
教妹学 Java 第 37 讲:字符串拼接
“哥,你让我看的《Java 开发手册》上有这么一段内容:循环体内,拼接字符串最好使用 StringBuilder 的 append() 方法,而不是 + 号操作符。这是为什么呀?”三妹疑惑地问。
沉默王二
2021/07/16
3210
昨天,还在 for 循环里写加号拼接字符串的那个同事,今天已经不在了
都说 StringBuilder 在处理字符串拼接上效率要强于 String,但有时候我们的理解可能会存在一定的偏差。最近我在测试数据导入效率的时候就发现我以前对 StringBuilder 的部分理解是错误的。后来我通过实践测试 + 找原理 的方式搞清楚了这块的逻辑。现在将过程分享给大家
开发者技术前线
2020/11/24
3750
昨天,还在 for 循环里写加号拼接字符串的那个同事,今天已经不在了
C#.Net筑基-String字符串超全总结 [深度好文]
字符串是日常编码中最常用的引用类型了,可能没有之一,加上字符串的不可变性、驻留性,很容易产生性能问题,因此必须全面了解一下。
郑子铭
2024/07/12
6470
C#.Net筑基-String字符串超全总结 [深度好文]
大话字符串逆序
面试官:“先来一点基础的吧,用Java写一个方法,入参是一个字符串,返回逆序后的字符串。”
万猫学社
2022/04/22
2040
聊聊字符串拼接的哪一些事儿
​ 字符串对我编程人员来说是字符串时每天见面的常客,你不认识不熟悉他都不得行,字符串的拼接更是家常便饭,那么在实际开发过程中实现字符串的拼接有哪一些方式呢?咱们一起来聊聊,来交流沟通,学习一波。也许你会说,那也太简单了嘛,谁不会啊,哈哈,使用起来确实简单,但是不一定我们都使用的方式还有优秀的方式吗?
小小许
2020/01/15
5710
聊聊字符串拼接的哪一些事儿
Java字符类Character字符串类String和StringBuffer
我们知道Java内置了数据类型char,但面向对象的Java在实际处理过程中需要的是对象,于是包装类Character就被设计了出来。
dongfanger
2021/10/21
6220
Java String + 拼接字符串原理
很明确,上述代码输出的结果是:"111111222222",但是它工作原理是怎样的呢?
用户7886150
2021/04/27
8790
面试题精选:字符串替换
字符串处理在程序猿日常工作工作中非常常见,常见到几乎各种语言中都已经封装好了字符串相关的API,我们只需要直接拿过来用就好。就拿Java为例,jdk中的String()类几乎封装了所有字符串相关的操作,其方法数量有近百个,几乎满足了程序猿所有字符串相关的操作。
xindoo
2021/01/22
4640
面试题精选:字符串替换
Java字符串之性能优化
在程序中你可能时常会需要将别的类型转化成String,有时候可能是一些基础类型的值。在拼接字符串的时候,如果你有两个或者多个基础类型的值需要放到前面,你需要显式的将第一个值转化成String(不然的话像System.out.println(1+’a')会输出98,而不是”1a”)。当然了,有一组String.valueOf方法可以完成这个(或者是基础类型对应的包装类的方法),不过如果有更好的方法能少敲点代码的话,谁还会愿意这么写呢?
哲洛不闹
2018/09/18
4390
c#字符串操作方法实例
# 字符串是使用 string 关键字声明的一个字符数组。字符串是使用引号声明的,如下例所示: string s = "Hello, World!"; 字符串对象是“不可变的”,即它们一旦创建就无法更改。对字符串进行操作的方法实际上返回的是新的字符串对象。因此,出于性能方面的原因,大量的连接或其他涉及字符串的操作应当用 StringBuilder 类执行,如下所示: System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.App
欢醉
2018/01/22
1.8K0
7. JDK拍了拍你:字符串拼接一定记得用MessageFormat#format
你好,我是A哥(YourBatman)。本文所属专栏:Spring类型转换,公号后台回复专栏名即可获取全部内容。
YourBatman
2022/03/08
1.5K0
7. JDK拍了拍你:字符串拼接一定记得用MessageFormat#format
玩转字符串篇--代码自动生成,解放双手
零、前言: 1.RecyclerView的Adapter自动生成器(含ViewHolder) 2.自定义属性的自定义View代码生成器(含自定义属性的初始化) 3.svg图标转换为Android可用xml生成器 最近喜欢切割字符串,这三个类是近期的作品,感觉挺好用的,在此分享一下 三个工具都会贴在本文末尾,本文末尾,本文末尾 ---- 一、RecyclerView的Adapter自动生成器(含ViewHolder) 最近写了几个RecyclerView的Adapter,控件一多ViewH
张风捷特烈
2022/09/20
4250
玩转字符串篇--代码自动生成,解放双手
SilverLight企业应用框架设计【四】实体层设计+为客户端动态生成服务代理(自己实现RiaService)
但是在silverlight客户端用处就非常大(等会会说道为silverlight客户端自动生成实体类型,silverlight 4.0是有Entity类的)
liulun
2022/05/09
7780
SilverLight企业应用框架设计【四】实体层设计+为客户端动态生成服务代理(自己实现RiaService)
笔试题—字符串常见的算法题集锦
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/52602327
程序员徐公
2018/09/18
9670
还在用StringBuilder进行字符串拼接?那你就OUT了
大部分人会这么做,但是我们发现里面有太重复的"," 和"+"了。感觉拼接不是很灵活。
Lvshen
2022/05/05
3490
还在用StringBuilder进行字符串拼接?那你就OUT了
.NET面试题解析(03)-string与字符串操作
4.以下代码执行后内存中会存在多少个字符串?分别是什么?输出结果是什么?为什么呢?
莫问今朝
2018/08/31
5630
.NET面试题解析(03)-string与字符串操作
四、字符串【黑马JavaSE笔记】
API(Application Programming Interface):应用程序编程接口
啵啵鱼
2022/11/23
3440
四、字符串【黑马JavaSE笔记】
推荐阅读
相关推荐
C# 高效率创建字符串类(StringBuilder)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验