C#中获取字符串最后n行

11

我有一个长度未知的字符串

它的格式为

\nline
\nline
\nline

不知道字符串有多长,我该如何获取最后10行的内容?每行之间用"\n"分隔。

6个回答

14
随着字符串变得越来越大,避免处理无关字符变得更加重要。使用string.Split的任何方法都是低效的,因为整个字符串都必须被处理。一个高效的解决方案将不得不从字符串的末尾运行。这里提供了一个正则表达式的方法。
请注意它返回一个List<string>,因为结果需要在返回之前被反转(因此使用了Insert方法)。
private static List<string> TakeLastLines(string text, int count)
{
    List<string> lines = new List<string>();
    Match match = Regex.Match(text, "^.*$", RegexOptions.Multiline | RegexOptions.RightToLeft);

    while (match.Success && lines.Count < count)
    {
        lines.Insert(0, match.Value);
        match = match.NextMatch();
    }

    return lines;
}

我不能点赞,但在尝试了所有解决方案后,这是最佳选择,速度非常快,谢谢Simon,你是一个很棒的程序员。 - user1588670
@SimonMcKenzie 不错的解决方案。正则表达式是C#中非常强大但经常被忽视的功能。 - MikeKulls

9
var result = text.Split('\n').Reverse().Take(10).ToArray();

3
+1 虽然这会颠倒行的顺序,可能会被忽略。你可以在结尾处添加另一个 ReverseToArray() 是多余的,因为 OP 没有提到他需要一个数组。 - Tim Schmelter
1
@Mike,您是完全正确的,显然我的答案不会提供最佳性能。 但是,我不能同意您的陈述的普遍性:复制可能是一个问题,也可能不是一个问题-这取决于字符串的长度以及执行此操作的频率。有时维护较少的代码行比过早进行优化更重要。问题以最简单的形式陈述,没有任何上下文或任何需要考虑的性能要求。因此,做好工作的最简单的解决方案是有效的,甚至可能是最好的方案。 - Volma
@julealgon:我猜你是指我的第二条评论,根据codesparkles(已删除!)的评论?我认为他提到了另一种使用Enumerable.Skip的方法,后来他删除了他的评论。 - Tim Schmelter
@TimSchmelter 没错。归根结底,这句话的意思是,“因为Skip枚举了整个(巨大的)数组...”,我认为如果你真的在谈论Enumerable.Skip,那么这句话是错误的。 - julealgon
@julealgon: 为什么?与 Enumerable.Reverse 不同,Enumerable.Skip 没有针对 ICollection<T> 使用 for-loop 进行优化。它会枚举序列。 - Tim Schmelter
显示剩余13条评论

6

Split()函数可以通过\n将字符串分割成数组,然后取该数组的最后10个元素。


嘭!比我写的好多了。 - Almo
1
只要字符串不是很大,这个操作就会非常快速和简单。 - Tony Hopkinson
如何从数组中取出最后10个元素(不使用for循环),顺便说一下,该字符串非常大。 - user1588670
1
@user1588670:仅循环最后10个元素的for循环有什么问题吗? for(int i=arr.Length-10;i<arr.Length;i++)String line=arr[i]; - Tim Schmelter

3
如果这是在一个特别大的文件中,您可能希望以高效的方式完成此操作。一种方法是倒序读取文件,然后只取前10行。您可以看到使用Jon Skeet的MiscUtil库来实现这个功能的示例here
var lines = new ReverseLineReader(filename);
var last = lines.Take(10);

OP没有提到字符串是来自文件的。 - Tim Schmelter

0
这里有一种方法可以实现,它的优点是不会创建整个源字符串的副本,因此相当高效。大部分代码将与其他通用扩展方法放置在一个类中,因此最终结果是您只需要1行代码即可完成。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string x = "a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni\r\nj\r\nk\r\nl\r\nm\r\nn\r\no\r\np";
            foreach(var line in x.SplitAsEnumerable("\r\n").TakeLast(10))
                Console.WriteLine(line);
            Console.ReadKey();
        }
    }

    static class LinqExtensions
    {
        public static IEnumerable<string> SplitAsEnumerable(this string source)
        {
            return SplitAsEnumerable(source, ",");
        }

        public static IEnumerable<string> SplitAsEnumerable(this string source, string seperator)
        {
            return SplitAsEnumerable(source, seperator, false);
        }

        public static IEnumerable<string> SplitAsEnumerable(this string source, string seperator, bool returnSeperator)
        {
            if (!string.IsNullOrEmpty(source))
            {
                int pos = 0;
                do
                {
                    int newPos = source.IndexOf(seperator, pos, StringComparison.InvariantCultureIgnoreCase);
                    if (newPos == -1)
                    {
                        yield return source.Substring(pos);
                        break;
                    }
                    yield return source.Substring(pos, newPos - pos);
                    if (returnSeperator) yield return source.Substring(newPos, seperator.Length);
                    pos = newPos + seperator.Length;
                } while (true);
            }
        }

        public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
        {
            List<T> items = new List<T>();
            foreach (var item in source)
            {
                items.Add(item);
                if (items.Count > count) items.RemoveAt(0);
            }
            return items;
        }
    }
}

编辑:有人指出这种方法可能不够高效,因为它需要迭代整个字符串。我也认为使用列表的RemoveAt(0)可能也不够高效。为了解决这个问题,代码可以被修改为向后搜索字符串。这将消除TakeLast函数的需求,因为我们可以直接使用Take。


0

高效利用空间的方法

    private static void PrintLastNLines(string str, int n)
    {
        int idx = str.Length - 1;
        int newLineCount = 0;

        while (newLineCount < n)
        {
            if (str[idx] == 'n' && str[idx - 1] == '\\')
            {
                newLineCount++;
                idx--;
            }

            idx--;
        }

        PrintFromIndex(str, idx + 3);
    }

    private static void PrintFromIndex(string str, int idx)
    {
        for (int i = idx; i < str.Length; i++)
        {
            if (i < str.Length - 1 && str[i] == '\\' && str[i + 1] == 'n')
            {
                Console.WriteLine();
                i++;
            }
            else
            {
                Console.Write(str[i]);
            }
        }

        Console.WriteLine();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接