代码之家  ›  专栏  ›  技术社区  ›  sɐunıɔןɐqɐp Zmey

在追加带空格的字符串时从文本文件中删除字符

  •  1
  • sɐunıɔןɐqɐp Zmey  · 技术社区  · 6 年前

    在c中,是否可以将包含退格的字符串附加到文本文件中,并将所有退格视为“删除最后一个字符”操作?

    例如,我的文本文件:

    这个文件有

    两行

    像这样的代码:

    string str = "...\b\b\b\b\b\b\b\b\b\b\b\b\b one line."
    myFile.Append(str);
    

    执行此代码后,文本文件如下所示:

    这个文件只有一行。

    这个 StreamWriter 以及 File 上课似乎没什么用。

    如果不在每个append操作上读写整个文件,我就很难找到一个好的实现方法,这可能会导致大型文本文件出现糟糕的性能问题这个想法是用这个新特性将日志语句密集地写入文本文件。

    我的第二个问题是如何处理windows风格的新行字符(“\r\n”)也就是说,一个退格应该删除一个完整的换行符序列(“\r\n”)。

    对如何实施这个有什么想法吗?

    源代码将非常感谢。

    2 回复  |  直到 6 年前
        1
  •  5
  •   xanatos    6 年前

    在“最一般的情况下”正确地做是 非常非常 , 非常 很难.NET中没有直接支持让我们看看最新的技术:

    • 有一个 FileStream 类…它是读/写的遗憾的是,它不知道编码,而且是以字节为单位工作的所以没有UTF-8和Unicode你看到你美丽的尼克了 sɐunıɔןɐqɐp ? 它显然需要一些编码:-)

    • StreamReader StreamWriter 可以“连接”到 文件流 …不幸的是,它们是分开的(一个是只读的,一个是写的),不幸的是它们预先缓冲,这样 FileStream.Position 与中当前的“读取”字符不对应 流读取器 是的。这使得阅读 流读取器 然后用 流线型作家 相当复杂。

    • 即使我们有一个 StreamReaderWriter ,会有点困难。.NET与UTF-16配合使用 char 所以许多Unicode字符(像emoji 🤪 grinning face 例如)由两个 烧焦 ... 所以一个人 \b 可能需要删除一两个 烧焦 (在UTF-8中介于1到4个字节之间),这取决于它找到了什么。

    • 注意,更复杂的表情符号(比如  👨‍👧‍👧  family )由多个单独的emojis(4个unicode代码点,对应于11.net)组成 烧焦 ,对应于UTF-8中的25个字节),但我们将忽略此问题

    最简单的解决方案是将整个文件加载到 string (或类似的),修改它,然后将其重新写入磁盘。即使在这里,也要当心行尾,那可能是两个字符( \r\n ,而“逻辑上”它们是一个字符(如果您在记事本中的一行开头并按一个退格键,它将完全删除 \ r\n )中。但正如您所注意到的,这个解决方案是“缓慢的”:-)

    其他解决方案,有很多限制。正如我在评论中所写的,你可以做相反的事情:保存 Position 在写作之前,如果你需要改正,写下 位置 回去,再写一遍, SetLength() 截断多余的文件(如果存在)。这将问题限制在只能修改在当前会话中编写的文本部分,并且通常只能修改文件的“最后”部分的情况下。

    public static long WriteAppend(this FileStream fs, string str, Encoding enc)
    {
        long pos = fs.Length;
        fs.Position = pos;
        byte[] bytes = enc.GetBytes(str);
        fs.Write(bytes, 0, bytes.Length);
        return pos;
    }
    
    
    public static long RewriteTruncate(this FileStream fs, long pos, string str, Encoding enc)
    {
        fs.Position = pos;
        byte[] bytes = enc.GetBytes(str);
        fs.Write(bytes, 0, bytes.Length);
        fs.SetLength(pos + bytes.Length);
        return pos;
    }
    

    使用:

    int secs = 5;
    
    using (var fs = new FileStream("Hello.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
    {
        fs.WriteAppend("Beginning of the elaboration\r\n", Encoding.UTF8);
    
        long pos1 = fs.WriteAppend("Step 1\r\n", Encoding.UTF8);
        long pos2 = fs.WriteAppend($"Working 0\r\n", Encoding.UTF8);
    
        for (int i = 1; i < 10; i++)
        {
            Thread.Sleep(secs * 1000);
            fs.RewriteTruncate(pos2, $"Working {i}\r\n", Encoding.UTF8);
        }
    
        Thread.Sleep(secs * 1000);
        fs.RewriteTruncate(pos1, $"Finished working\r\n", Encoding.UTF8);
    }
    

    在Notepad++中打开输出文件,每隔几秒钟刷新一次。

        2
  •  0
  •   sɐunıɔןɐqɐp Zmey    6 年前

    基于 xanatos' answer , and on TheGeneral's comment ,我写了这个原型 FileLogger 类,它将字符串(要追加)求值为初始退格字符序列加上剩余字符串(不带任何退格)。

    如果存在初始退格,程序将截断 FileStream 对象,然后附加剩余的字符串。

    不幸的是,这个解决方案没有考虑任何 \r\n 应该用一个退格键删除的换行符序列,从 文件流 从附加的字符串现在,删除一个windows样式的换行符序列需要两个退格。

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Text;
    
    namespace Example
    {
        public static class FileLogger
        {
            public static bool IsStarted { get; private set; }
            public static Encoding Encoding { get; private set; }
            public static string LogFilePath { get; private set; }
    
            private static FileStream FS;
            private static int BytesPerChar;
            private static readonly object Locker = new object();
    
            public static void Start(string logFilePath, Encoding encoding = null)
            {
                lock (Locker)
                {
                    if (IsStarted) return;
                    LogFilePath = logFilePath;
                    Encoding = encoding ?? Encoding.UTF8;
                    if (File.Exists(LogFilePath)) File.SetAttributes(LogFilePath, FileAttributes.Normal);
                    FS = new FileStream(LogFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, FileOptions.RandomAccess);
                    FS.SetLength(0);
                    FS.Flush();
                    BytesPerChar = Encoding.UTF8.GetByteCount(new[] { 'A' });
                    IsStarted = true;
                }
            }
    
            public static void Close()
            {
                lock (Locker)
                {
                    if (!IsStarted) return;
                    try { FS?.Close(); } catch { }
                    FS = null;
                    IsStarted = false;
                }
            }
    
            public static void WriteToFile(string text)
            {
                lock (Locker)
                {
                    if (string.IsNullOrEmpty(text)) return;
    
                    if (!text.Contains('\b'))
                    {
                        FS.Position = FS.Length;
                        byte[] bytes = Encoding.GetBytes(text);
                        FS.Write(bytes, 0, bytes.Length);
                        FS.Flush();
                        return;
                    }
    
                    // Evaluates the the string into initial backspaces and remaining text to be appended:
                    EvaluateText(text, out int initialBackspaces, out string remainingText);
    
                    // If there are no initial backspaces after evaluating the string, just append it and return:
                    if (initialBackspaces <= 0)
                    {
                        if (string.IsNullOrEmpty(remainingText)) return;
    
                        FS.Position = FS.Length;
                        byte[] bytes = Encoding.GetBytes(remainingText);
                        FS.Write(bytes, 0, bytes.Length);
                        FS.Flush();
                        return;
                    }
    
                    // First process the initial backspaces:
                    long pos = FS.Length - initialBackspaces * BytesPerChar;
                    FS.Position = pos > 0 ? pos : 0;
                    FS.SetLength(FS.Position);
    
                    // Then write any remaining evaluated text:
                    if (!string.IsNullOrEmpty(remainingText))
                    {
                        byte[] bytes = Encoding.GetBytes(remainingText);
                        FS.Write(bytes, 0, bytes.Length);
                    }
                    FS.Flush();
                    return;
                }
            }
    
            public static void EvaluateText(string text, out int initialBackspaces, out string remainingTextToAppend)
            {
                initialBackspaces = 0;
                StringBuilder sb = new StringBuilder();
                foreach (char ch in text)
                {
                    if(ch == '\b')
                    {
                        if (sb.Length > 0) sb.Length--;
                        else initialBackspaces++;
                    }
                    else sb.Append(ch);
                }
                remainingTextToAppend = sb.ToString();
            }
        }
    }
    

    测试代码:

    FileLogger.Start("test.log");
    FileLogger.WriteToFile("aaa\r\n");
    FileLogger.WriteToFile("bbbb");
    FileLogger.WriteToFile("\b");
    FileLogger.WriteToFile("\b\b");
    FileLogger.WriteToFile("\b\b\b\b");
    FileLogger.WriteToFile("XXX");
    FileLogger.WriteToFile("\b\bYY\bZ");
    FileLogger.WriteToFile("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
    FileLogger.WriteToFile("Done!");
    FileLogger.Close();
    

    输出(test.log文件):

    aaXYZ公司