【问题标题】:Writing to Filestream and copying to MemoryStream写入 Filestream 并复制到 MemoryStream
【发布时间】:2015-12-01 15:04:55
【问题描述】:

我想在磁盘上覆盖或创建一个 xml 文件,并从函数中返回 xml。我想我可以通过从 FileStream 复制到 MemoryStream 来做到这一点。但我最终将一个新的 xml 文档附加到同一个文件中,而不是每次都创建一个新文件。 我究竟做错了什么?如果我删除复制,一切正常。

 public static string CreateAndSave(IEnumerable<OrderPage> orderPages, string filePath)
    {
        if (orderPages == null || !orderPages.Any())
        {
            return string.Empty;
        }

        var xmlBuilder = new StringBuilder();

        var writerSettings = new XmlWriterSettings
        {
            Indent = true,
            Encoding = Encoding.GetEncoding("ISO-8859-1"),
            CheckCharacters = false,
            ConformanceLevel = ConformanceLevel.Document
        };

        using (var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
        {
            try
            {
                XmlWriter xmlWriter = XmlWriter.Create(fs, writerSettings);
                xmlWriter.WriteStartElement("PRINT_JOB");
                WriteXmlAttribute(xmlWriter, "TYPE", "Order Confirmations");

                foreach (var page in orderPages)
                {
                    xmlWriter.WriteStartElement("PAGE");
                    WriteXmlAttribute(xmlWriter, "FORM_TYPE", page.OrderType);

                    var outBound = page.Orders.SingleOrDefault(x => x.FlightInfo.Direction == FlightDirection.Outbound);
                    var homeBound = page.Orders.SingleOrDefault(x => x.FlightInfo.Direction == FlightDirection.Homebound);

                    WriteXmlOrder(xmlWriter, outBound, page.ContailDetails, page.UserId, page.PrintType, FlightDirection.Outbound);
                    WriteXmlOrder(xmlWriter, homeBound, page.ContailDetails, page.UserId, page.PrintType, FlightDirection.Homebound);

                    xmlWriter.WriteEndElement();
                }

                xmlWriter.WriteFullEndElement();

                MemoryStream destination = new MemoryStream();
                fs.CopyTo(destination);

                Log.Progress("Xml string length: {0}", destination.Length);

                xmlBuilder.Append(Encoding.UTF8.GetString(destination.ToArray()));

                destination.Flush();
                destination.Close();
                xmlWriter.Flush();
                xmlWriter.Close();
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Unhandled exception occured during create of xml. {0}", ex.Message);
                throw;
            }

            fs.Flush();
            fs.Close();
        }

        return xmlBuilder.ToString();
    }

干杯 延斯

【问题讨论】:

  • FileMode.OpenOrCreate 应该是FileMode.Create
  • 使用 Linq to XML 会让这件事变得容易得多——而且你也没有展示你实际上是如何决定文件名的
  • 将其更改为 FileMode.Create 并且可以正常工作,但现在我没有得到任何 xml。返回字符串为空。
  • 文件名是常量。

标签: c# .net xml


【解决方案1】:

FileMode.OpenOrCreate 导致文件内容被覆盖而不缩短,留下之前运行的任何“尾随”数据。如果使用FileMode.Create,文件将首先被截断。但是,要读回您刚刚编写的内容,您需要使用Seek 来重置文件指针。

另外,在从底层流复制之前刷新XmlWriter

另见问题Simultaneous Read Write a file in C Sharp (3817477)

以下测试程序似乎可以满足您的需求(减去您自己的日志记录和订单详细信息)。

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Threading.Tasks;

namespace ReadWriteTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string filePath = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.Personal),
                "Test.xml");

            string result = CreateAndSave(new string[] { "Hello", "World", "!" }, filePath);

            Console.WriteLine("============== FIRST PASS ==============");
            Console.WriteLine(result);

            result = CreateAndSave(new string[] { "Hello", "World", "AGAIN", "!" }, filePath);
            Console.WriteLine("============== SECOND PASS ==============");
            Console.WriteLine(result);

            Console.ReadLine();
        }

        public static string CreateAndSave(IEnumerable<string> orderPages, string filePath)
        {
            if (orderPages == null || !orderPages.Any())
            {
                return string.Empty;
            }

            var xmlBuilder = new StringBuilder();

            var writerSettings = new XmlWriterSettings
            {
                Indent = true,
                Encoding = Encoding.GetEncoding("ISO-8859-1"),
                CheckCharacters = false,
                ConformanceLevel = ConformanceLevel.Document
            };

            using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
            {
                try
                {
                    XmlWriter xmlWriter = XmlWriter.Create(fs, writerSettings);
                    xmlWriter.WriteStartElement("PRINT_JOB");

                    foreach (var page in orderPages)
                    {
                        xmlWriter.WriteElementString("PAGE", page);
                    }

                    xmlWriter.WriteFullEndElement();

                    xmlWriter.Flush();  // Flush from xmlWriter to fs
                    xmlWriter.Close();

                    fs.Seek(0, SeekOrigin.Begin); // Go back to read from the begining

                    MemoryStream destination = new MemoryStream();
                    fs.CopyTo(destination);

                    xmlBuilder.Append(Encoding.UTF8.GetString(destination.ToArray()));

                    destination.Flush();
                    destination.Close();
                }
                catch (Exception ex)
                {
                    throw;
                }

                fs.Flush();
                fs.Close();
            }

            return xmlBuilder.ToString();
        }
    }
}

对于那里的优化器,StringBuilder 是不必要的,因为字符串是完整的,只需将 fs 包装在StreamReader 中就可以避免MemoryStream。这将使代码如下。

        public static string CreateAndSave(IEnumerable<string> orderPages, string filePath)
        {
            if (orderPages == null || !orderPages.Any())
            {
                return string.Empty;
            }

            string result;

            var writerSettings = new XmlWriterSettings
            {
                Indent = true,
                Encoding = Encoding.GetEncoding("ISO-8859-1"),
                CheckCharacters = false,
                ConformanceLevel = ConformanceLevel.Document
            };

            using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
            {
                try
                {
                    XmlWriter xmlWriter = XmlWriter.Create(fs, writerSettings);
                    xmlWriter.WriteStartElement("PRINT_JOB");

                    foreach (var page in orderPages)
                    {
                        xmlWriter.WriteElementString("PAGE", page);
                    }

                    xmlWriter.WriteFullEndElement();

                    xmlWriter.Close();  // Flush from xmlWriter to fs

                    fs.Seek(0, SeekOrigin.Begin); // Go back to read from the begining

                    var reader = new StreamReader(fs, writerSettings.Encoding);
                    result = reader.ReadToEnd();
                    // reader.Close();  // This would just flush/close fs early(which would be OK)
                }
                catch (Exception ex)
                {
                    throw;
                }
            }

            return result;
        }

【讨论】:

    【解决方案2】:

    我知道我迟到了,但似乎有一个更简单的解决方案。您希望您的函数生成 xml,将其写入文件并返回生成的 xml。显然分配 string 是不可避免的(因为你希望它被返回),写入文件也是如此。但是通过简单地“交换”操作可以轻松避免从文件中读取(如在您和 SensorSmith 的解决方案中) - 生成 xml 字符串并将其写入文件。像这样:

    var output = new StringBuilder();
    var writerSettings = new XmlWriterSettings { /* your settings ... */ };
    using (var xmlWriter = XmlWriter.Create(output, writerSettings))
    {
        // Your xml generation code using the writer
        // ...
        // You don't need to flush the writer, it will be done automatically
    }
    // Here the output variable contains the xml, let's take it...
    var xml = output.ToString();
    // write it to a file...
    File.WriteAllText(filePath, xml);
    // and we are done :-)
    return xml;
    

    重要更新: 原来XmlWriter.Create(StringBuider, XmlWriterSettings) 重载忽略了设置中的Encoding 并且总是使用"utf-16",所以如果您需要其他编码,请勿使用此方法。

    【讨论】:

    • 非常漂亮和干净的代码。我会试一试并比较性能。谢谢。
    • 好的,我运行了一个比较检查,将 10000 个订单转储到 xml。使用 SensorSmiths 代码花费了 1198 毫秒。使用 File.WriteAllText 花了 1108 毫秒。所以没什么大不了的。即使在指定 ISO-8859-1 时,xml 也以某种方式以 UTF-16 编码。无论如何感谢您的意见。
    • 关于性能,现在所有这些缓存都不足为奇:-) 但是编码问题真的很烦人,感谢报告,我会更新答案以防其他人阅读它。保重。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-16
    • 1970-01-01
    • 1970-01-01
    • 2016-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多