【问题标题】:Version of C# StringBuilder to allow for strings larger than 2 billion characters允许超过 20 亿个字符的字符串的 C# StringBuilder 版本
【发布时间】:2019-06-07 18:51:39
【问题描述】:

在 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 的用户的需求。

【问题讨论】:

  • 其实StringBuilder并不是一个单一的对象;这是一个较小的字符串生成器链,所以理论上,你所说的限制应该不是问题。
  • @InBetween:经过测试,我发现 StringBuilder 的限制在 2147483648-3500 (2^31 - 3500) 个字符左右,然后才生成 OutOfMemoryException
  • 是的,再想一想,这是有道理的。每次字符串构建器调整大小时,它都会向链中添加一个新的构建器,其容量是当前链总容量的两倍,所以是的,实际上你会遇到同一堵墙,因为你最终会达到数组限制。您可以进行一些调查,看看是否有办法设置默认扩展率来规避这种情况,但我有点怀疑是否有一种开箱即用的方法。
  • StringBuilder 是一段代码(使用内部 .net 的东西等),是一个通用的东西。您的要求:2B+ 字符串 + 托管代码 + 性能可以看作是矛盾的。我猜你需要的实现在某种程度上完全取决于这些要求(我们并不完全知道)。我的意思是,最佳实施真的取决于你将如何处理这么大的东西。
  • 为什么要在内存中存储这么大的字符串?将其存储在文件中可能更实用,即使它更慢。或者,您可以使用多个字符串构建器或 char 数组,每个都在大小限制之下,中间有一个类来处理根据位置进出哪个字符串构建器的数据操作。

标签: c# 64-bit pinvoke stringbuilder unsafe


【解决方案1】:

根据this answer,C++ 中字符串的大小是无限的。

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

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

Here 是一个代码项目页面的链接,它提供了一些关于从 C# 到 C++ DLL 导入的指导。

【讨论】:

  • “不安全”代码在不需要 DLL 的情况下不会更简单地完成同样的事情吗?
【解决方案2】:

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

如果您使用的是 64 位 Windows,您现在可以轻松超过 20 亿个字符元素的限制。我设法创建了一个大约 32 GB 的巨型字符串(需要首先增加操作系统中的虚拟内存,否则我只能在我的 8 GB RAM PC 上获得大约 7 GB)。我确信它可以轻松处理超过 32GB 的容量。理论上,它应该能够处理大约 1,000,000,000 * 1,000,000,000 个字符或一个 quintillion 字符,这对任何人来说都应该足够了。

在速度方面,一些快速测试表明它在追加时仅比 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]);
            }
        }
    }


}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 2014-10-02
    • 1970-01-01
    • 2011-02-08
    • 2011-03-20
    • 2010-12-09
    相关资源
    最近更新 更多