优化C#中用于大文件的列表视图

7
我有一个C#程序,需要导入一个大约有42,000行的csv文件。文件中所有数据都以以下方式存储:
Zipcode,City,State 我将所有信息导入到listview的三列中。目前,导入这些数据需要约30-50秒的时间。我的问题是如何优化代码以缩短这个时间?
以下是我的一部分代码。注释掉的代码是我之前尝试过的,但没有成功地减少时间,因此我重新编写了更易于阅读的代码。
 //These are globally declared.
lvZip.Columns.Add("Zipcode", 150, HorizontalAlignment.Left);
lvZip.Columns.Add("City", 150, HorizontalAlignment.Left);
lvZip.Columns.Add("State", 150, HorizontalAlignment.Left);
lvZip.View = View.Details;

lvZip.Items.Clear();

        //string dir = System.IO.Path.GetDirectoryName(
        //  System.Reflection.Assembly.GetExecutingAssembly().Location);

        //string path = dir + @"\zip_code_database_edited.csv";
        //var open = new StreamReader(File.OpenRead(path));

        //foreach (String s in File.ReadAllLines(path))
        //{
        //    Zipinfo = s.Split(',');
        //    Zipinfo[0] = Zipinfo[0].Trim();
        //    Zipinfo[1] = Zipinfo[1].Trim();
        //    Zipinfo[2] = Zipinfo[2].Trim();
        //    lvItem = new ListViewItem(Zipinfo);
        //    lvZip.Items.Add(lvItem);
        //}
        //open.Close();

        StreamReader myreader = File.OpenText(path);
        aLine = myreader.ReadLine();

        while (aLine != null)
        {
            Zipinfo = aLine.Split(',');
            Zipinfo[0] = Zipinfo[0].Trim();
            Zipinfo[1] = Zipinfo[1].Trim();
            Zipinfo[2] = Zipinfo[2].Trim();
            lvItem = new ListViewItem(Zipinfo);
            lvZip.Items.Add(lvItem);
            aLine = myreader.ReadLine();
        }
        myreader.Close();

到目前为止,这3个答案都很好。你可能想尝试一下ListView属性DoubleBuffered,但如果已经使用了BeginUpdate+EndUpdate,它可能不会有任何区别。如果你认为值得额外的努力,你可以通过VirtualMode使ListView非常快(例如,在加载仅1个“页面”的数据时少于1秒)。 - groverboy
使用 BeginUpdate()EndUpdate() 将提供更为可见的性能提升。但是 Tweety 建议采用 AddRange() 方法将进一步优化您的代码。虽然性能提升可能不像 AddRange() 那样显著,但 TextFieldParser 类专门设计用于处理结构化文本文件,如 CSV。 - Derek W
4个回答

6
你应该在将任何内容添加到ListView之前和之后使用 ListView.BeginUpdate()ListView.EndUpdate()。第二件事是使用 ListView.AddRange() 而不是 ListView.Add()。使用Add方法会在每次使用它时重绘ListView。而使用 ListView.AddRange() 只会重绘一次。这样可以为您优化一些内容。

2

您可以尝试以下方法:

lvZip.BeginUpdate();

在您开始添加所有项目之前,请注意以下事项。
接下来,请执行以下操作:
lvZip.EndUpdate();

当您完成后。

这将防止控件在添加每个项目时进行绘制,从而使整个过程变得非常缓慢。


这就是为什么我喜欢stackoverflow的原因。清晰简洁的答案。它帮了我很大的忙。加载大约需要5秒钟。还有其他建议吗?谢谢! - Mark P.
1
让它更快可能会很棘手。这里有一个类似的问题,提供了其他建议和时间:https://dev59.com/EWox5IYBdhLWcg3wvWsy - Baldrick
谢谢。我并不是想重复已经发布的问题。我做了一些研究,但没有找到那个特定的问题! - Mark P.
没问题。我觉得对于你的情况来说,5秒钟听起来还不错。 - Baldrick

2
黄金法则:不要使用String.Split()来读取CSV数据。
.NET Framework已经有一个内置的专用CSV解析器,叫做TextFieldParser
它位于Microsoft.VisualBasic.FileIO命名空间中。
不仅有许多边缘情况是String.Split()无法处理的,而且使用StreamReader也更慢。
最后一条建议:为了确保您的可处理对象被释放(释放非托管资源),请使用using语句。我看到你在上面的代码中没有使用它们(双关语不是故意的)。
实际上,这并不远离本问题的范围,因为有效的内存管理可以提高代码的性能。

一个有用的类如TextFieldParser在VB命名空间中是做什么的?我是C#新手,之前从未听说过它,但根据我的研究它看起来很容易。谢谢你的提示! - Mark P.

0

可能需要更多的工作,但是使用一个DataGridView,并将文本文件用作数据源,您可以在不到2秒钟的时间内加载42,000行的.csv文件。以下是一些要查看的代码:

    private void button2_Click(object sender, EventArgs e)
    {
        string errorInfo = String.Empty;
        //open text file into Dataset:
        string textFilePath = @"textfile1.csv";

        DataSet dataTextFile = new DataSet("textfile");
        if(!LoadTextFile(textFilePath, dataTextFile, out errorInfo))
        {
            MessageBox.Show("Failed to load text file:\n" + errorInfo,
                "Load Text File");
            return;
        }
        dgTextFile.DataSource = dataTextFile.Tables[0];
        dataTextFile.Dispose(); 
    }

    private bool LoadTextFile(string textFilePath, DataSet dataToLoad, out string errorInfo)
    {
        errorInfo = String.Empty;

        try
        {
            string textFileFolder = (new System.IO.FileInfo(textFilePath)).DirectoryName;
            string textConnectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;" +
                                            "Data Source=" + textFileFolder + ";" +
                                            "Extended Properties=\"text;\";";
            OleDbConnection textConnection = new OleDbConnection(textConnectionString);

            textConnection.Open();

            textFilePath = (new System.IO.FileInfo(textFilePath)).Name;
            string selectCommand = "select * from " + textFilePath;

            //open command:
            OleDbCommand textOpenCommand = new OleDbCommand(selectCommand);
            textOpenCommand.Connection = textConnection;

            OleDbDataAdapter textDataAdapter = new OleDbDataAdapter(textOpenCommand);

            int rows = textDataAdapter.Fill(dataToLoad);

            textConnection.Close();
            textConnection.Dispose();

            return true;
        }
        catch(Exception ex_load_text_file)
        {
            errorInfo = ex_load_text_file.Message;
            return false;
        }
    }

这段代码有一部分是来自于MSDN的示例,但我似乎找不到那个页面了。


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