6

大きなDataTable(> 50 lacs( 5M ) DataRows ) を.csvファイルにエクスポートする必要がある

以下のコードを使用していますが、時間がかかります。

public void CreateCSVFile(DataTable dtDataTablesList, string strFilePath)
{
    // Create the CSV file to which grid data will be exported.
    StreamWriter sw = new StreamWriter(strFilePath, false);
    //First we will write the headers.
    int iColCount = dtDataTablesList.Columns.Count;
    for (int i = 0; i < iColCount; i++)
    {
        sw.Write(dtDataTablesList.Columns[i]);
        if (i < iColCount - 1)
        {
            sw.Write("", "");
        }
    }
    sw.Write(sw.NewLine);

    // Now write all the rows.
    foreach (DataRow dr in dtDataTablesList.Rows)
    {
        for (int i = 0; i < iColCount; i++)
        {
            if (!Convert.IsDBNull(dr[i]))
            {
                sw.Write(dr[i].ToString());
            }
            if (i < iColCount - 1)
            {
                sw.Write("", "");
            }
        }
        sw.Write(sw.NewLine);
    }
    sw.Close();
}

他に手っ取り早い方法があれば教えてください。

4

5 に答える 5

4

StreamWriter.Write(..)常に呼び出す代わりに、 StringBuilder の使用を検討できます。すべての文字列を Builder に追加し、ディスクに 1 回だけ書き込みます。

string filePath = @"e:\temp\test.csv";
string delimiter = ",";

#region init DataTable
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("a", typeof(string)));
dt.Columns.Add(new DataColumn("b", typeof(string)));
dt.Columns.Add(new DataColumn("c", typeof(string)));
dt.Columns.Add(new DataColumn("d", typeof(string)));
dt.Columns.Add(new DataColumn("e", typeof(string)));
dt.Columns.Add(new DataColumn("f", typeof(string)));
dt.Columns.Add(new DataColumn("g", typeof(string)));
dt.Columns.Add(new DataColumn("h", typeof(string)));
dt.Columns.Add(new DataColumn("i", typeof(string)));
dt.Columns.Add(new DataColumn("j", typeof(string)));
dt.Columns.Add(new DataColumn("k", typeof(string)));
dt.Columns.Add(new DataColumn("l", typeof(string)));
dt.Columns.Add(new DataColumn("m", typeof(string)));
dt.Columns.Add(new DataColumn("n", typeof(string)));
dt.Columns.Add(new DataColumn("o", typeof(string)));
dt.Columns.Add(new DataColumn("p", typeof(string)));

for (int i = 0; i < 100000; i++)
{
    DataRow dr = dt.NewRow();
    for (int j = 0; j < dt.Columns.Count; j++)
    {
        dr[j] = "test" + i + " " + j;
    }
    dt.Rows.Add(dr);
}
#endregion

Stopwatch sw = new Stopwatch();
sw.Start();
StringBuilder sb = new StringBuilder();
foreach (DataRow dr in dt.Rows)
{
    sb.AppendLine(string.Join(delimiter, dr.ItemArray));
}
File.WriteAllText(filePath, sb.ToString());
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();

編集

100000 行で 271 ミリ秒かかり、約 18 MB のファイルが作成されました

@aiodintsov が指摘したように、数 MBのデータがある場合、StringBuilder を使用すると問題が発生する可能性があります。そこで、彼のコメントに従って例を作成しました。私にとってはうまくいきました。2685 ミリ秒以内に 1,000,000 行をエクスポートしました。

Stopwatch sw = new Stopwatch();
sw.Start();
using (StreamWriter swr = 
         new StreamWriter(File.Open(filePath, FileMode.CreateNew), Encoding.Default, 1000000))
         // change buffer size and Encoding to your needs
{
    foreach (DataRow dr in dt.Rows)
    {
        swr.WriteLine(string.Join(delimiter, dr.ItemArray));
    }
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
于 2012-09-07T07:45:09.867 に答える
2

StringBuilderas バッファを使用するようにコードを少し変更しました。これにはより多くの RAM が必要ですが、より効率的なはずです。OutOfmemoryExceptions回避し、最大の効率を得るために、初期の Capacity と MaxCapacity をいじってみましょう。

public void CreateFastCSVFile(DataTable table, string strFilePath)
{
    const int capacity = 5000000;
    const int maxCapacity = 20000000;

    //First we will write the headers.
    StringBuilder csvBuilder = new StringBuilder(capacity);
    csvBuilder.AppendLine(string.Join(",", table.Columns.Cast<DataColumn>().Select(c => c.ColumnName)));

    // Create the CSV file and write all from StringBuilder
    using (var sw = new StreamWriter(strFilePath, false))
    {
        foreach (DataRow dr in table.Rows)
        {
            if (csvBuilder.Capacity >= maxCapacity)
            {
                sw.Write(csvBuilder.ToString());
                csvBuilder = new StringBuilder(capacity);
            }
            csvBuilder.Append(String.Join(",", dr.ItemArray));
        }
        sw.Write(csvBuilder.ToString());
    }
}

サンプルデータ (10000000/100 lac DataRows) を使用した簡単な測定を次に示します。

サンプルデータ:

var TblData = new DataTable();
TblData.Columns.Add("FeeID", typeof(int));
TblData.Columns.Add("Amount", typeof(int));
TblData.Columns.Add("FeeItem", typeof(string));
TblData.Columns.Add("Type", typeof(char));
for (int i = 0; i < 1000000; i++)
{
    TblData.Rows.Add(9, 8500, "Admission Free", 'T');
    TblData.Rows.Add(9, 950, "Annual Fee", 'T');
    TblData.Rows.Add(9, 150, "Application Free", 'T');
    TblData.Rows.Add(9, 850, "Boy's Uniform", DBNull.Value);
    TblData.Rows.Add(9, 50, DBNull.Value, 'R');
    TblData.Rows.Add(10, 7500, "Admission Free", 'T');
    TblData.Rows.Add(11, 900, "Annual Fee", 'T');
    TblData.Rows.Add(11, 150, "Application Free", 'T');
    TblData.Rows.Add(11, 850, DBNull.Value, 'T');
    TblData.Rows.Add(11, 50, "Computer Free", 'R');
}
int rowCount = TblData.Rows.Count; // 10000000

測定 (207 MB のファイルで 30 秒未満で問題ないようです):

var watch = new System.Diagnostics.Stopwatch();
watch.Start();
CreateFastCSVFile(TblData, @"C:\Temp\TestCSV.csv");
watch.Stop();
Console.Write("Elapsed: {0}", watch.Elapsed); // 00:00:26 for 207 MB CSV-file
于 2012-09-07T08:35:52.637 に答える
1

次のような接続文字列で OleDbConnection を使用することを検討してください

"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\txtFilesFolder;Extended Properties="text;HDR=Yes;FMT=Delimited";

その他のサンプル接続文字列

CSV ファイルと区切り文字にはいくつかの規則があり、引用符、タブ、コンマ、改行などの文字は特に注意して扱う必要があります。このようなルールの詳細については、RFC4180を参照してください。

UPD: ファイル ストリーム バッファを増やしてみてください:

using(var stream = new FileStream(path,FileMode.Create,FileAccess.Write,FileShare.None,4*1024*1024))
{
  // your code referencing stream in StreamWriter()
}

StreamWriter コンストラクターでより大きなバッファー サイズを指定することもできます。パフォーマンスを改善するために他にできることはあまりありません.StreamWriterはすでに十分に高速であり、標準型のToString()は非常に優れています. そこにユーザー タイプを出力するとは思えませんが、出力する場合は、ToString() メソッドが十分に効率的であることを確認してください。他のすべては、ここでは制御できません。

于 2012-09-07T07:27:52.880 に答える
1

これが私の最終的な解決策です。

このコードを使用すると、50 lakhs のレコードを 2 分未満で csv ファイルにエクスポートできます。ここでは datatable の代わりに datareader を使用しました。

private void button1_Click(object sender, EventArgs e)
    {

        Stopwatch swra = new Stopwatch();
        swra.Start();
        string NewconnectionString = "myCoonectionString";
        StreamWriter CsvfileWriter = new StreamWriter(@"D:\testfile.csv");
        string sqlselectQuery = "select * from Mytable";
        SqlCommand sqlcmd = new SqlCommand();

        SqlConnection spContentConn = new SqlConnection(NewconnectionString);
        sqlcmd.Connection = spContentConn;
        sqlcmd.CommandTimeout = 0;
        sqlcmd.CommandType = CommandType.Text;
        sqlcmd.CommandText = sqlselectQuery;
        spContentConn.Open();
        using (spContentConn)
        {
            using (SqlDataReader sdr = sqlcmd.ExecuteReader())
            using (CsvfileWriter)
            {
                //For getting the Table Headers
                DataTable Tablecolumns = new DataTable();

                for (int i = 0; i < sdr.FieldCount; i++)
                {
                    Tablecolumns.Columns.Add(sdr.GetName(i));
                }
                CsvfileWriter.WriteLine(string.Join(",", Tablecolumns.Columns.Cast<datacolumn>().Select(csvfile => csvfile.ColumnName)));
                //For table headers

                while (sdr.Read())
                //based on your columns
                    YourWriter.WriteLine(sdr[0].ToString() + "," + sdr[1].ToString() + "," + sdr[2].ToString() + "," + sdr[3].ToString() + "," + sdr[4].ToString() + "," + sdr[5].ToString() + "," + sdr[6].ToString() + "," + sdr[7].ToString() + "," + sdr[8].ToString() + "," + sdr[9].ToString() + "," + sdr[10].ToString() + "," + sdr[11].ToString() + ",");

            }
        }
       swra.Stop();
Console.WriteLine(swra.ElapsedMilliseconds);
}

全てに感謝。

于 2012-09-14T09:39:40.137 に答える
0

可能な高速化の方法の 1 つは、StringBuilder を使用して StringBuilder に 1K レコード データを追加し、それを sw.Write(); に書き込むことです。

したがって、ロジックは、最初に SB に 1000 レコードを書き込み、次に SW.Write に書き込む必要があります。

これにより、確実にパフォーマンスが向上します。

10K で 1000 レコードを増やしてテストすると、パフォーマンスは大幅に向上します。

お役に立てれば。

于 2012-09-07T07:41:34.080 に答える