【问题标题】:Exception when reading million characters in JSON file [OutOfMemoryException]读取 JSON 文件中的百万字符时出现异常 [OutOfMemoryException]
【发布时间】:2017-01-20 09:12:23
【问题描述】:

我已经下载了一个从 Azure Blob 存储中记录的 JSON 文件。文件大小为 137MB。

使用 Notepad++ 打开时的字符和行属性如下图所示:

当我从文件上下文菜单中选择“使用 Notepad++ 编辑”时,大约需要 1-2 秒。因此,我决定创建一个程序来将 JSON 转换器转换为 CSV 文件格式。但似乎,我遇到了某种例外。目前,为了查看 JSON 内容,我将显示在 RichTextBox 中,因为它可以在我决定转换为 CSV 之前查看内容。

开始加载的事件:-

private async void txtjsonname_DoubleClick(object sender, EventArgs e)
{
    OpenFileDialog ofd = new OpenFileDialog();
    ofd.Filter = "JSON Files (*.json)|*.json";
    ofd.InitialDirectory = @"C:\";
    ofd.Title = "Select single json file to be converted";
    ofd.Multiselect = false;
    if (ofd.ShowDialog() == DialogResult.OK)
    {
        rtbstat.Text = null;
        txtcsvname.Text = null;
        txtjsonname.Text = null;
        lblcsvpath.Text = null;
        lbljsonpath.Text = null;
        rtbjson.Clear();
        txtjsonname.Text = Path.GetFileName(ofd.FileName);
        lbljsonpath.Text = Path.GetDirectoryName(ofd.FileName);

        if (await LoadJSONtoRTB(ofd.FileName))
        {
            rtbjson.WordWrap = false;
            rtbstat.Text = "Load file finished! " + (rtbjson.Lines.Count()).ToString() + " line(s) detected | " + rtbjson.Text.Length.ToString() + " character(s) detected";
            txtcsvname.Text = Path.GetFileNameWithoutExtension(ofd.FileName) + ".csv";
        }
    }
    await Task.Delay(1000);
}

我尝试遇到异常的代码:-

第一种方法: 第一个代码:

private async Task<bool> LoadJSONtoRTB(string path)
    {
        try
        {
            foreach (var line in File.ReadLines(path))
            {
                rtbjson.Text = line;
            }
            await Task.Delay(10);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

第二个代码:

private async Task<bool> LoadJSONtoRTB(string path)
    {
        try
        {
            using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            using (BufferedStream bs = new BufferedStream(fs))
            using (StreamReader sr = new StreamReader(bs))
            {
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    rtbjson.AppendText(line);
                }
            }
            await Task.Delay(10);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

异常:System.Windows.Forms.dll 中出现“System.AccessViolationException”类型的未处理异常

附加信息:试图读取或写入受保护的内存。这通常表明其他内存已损坏。

第二种方法:-

private async Task<bool> LoadJSONtoRTB(string path)
{
    try
    {
        StreamReader sr = new StreamReader(@path);
        while (!sr.EndOfStream)
            rtbjson.Text += sr.ReadLine();
        await Task.Delay(10);
        return true;
    }
    catch (Exception)
    {
        return false;
    }
}

使用上面的代码,当我放置断点以查看进度时,它运行了大约 12 分钟。

12 分钟,阅读长度为 600 万。

有没有什么方法可以显示文本文件(json/txt),例如notepad++只需要1-2秒就可以查看文件?

【问题讨论】:

  • 这样的操作:rtbjson.Text += sr.ReadLine(); 对大量文本的字符串非常慢,因为在每一步它都会为该操作的结果创建新变量。而是尝试使用 StringBuilder 和 AppendText 方法,让我知道它是如何工作的
  • 你能发布完整的异常堆栈跟踪吗?
  • 第一种方法可能比第二种方法好。如果在添加行时禁用图形更新怎么办?见How do you prevent a RichTextBox from refreshing its display?
  • 另外,RichTextBox.LoadFile() 可能会有所帮助。见C# - Read in a large (150MB) text file into a Rich Text Box
  • 标准 Windows 控件专为正常数量的文本而设计。支持大量文本的编辑器专为此目的而设计。

标签: c# json csv azure


【解决方案1】:

我怀疑 Notepad++ 在内存中加载了与System.IO.File.ReadAllText 等效的整个文件。此外,将文件的每一行附加到字符串没有任何好处,最终结果是占用相同的内存。 使用 RichTextBox,您能做的最好的事情是:

richTextBox1.Text = System.IO.File.ReadAllText(filePath);

无论如何,Notepad++ 使用的Scintilla 比 RichTextBox 快。

您可以尝试使用ScintillaNET,它是 Scintilla 的包装器。

您可以像使用 RichTextBox 一样设置控件文本:

scintilla1.Text = System.IO.File.ReadAllText(filePath);

【讨论】:

  • 我已经尝试探索这个 Scintilla ..它非常棒。我目前正在 7.118 秒内加载 88,940,748 长度 !!!!!现在,我需要搜索 143,685,300 长度的读数。目前,它只是在 ReadAllText 之后返回空白。我的 5!
  • 当前读取 475,212,842 长度,需要 6 秒才能加载。加载大文件时的问题是构建架构。我在 Build Platform Target 上从 AnyCPU 更改为 x64。都解决了。
【解决方案2】:

您的 LoadJSONtoRTB 方法异步运行。因此,您尝试从错误的线程更新 gui(文本框)。 这种方法将帮助您在正确的线程上运行 gui 更新:

this.Invoke(new Action(() => { rtbjson.Text += sr.ReadLine(); }));

当然,还有更有效的方法来使用大量文本填充控件,例如 StringBuilder。 重要的一点是始终在 gui 线程上更新 gui。 这可以通过运行 Form.Invoke 来完成

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-29
    • 2021-08-25
    • 2021-04-17
    • 1970-01-01
    • 2012-10-05
    • 2018-02-01
    • 2016-04-07
    • 1970-01-01
    相关资源
    最近更新 更多