C#StringBuilder的版本允许大于20亿个字符的字符串

时间:2019-01-13 15:37:15

标签: c# 64-bit pinvoke stringbuilder unsafe

在C#中, 64位Windows + .NET 4.5(或更高版本) + 在App.config文件中启用gcAllowVeryLargeObjects 允许大于2 GB的对象。太酷了,但是很遗憾,C#允许字符数组中的元素最大数量仍然为limited to about 2^31 = 2.15 billion chars。测试证实了这一点。

为解决此问题,Microsoft recommends in Option B本机创建数组(它们的“选项C”甚至不编译)。这很适合我,因为速度也是一个问题。是否有一些经过尝试和受信任的.NET不安全/本机/互操作/ PInvoke代码可以替换并用作增强的StringBuilder来达到20亿个元素限制?

首选使用不安全/ pinvoke代码,但不要破坏交易。另外,是否有可用的.NET(安全)版本?

理想情况下,替换StringBuilder时会以较小的大小(最好是用户定义的)开始,然后每次超过容量时都将大小重复增加一倍。我主要是在这里寻找append()功能。将字符串保存到文件中也很有用,尽管我敢肯定,如果还集成了substring()功能,我可以对该位进行编程。如果代码使用pinvoke,则显然必须考虑某种程度的内存管理,以避免内存丢失。

如果已经存在一些简单的代码,我不想重新创建轮子,但是另一方面,我不想仅为该简单功能而下载并合并DLL。

我还使用.NET 3.5来迎合没有最新版本Windows的用户。

2 个答案:

答案 0 :(得分:0)

根据this answer,C ++中的字符串大小不受限制。

您可以用C ++编写字符串处理代码,并使用DLL导入在C#代码和C ++代码之间进行通信。这使得从C#代码中调用C ++函数变得简单。

在大字符串上进行处理的代码部分将指示C ++和C#代码之间的边界需要在哪里。显然,任何对大字符串的引用都需要保留在C ++一侧,但是随后可以将汇总的处理结果信息传递回C#代码。

Here是指向代码项目页面的链接,该页面提供了有关C#到C ++ DLL导入的一些指导。

答案 1 :(得分:0)

因此,我最终创建了自己的BigStringBuilder函数。这是一个列表,其中每个列表元素(或页面)都是一个char数组(类型List<char[]>)。

假设您使用的是64位Windows,则现在可以轻松超过20亿个字符元素限制。我设法测试创建了一个大约32 GB的巨型字符串(需要首先在OS中增加虚拟内存,否则我在8GB RAM PC上只能得到7GB)。我确信它可以轻松处理超过32GB的数据。从理论上讲,它应该能够处理大约1,000,000,000 * 1,000,000,000个字符或一个五百万个字符,这对于任何人来说都足够。

速度方面,一些快速测试表明,追加时它仅比StringBuilder慢33%左右。如果我使用2D锯齿形字符数组(char[][])而不是List<char[]>,则可以获得非常相似的性能,但是列表更易于使用,因此我坚持使用它。

希望其他人发现它有用!可能存在错误,因此请谨慎使用。我对它进行了很好的测试。

// A simplified version specially for StackOverflow
public class BigStringBuilder
{
    List<char[]> c = new List<char[]>();
    private int pagedepth;
    private long pagesize;
    private long mpagesize;         // https://stackoverflow.com/questions/11040646/faster-modulus-in-c-c
    private int currentPage = 0;
    private int currentPosInPage = 0;

    public BigStringBuilder(int pagedepth = 12) {   // pagesize is 2^pagedepth (since must be a power of 2 for a fast indexer)
        this.pagedepth = pagedepth;
        pagesize = (long)Math.Pow(2, pagedepth);
        mpagesize = pagesize - 1;
        c.Add(new char[pagesize]);
    }

    // Indexer for this class, so you can use convenient square bracket indexing to address char elements within the array!!
    public char this[long n]    {
        get { return c[(int)(n >> pagedepth)][n & mpagesize]; }
        set { c[(int)(n >> pagedepth)][n & mpagesize] = value; }
    }

    public string[] returnPagesForTestingPurposes() {
        string[] s = new string[currentPage + 1];
        for (int i = 0; i < currentPage + 1; i++) s[i] = new string(c[i]);
        return s;
    }
    public void clear() {
        c = new List<char[]>();
        c.Add(new char[pagesize]);
        currentPage = 0;
        currentPosInPage = 0;
    }


    public void fileOpen(string path)
    {
        clear();
        StreamReader sw = new StreamReader(path);
        int len = 0;
        while ((len = sw.ReadBlock(c[currentPage], 0, (int)pagesize)) != 0) {
            if (!sw.EndOfStream)    {
                currentPage++;
                if (currentPage > (c.Count - 1)) c.Add(new char[pagesize]);
            }
            else    {
                currentPosInPage = len;
                break;
            }
        }
        sw.Close();
    }

    // See: https://stackoverflow.com/questions/373365/how-do-i-write-out-a-text-file-in-c-sharp-with-a-code-page-other-than-utf-8/373372
    public void fileSave(string path)   {
        StreamWriter sw = File.CreateText(path);
        for (int i = 0; i < currentPage; i++) sw.Write(new string(c[i]));
        sw.Write(new string(c[currentPage], 0, currentPosInPage));
        sw.Close();
    }

    public long length()    {
        return (long)currentPage * (long)pagesize + (long)currentPosInPage;
    }

    public string ToString(long max = 2000000000)   {
        if (length() < max) return substring(0, length());
        else return substring(0, max);
    }

    public string substring(long x, long y) {
        StringBuilder sb = new StringBuilder();
        for (long n = x; n < y; n++) sb.Append(c[(int)(n >> pagedepth)][n & mpagesize]);    //8s
        return sb.ToString();
    }

    public bool match(string find, long start = 0)  {
        //if (s.Length > length()) return false;
        for (int i = 0; i < find.Length; i++) if (i + start == find.Length || this[start + i] != find[i]) return false;
        return true;
    }
    public void replace(string s, long pos) {
        for (int i = 0; i < s.Length; i++)  {
            c[(int)(pos >> pagedepth)][pos & mpagesize] = s[i];
            pos++;
        }
    }

    public void Append(string s)
    {
        for (int i = 0; i < s.Length; i++)
        {
            c[currentPage][currentPosInPage] = s[i];
            currentPosInPage++;
            if (currentPosInPage == pagesize)
            {
                currentPosInPage = 0;
                currentPage++;
                if (currentPage == c.Count) c.Add(new char[pagesize]);
            }
        }
    }


}