C# 如何将int转换为char[],而不在C中生成垃圾#

C# 如何将int转换为char[],而不在C中生成垃圾#,c#,.net,C#,.net,毫无疑问,考虑到ToString()和Convert.ToString()的可用性,这似乎是一个奇怪的请求,但我需要将无符号整数(即UInt32)转换为其字符串表示形式,但我需要将答案存储到char[]中 原因是为了提高效率,我正在使用字符数组,并且在对象创建时,目标char[]被初始化为char[10](保存UInt32.MaxValue的字符串表示形式)的成员,从理论上讲,在不生成任何垃圾的情况下执行转换(我的意思是不在托管堆中生成任何临时对象) 有人能想出一个好办法来实现这一点吗 (我在F

毫无疑问,考虑到
ToString()
Convert.ToString()
的可用性,这似乎是一个奇怪的请求,但我需要将无符号整数(即
UInt32
)转换为其字符串表示形式,但我需要将答案存储到
char[]

原因是为了提高效率,我正在使用字符数组,并且在对象创建时,目标
char[]
被初始化为
char[10]
(保存
UInt32.MaxValue的字符串表示形式)的成员,从理论上讲,在不生成任何垃圾的情况下执行转换(我的意思是不在托管堆中生成任何临时对象)

有人能想出一个好办法来实现这一点吗


(我在Framework 3.5SP1中工作,以防出现任何相关情况。)

以下代码执行此操作,但有以下警告:它不尊重区域性设置,但始终输出正常的十进制数字

public static int ToCharArray(uint value, char[] buffer, int bufferIndex) {
    if (value == 0) {
        buffer[bufferIndex] = '0';
        return 1;
    }
    int len = (int)Math.Ceiling(Math.Log10(value));
    for (int i = len-1; i>= 0; i--) {
        buffer[bufferIndex+i] = (char)('0'+(value%10));
        value /= 10;
    }
    return len;
}
返回的值是使用了多少
char[]

编辑(对于arx):以下版本避免了浮点运算并将缓冲区交换到位:

public static int ToCharArray(uint value, char[] buffer, int bufferIndex) {
    if (value == 0) {
        buffer[bufferIndex] = '0';
        return 1;
    }
    int bufferEndIndex = bufferIndex;
    while (value > 0) {
        buffer[bufferEndIndex++] = (char)('0'+(value%10));
        value /= 10;
    }
    int len = bufferEndIndex-bufferIndex;
    while (--bufferEndIndex > bufferIndex) {
        char ch = buffer[bufferEndIndex];
        buffer[bufferEndIndex] = buffer[bufferIndex];
        buffer[bufferIndex++] = ch;
    }
    return len;
}
这里还有另一个变化,它计算一个小循环中的位数:

public static int ToCharArray(uint value, char[] buffer, int bufferIndex) {
    if (value == 0) {
        buffer[bufferIndex] = '0';
        return 1;
    }
    int len = 1;
    for (uint rem = value/10; rem > 0; rem /= 10) {
        len++;
    }
    for (int i = len-1; i>= 0; i--) {
        buffer[bufferIndex+i] = (char)('0'+(value%10));
        value /= 10;
    }
    return len;
}

我把基准测试留给任何想做的人……;)

在我上面的评论之后,我想知道log10是否太慢了,所以我写了一个不使用它的版本

对于四位数字,这个版本大约快35%,对于十位数字,下降到大约快16%

一个缺点是,它需要为缓冲区中的整整十个数字留出空间

我发誓它没有任何虫子

public static int ToCharArray2(uint value, char[] buffer, int bufferIndex)
{
    const int maxLength = 10;

    if (value == 0)
    {
        buffer[bufferIndex] = '0';
        return 1;
    }

    int startIndex = bufferIndex + maxLength - 1;
    int index = startIndex;
    do
    {
        buffer[index] = (char)('0' + value % 10);
        value /= 10;
        --index;
    }
    while (value != 0);

    int length = startIndex - index;

    if (bufferIndex != index + 1)
    {
        while (index != startIndex)
        {
            ++index;
            buffer[bufferIndex] = buffer[index];
            ++bufferIndex;
        }
    }

    return length;
}
更新

我应该补充一点,我使用的是奔腾4。较新的处理器可以更快地计算超越函数

结论

我昨天意识到我犯了一个小学生的错误,并在调试版本上运行了基准测试。所以我又运行了一次,但实际上没有多大区别。第一列显示正在转换的数字中的位数。其余列以毫秒为单位显示转换500000个数字的时间

uint的结果:

    luc1   arx henk1  luc3 henk2  luc2
 1   715   217   966   242   837   244
 2   877   420  1056   541   996   447
 3  1059   608  1169   835  1040   610
 4  1184   795  1282  1116  1162   801
 5  1403   969  1405  1396  1279   978
 6  1572  1149  1519  1674  1399  1170
 7  1740  1335  1648  1952  1518  1352
 8  1922  1675  1868  2233  1750  1545
 9  2087  1791  2005  2511  1893  1720
10  2263  2103  2139  2797  2012  1985
ulong的结果:

    luc1   arx henk1  luc3 henk2  luc2
 1   802   280   998   390   856   317
 2   912   516  1102   729   954   574
 3  1066   746  1243  1060  1056   818
 4  1300  1141  1362  1425  1170  1210
 5  1557  1363  1503  1742  1306  1436
 6  1801  1603  1612  2233  1413  1672
 7  2269  1814  1723  2526  1530  1861
 8  2208  2142  1920  2886  1634  2149
 9  2360  2376  2063  3211  1775  2339
10  2615  2622  2213  3639  2011  2697
11  3048  2996  2513  4199  2244  3011
12  3413  3607  2507  4853  2326  3666
13  3848  3988  2663  5618  2478  4005
14  4298  4525  2748  6302  2558  4637
15  4813  5008  2974  7005  2712  5065
16  5161  5654  3350  7986  2994  5864
17  5997  6155  3241  8329  2999  5968
18  6490  6280  3296  8847  3127  6372
19  6440  6720  3557  9514  3386  6788
20  7045  6616  3790 10135  3703  7268
luc1:Lucero的第一个函数

我的功能

henk1:Henk函数

luc3-Lucero的第三个函数

henk2:没有复制到char数组的Henk函数;i、 e.只需测试ToString()的性能

luc2:Lucero的第二个函数

这种特殊的顺序就是它们产生的顺序

我也在没有henk1和henk2的情况下运行了测试,因此不会有垃圾收集。其他三个功能的时间几乎相同。一旦基准测试超过三位数,内存使用就稳定了:因此GC在Henk的函数中发生,不会对其他函数产生有害影响


结论:只需调用ToString()

让我们保持简单并最大限度地利用现有代码:

public static int ToCharArray(uint value, char[] buffer, int bufferIndex) 
{
     string txt = value.ToString();
     txt.CopyTo(0, buffer, bufferIndex, txt.Length);     
     return txt.Length;
}

由于
txt
是超级便宜的gen0垃圾,因此非常有效

我参加聚会有点晚了,但我想你可能无法获得比简单地重新解释记忆更快、更少的记忆需求结果:

    [System.Security.SecuritySafeCritical]
    public static unsafe char[] GetChars(int value, char[] chars)
    {
        //TODO: if needed to use accross machines then
        //  this should also use BitConverter.IsLittleEndian to detect little/big endian
        //  and order bytes appropriately

        fixed (char* numPtr = chars)
            *(int*)numPtr = value;
        return chars;
    }

    [System.Security.SecuritySafeCritical]
    public static unsafe int ToInt32(char[] value)
    {
        //TODO: if needed to use accross machines then
        //  this should also use BitConverter.IsLittleEndian to detect little/big endian
        //  and order bytes appropriately

        fixed (char* numPtr = value)
            return *(int*)numPtr;
    }

这只是一个想法的演示-您显然需要添加字符数组大小的检查,并确保有正确的字节顺序编码。对于这些检查,您可以查看
位转换器的反射帮助器方法。

使用模数和除法手动进行转换您是否知道性能开销足以影响应用程序?int.MaxValue最多是30个数字,因此偶尔创建和垃圾收集的30个字符的字符串应该不会引起注意。这听起来很像过早优化。是否确实需要使用那些
char[]
而不是字符串?通常,为获得一个工作的实现而付出的额外努力不值得获得性能增益(如果有的话-短期小对象的分配和收集在.NET中非常便宜)。Matt-恕我直言,这一点相当明显-我正在寻找一个整洁的实现。JonathanW-换句话说,没有临时字符串。JonathanP,Lucero-你可能是对的,但我发现StackOverflow令人沮丧的一件事是,当问题的真正答案可能确实相关时,人们往往会跳上过早的优化潮流。也许我应该说得更清楚些,但我还是很感激有人花时间给出任何答案。@Steve,我确实给出了答案-但在这种情况下,关于过早优化的问题是强制性的,因为即使你的情况并非如此,其他人在谷歌搜索后阅读这个问题可能也是如此。这假设缓冲区已经被初始化为所有0'sLucero-这正是我所想的(现在我为过早优化而与你争论感到难过!)。
Math.天花(Math.Log10(value))
是我错过的技巧。非常感谢:-)。(另外,我不在乎文化设置,我只想要一组原始数字)。@Steve,不客气。我总是试图回答,这样答案就可以清楚地界定范围,以防其他人阅读@克里斯,不,我不假设数组有任何初始化;但我只会根据需要修改尽可能多的字符,这就是我返回长度的原因(如果您希望使用以零结尾的字符数组,那么还可以使用该长度添加
'\0'
),log10是一个非常昂贵的操作,在缓冲区的末尾以相反的顺序生成数字,然后将它们向下移动到前面,速度更快。我不知道这是否仍然是真的,但如果速度