将大文本分成较小块的最快方法

时间:2015-12-23 07:02:30

标签: c# .net substring

我使用下面的代码来分割字符串,但这需要花费很多时间。

using (StreamReader srSegmentData = new StreamReader(fileNamePath))
{
    string strSegmentData = "";
    string line = srSegmentData.ReadToEnd();
    int startPos = 0;

    ArrayList alSegments = new ArrayList();
    while (startPos < line.Length && (line.Length - startPos) >= segmentSize)
    {
        strSegmentData = strSegmentData + line.Substring(startPos, segmentSize) + Environment.NewLine;
        alSegments.Add(line.Substring(startPos, segmentSize) + Environment.NewLine);
        startPos = startPos + segmentSize;
    }
}

请建议我另一种方法将字符串拆分为固定大小的小块

2 个答案:

答案 0 :(得分:12)

首先,您应该定义块大小的含义。如果您指的是具有固定数量的代码单元块,那么您的实际算法可能会很慢,但它可以正常工作。如果它不是您想要的,而您实际上是指具有固定数量的字符 块,那么它就会被破坏。我在此代码审核帖子中讨论了类似的问题:Split a string into chunks of the same length然后我将在此仅重复相关部分。

  • 您正在对Char进行分区,但String是UTF-16编码的,那么您可以在至少三种情况下生成已损坏的字符串:

    1. 一个字符使用多个代码单元进行编码。该字符的Unicode代码点被编码为两个UTF-16代码单元,每个代码单元最终可能位于两个不同的切片中(两个字符串将无效)。
    2. 一个字符由多个代码点组成。您正在处理由两个单独的Unicode代码点(例如汉字符)创建的字符。
    3. 一个角色组合了字符或修饰符。这比您想象的更常见:例如Unicode组合字符,如 U + 0300 COMBINING GRAVE ACCENT 用于构建 à和Unicode 修饰符,例如 U + 02BC MODIFIER LETTER APOSTROPHE
  • 编程语言和人类的字符的定义非常不同,例如在斯洛伐克是单个字符,但它是由2/3 Unicode制作的代码点在这种情况下也是2/3 UTF-16代码单元,然后是"dž".Length > 1。更多关于How can I perform a Unicode aware character by character comparison?上的这个和其他文化问题
  • 存在连字。假设一个连字是一个代码点(并且还假设它被编码为一个代码单元),那么您将把它视为单个字形,但它代表两个字符。在这种情况下该怎么办?一般来说,字符的定义可能非常模糊,因为根据使用该单词的学科,它具有不同的含义。你不能(可能)正确处理所有事情,但你应该设置一些约束和文档代码行为。

一个提议的(和未经测试的)实现可能是这样的:

public static IEnumerable<string> Split(this string value, int desiredLength)
{
    var characters = StringInfo.GetTextElementEnumerator(value);
    while (characters.MoveNext())
        yield return String.Concat(Take(characters, desiredLength));
}

private static IEnumerable<string> Take(TextElementEnumerator enumerator, int count)
{
    for (int i = 0; i < count; ++i)
    {
        yield return (string)enumerator.Current;

        if (!enumerator.MoveNext())
            yield break;
    }
}

它没有针对速度进行优化(正如您所看到的那样,我尝试使用枚举来保持代码简短和清晰)但是,对于大文件,它仍然比您的实现表现更好(请参阅下一段的原因)。

关于您的代码请注意:

  • 你正在构建一个巨大的ArrayList(?!)来保存结果。另请注意,通过这种方式,您可以多次调整ArrayList的大小(即使给定输入大小和块大小,也可以知道其最终大小)。
  • strSegmentData重建多次,如果你需要累积必须使用的字符StringBuilder,否则每个操作都会分配一个新的字符串并复制旧的值(它很慢而且它也给垃圾收集器增加了压力) )。

有更快的实施(请参阅链接的代码审核帖子,尤其是Heslacher's implementation以获得更快的版本),如果您不需要正确处理Unicode(您确实确定)只有US ASCII字符)然后还有一个漂亮的readable implementation from Jon Skeet(请注意,在分析代码之后,您仍然可以提高其预分配正确大小输出列表的大文件的性能)。我不在这里重复他们的代码,请参阅链接的帖子。

在您特定的中,您不需要读取内存中的整个大文件,您可以及时读取/解析 n 个字符(不要过于担心磁盘访问,I / O被缓冲)。它会略微降低性能,但会大大提高内存使用率。或者,您可以逐行阅读(管理处理跨行块)。

答案 1 :(得分:0)

以下是我对您的问题和代码的分析(阅读评论)

using (StreamReader srSegmentData = new StreamReader(fileNamePath))
{
    string strSegmentData = "";
    string line = srSegmentData.ReadToEnd(); // Why are you reading this till the end if it is such a long string?
    int startPos = 0;

    ArrayList alSegments = new ArrayList(); // Better choice would be to use List<string>
    while (startPos < line.Length && (line.Length - startPos) >= segmentSize)
    {
        strSegmentData = strSegmentData + line.Substring(startPos, segmentSize) + Environment.NewLine; // Seem like you are inserting linebreaks at specified interval in your original string. Is that what you want?
        alSegments.Add(line.Substring(startPos, segmentSize) + Environment.NewLine); // Why are you recalculating the Substring? Why are you appending the newline if the aim is to just "split"
        startPos = startPos + segmentSize;
    }
}

做出各种假设,下面是我建议用于拆分长字符串的代码。这只是一种干净的方式来做你在样本中做的事情。您可以对此进行优化,但不确定您的查找速度有多快。

static void Main(string[] args) {
    string fileNamePath = "ConsoleApplication1.pdb";
    var segmentSize = 32;

    var op = ReadSplit(fileNamePath, segmentSize);
    var joinedSTring = string.Join(Environment.NewLine, op);
}

static List<string> ReadSplit(string filePath, int segmentSize) {
    var splitOutput = new List<string>();
    using (var file = new StreamReader(filePath, Encoding.UTF8, true, 8 * 1024 )) {
        char []buffer = new char[segmentSize];
        while (!file.EndOfStream) {
            int n = file.ReadBlock(buffer, 0, segmentSize);
            splitOutput.Add(new string(buffer, 0, n));
        }
    }

    return splitOutput;
}

我没有对我的版本进行任何性能测试,但我的猜测是它比你的版本更快。

此外,我不确定您打算如何使用输出,但在执行I / O时进行的优化是使用异步调用。处理大型string时的良好优化(以可读性和复杂性为代价)是坚持char[]

请注意

  • 您可能必须在阅读文件时处理字符编码问题
  • 如果您已经在内存中包含长字符串并且文件读取仅包含在演示中,那么您应该使用StringReader类而不是StreamReader