如何将文件读入字符串并保留CR/LF格式?

10
如果我问“如何将文件读入字符串”,答案是显而易见的。然而 - 这里有一个问题,需要保留回车符和换行符。
问题在于,File.ReadAllText会去掉这些字符。StreamReader.ReadToEnd仅将LF转换为CR,这导致了长时间调查,我在相当明显的代码中发现了错误;-)
因此,简而言之,如果我有一个包含foo\n\r\nbar的文件,我希望得到foo\n\r\nbar(即完全相同的内容),而不是foo bar、foobar或foo\n\n\nbar。在.Net空间中是否有一些现成的方法?
结果应始终为单个字符串,包含整个文件。

5
为什么不直接将其视为“byte[]”?例如File.ReadAllBytes等。 - Marc Gravell
不确定,但是\n\r\n代表换行符-回车符-换行符。所以当它读取时,很有可能会优先选择回车符-换行符组合,以便删除开头的悬挂换行符。但我对.NET的StreamReader内部工作机制并不是很了解 :( - Simon Whitehead
@MarcGravell,类似于new string(System.IO.File.ReadAllBytes(filename).Select(b => (char)b).ToArray())这样的东西吗?不过还是有地方会转换\n,我需要进一步调查。 - greenoldman
非标准要求使用标准程序,不能正常工作,这并不奇怪,是吧?你说了文本,它就按照你说的做了。 - Tony Hopkinson
6个回答

12

你确定那些方法是导致你的字符被剥离的罪魁祸首吗?

我尝试写了一个快速测试;StreamReader.ReadToEnd 会保留所有换行符。

string str = "foo\n\r\nbar";
using (Stream ms = new MemoryStream(Encoding.ASCII.GetBytes(str)))
using (StreamReader sr = new StreamReader(ms, Encoding.UTF8))
{
    string str2 = sr.ReadToEnd();
    Console.WriteLine(string.Join(",", str2.Select(c => ((int)c))));
}

// Output: 102,111,111,10,13,10,98,97,114
//           f   o   o \n \r \n  b  a   r

当向临时文件写入并从中读取时,可以实现相同的结果:

string str = "foo\n\r\nbar";
string temp = Path.GetTempFileName();
File.WriteAllText(temp, str);
string str2 = File.ReadAllText(temp);
Console.WriteLine(string.Join(",", str2.Select(c => ((int)c))));

看起来你的换行符在其他地方被丢失了。


1
天啊,你说得对,还有感谢提供样例代码来更彻底地测试它,我在读取文本后的代码上弄错了,导致显示结果让我感到困惑。非常抱歉,但是还有很多东西需要学习。 - greenoldman
很高兴你找到了原因 :-) - Douglas

5

这段代码将保留LR和CR。

string r = File.ReadAllText(@".\TestData\TR120119.TRX", Encoding.ASCII);

2
结果应始终为单个字符串,包含整个文件。
这需要两个步骤。第一步是使用File.ReadAllBytes()获取文件中的所有字节。它不会尝试翻译任何内容,您可以获得文件中的原始数据,因此奇怪的行尾保留为原样。
但是那是字节,您要求一个字符串。因此,第二步是应用Encoding.GetString()将字节转换为字符串。您唯一需要做的事情是选择正确的Encoding类,与编写文件的程序使用的编码相匹配。考虑到文件如果包含\n\r\n序列则相当混乱,并且您没有关于文件的其他文档,最好使用Encoding.Default。根据需要进行调整。

我不相信编码的选择应该导致换行符序列被改变。 - Douglas
如果你还没有遇到EBCDIC,那么这是一个很高的假设。但这不是重点,重点是ASCII控制字符之间的内容是什么。 - Hans Passant
这正是重点所在。如果OP使用任何ASCII兼容编码(包括UTF-8),那么控制字符之间的内容就无关紧要;多字节序列不能包含值10或13。是的,使用非ASCII兼容编码,如EBCDIC(甚至UTF-16),会引入一系列新的考虑因素,但我假设如果他们使用了这样的编码方式,OP肯定会提到的。 - Douglas
虽然这个问题是我代码中的错误,但我喜欢你的解释和步骤描述 -- 非常感谢! - greenoldman

1
您可以使用 File.ReadAllLines 读取文件内容,它将返回一个行数组。然后使用 String.Join 将这些行使用分隔符合并在一起。
string[] lines = File.ReadAllLines(@"C:\Users\User\file.txt");
string allLines = String.Join("\r\n", lines);

请注意,这会丢失实际行终止符字符的精度。例如,如果行仅以\n\r结尾,则生成的字符串allLines将用\r\n行结束符替换它们。
当然,也有其他方法可以实现这一点而不会丢失真正的EOL终止符,但是ReadAllLines很方便,因为它可以自己检测许多类型的文本编码,并且还占用非常少的代码行。

0

这与被接受的答案类似,但想要更直接。 sr.ReadToEnd()将读取所需的字节:

string myFilePath = @"C:\temp\somefile.txt";
string myEvents = String.Empty;

FileStream fs = new FileStream(myFilePath, FileMode.Open);
StreamReader sr = new StreamReader(fs);
myEvents = sr.ReadToEnd();
sr.Close();
fs.Close();

你甚至可以使用级联的using语句来完成这些操作。但是我想描述一下,你首先写入文件的方式将决定如何从myEvents字符串中读取内容,并且可能真正存在问题的地方就在于此。我是这样写入文件的:

using System.Reflection;
using System.IO;

private static void RecordEvents(string someEvent)
{
    string folderLoc = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!folderLoc.EndsWith(@"\")) folderLoc += @"\";
    folderLoc = folderLoc.Replace(@"\\", @"\"); // replace double-slashes with single slashes
    string myFilePath = folderLoc + "myEventFile.txt";

    if (!File.Exists(myFilePath))
        File.Create(myFilePath).Close(); // must .Close() since will conflict with opening FileStream, below

    FileStream fs = new FileStream(myFilePath, FileMode.Append);
    StreamWriter sr = new StreamWriter(fs);
    sr.Write(someEvent + Environment.NewLine);
    sr.Close();
    fs.Close();
}

然后我可以使用上面更远的代码来获取内容的字符串。因为我正在进一步查找单个字符串,所以我将此代码放在那个代码之后,就像这样:

if (myEvents != String.Empty) // we have something
{
    // (char)2660 is ♠  -- I could have chosen any delimiter I did not
    // expect to find in my text
    myEvents = myEvents.Replace(Environment.NewLine, ((char)2660).ToString());
    string[] eventArray = myEvents.Split((char)2660);
    foreach (string s in eventArray)
    {
        if (!String.IsNullOrEmpty(s))
            // do whatever with the individual strings from your file
    }
}

这个很好用。所以我知道myEvents必须保留Environment.NewLine字符,因为我能够用(char)2660替换它,并使用该字符对字符串进行.Split()操作,将其分成单独的段落。


0

ReadAllText不会返回回车符。

该方法打开一个文件,读取文件的每一行,然后将每一行作为字符串的一个元素添加。然后关闭文件。一行被定义为由字符序列组成,后面跟着回车符('\r')、换行符('\n')或紧接着回车符后面跟着换行符。 生成的字符串不包含终止的回车符和/或换行符

来自MSDN - https://msdn.microsoft.com/en-us/library/ms143368(v=vs.110).aspx


不幸的是,这并不是真的,因为所有其他的示例、我的测试和 .Net 参考代码都表明这一点。 - djk

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