2

文字列を使用してDataTableをXLSにエクスポートする簡単な方法があります。列数は5〜30で、行数は1〜1000の場合があります。パフォーマンスに問題がある場合があります。コードで何を変更できるか、アドバイスをお願いします。.net4.0を使用しています

public string FormatCell(string columnName, object value)
        {
        StringBuilder builder = new StringBuilder();
        string formattedValue = string.Empty;
        string type = "String";
        string style = "s21";

        if (!(value is DBNull) && columnName.Contains("GIS"))
            formattedValue = Convert.ToDouble(value).ToString("##.00000000°");
        else if (value is DateTime)
        {
            style = "s22";
            type = "DateTime";
            DateTime date = (DateTime)value;
            formattedValue = date.ToString("yyyy-MM-ddTHH:mm:ss.fff");
        }
        else if (value is double || value is float || value is decimal)
        {
            formattedValue = Convert.ToDecimal(value).ToString("#.00").Replace(',', '.');
            type = "Number";
        }
        else if (value is int)
        {
            formattedValue = value.ToString();
            type = "Number";
        }
        else
            formattedValue = value.ToString();

        builder.Append(string.Format("<Cell ss:StyleID=\"{0}\"><Data ss:Type=\"{1}\">", style, type));

        builder.Append(formattedValue);
        builder.AppendLine("</Data></Cell>");

        return builder.ToString();
    }

    public string ConvertToXls(DataTable table)
    {
        StringBuilder builder = new StringBuilder();

        int rows = table.Rows.Count + 1;
        int cols = table.Columns.Count;

        builder.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
        builder.AppendLine("<?mso-application progid=\"Excel.Sheet\"?>");
        builder.AppendLine("<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"");
        builder.AppendLine(" xmlns:o=\"urn:schemas-microsoft-com:office:office\"");
        builder.AppendLine(" xmlns:x=\"urn:schemas-microsoft-com:office:excel\"");
        builder.AppendLine(" xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"");
        builder.AppendLine(" xmlns:html=\"http://www.w3.org/TR/REC-html40/\">");
        builder.AppendLine(" <DocumentProperties xmlns=\"urn:schemas-microsoft-com:office:office\">;");
        builder.AppendLine("  <Author>Author</Author>");
        builder.AppendLine(string.Format("  <Created>{0}T{1}Z</Created>", DateTime.Now.ToString("yyyy-mm-dd"), DateTime.Now.ToString("HH:MM:SS")));
        builder.AppendLine("  <Company>Company</Company>");
        builder.AppendLine("  <Version>1.0</Version>");
        builder.AppendLine(" </DocumentProperties>");
        builder.AppendLine(" <ExcelWorkbook xmlns=\"urn:schemas-microsoft-com:office:excel\">");
        builder.AppendLine("  <WindowHeight>8955</WindowHeight>");
        builder.AppendLine("  <WindowWidth>11355</WindowWidth>");
        builder.AppendLine("  <WindowTopX>480</WindowTopX>");
        builder.AppendLine("  <WindowTopY>15</WindowTopY>");
        builder.AppendLine("  <ProtectStructure>False</ProtectStructure>");
        builder.AppendLine("  <ProtectWindows>False</ProtectWindows>");
        builder.AppendLine(" </ExcelWorkbook>");
        builder.AppendLine(" <Styles>");
        builder.AppendLine("  <Style ss:ID=\"Default\" ss:Name=\"Normal\">");
        builder.AppendLine("   <Alignment ss:Vertical=\"Bottom\"/>");
        builder.AppendLine("   <Borders/>");
        builder.AppendLine("   <Font/>");
        builder.AppendLine("   <Interior/>");
        builder.AppendLine("   <Protection/>");
        builder.AppendLine("  </Style>");
        builder.AppendLine("  <Style ss:ID=\"s21\">");
        builder.AppendLine("   <Alignment ss:Vertical=\"Bottom\" ss:WrapText=\"1\"/>");
        builder.AppendLine("  </Style>");
        builder.AppendLine("  <Style ss:ID=\"s22\">");
        builder.AppendLine("    <NumberFormat ss:Format=\"Short Date\"/>");
        builder.AppendLine("  </Style>");
        builder.AppendLine(" </Styles>");
        builder.AppendLine(" <Worksheet ss:Name=\"Export\">");
        builder.AppendLine(string.Format("  <Table ss:ExpandedColumnCount=\"{0}\" ss:ExpandedRowCount=\"{1}\" x:FullColumns=\"1\"", cols.ToString(), rows.ToString()));
        builder.AppendLine("   x:FullRows=\"1\">");

        //generate title
        builder.AppendLine("<Row>");
        foreach (DataColumn eachColumn in table.Columns)  // you can write a half columns of table and put the remaining columns in sheet2
        {
            if (eachColumn.ColumnName != "ID")
            {
                builder.Append("<Cell ss:StyleID=\"s21\"><Data ss:Type=\"String\">");
                builder.Append(eachColumn.ColumnName.ToString());
                builder.AppendLine("</Data></Cell>");
            }
        }
        builder.AppendLine("</Row>");

        //generate data
        foreach (DataRow eachRow in table.Rows)
        {
            builder.AppendLine("<Row>");
            foreach (DataColumn eachColumn in table.Columns)
            {
                if (eachColumn.ColumnName != "ID")
                {
                    builder.AppendLine(FormatCell(eachColumn.ColumnName, eachRow[eachColumn]));
                }
            }
            builder.AppendLine("</Row>");
        }
        builder.AppendLine("  </Table>");
        builder.AppendLine("  <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">");
        builder.AppendLine("   <Selected/>");
        builder.AppendLine("   <Panes>");
        builder.AppendLine("    <Pane>");
        builder.AppendLine("     <Number>3</Number>");
        builder.AppendLine("     <ActiveRow>1</ActiveRow>");
        builder.AppendLine("    </Pane>");
        builder.AppendLine("   </Panes>");
        builder.AppendLine("   <ProtectObjects>False</ProtectObjects>");
        builder.AppendLine("   <ProtectScenarios>False</ProtectScenarios>");
        builder.AppendLine("  </WorksheetOptions>");
        builder.AppendLine(" </Worksheet>");
        builder.AppendLine(" <Worksheet ss:Name=\"Sheet2\">");
        builder.AppendLine("  <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">");
        builder.AppendLine("   <ProtectObjects>False</ProtectObjects>");
        builder.AppendLine("   <ProtectScenarios>False</ProtectScenarios>");
        builder.AppendLine("  </WorksheetOptions>");
        builder.AppendLine(" </Worksheet>");
        builder.AppendLine(" <Worksheet ss:Name=\"Sheet3\">");
        builder.AppendLine("  <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">");
        builder.AppendLine("   <ProtectObjects>False</ProtectObjects>");
        builder.AppendLine("   <ProtectScenarios>False</ProtectScenarios>");
        builder.AppendLine("  </WorksheetOptions>");
        builder.AppendLine(" </Worksheet>");
        builder.AppendLine("</Workbook>");

        return builder.ToString();
    }

これを使用して:

string xlsData= ConvertToXls(someTable)


System.CodeDom.Compiler.TempFileCollection fileCollection = new System.CodeDom.Compiler.TempFileCollection();

                    string tempFileName = fileCollection.AddExtension("xls", true);

                    if (File.Exists(tempFileName))
                        File.Delete(tempFileName);

                    using (StreamWriter writer = new StreamWriter(tempFileName, false, Encoding.UTF8))
                        writer.Write(xlsData);
4

4 に答える 4

2

あなたができる最も簡単なことは、デフォルト値以外の容量でStringBuilderを宣言することです。

StringBuilder builder = new StringBuilder(100000);

デフォルトの割り当ては16バイトで、再割り当てが必要になるたびに2倍になります。これは、デフォルトを使用すると、何度も再割り当てされることを意味します。

システムのメモリが不足している場合、またはこれが本当に非常に大きい場合を除いて、以前に提案されたようにシステムを直接ストリーミングしても大きな違いが生じるとは思えません。すでに割り当てられているStreamBuilderオブジェクトにデータを追加するよりも、ファイルストリームの書き込みのオーバーヘッドが少ないとは思えないため、実際には状況がわずかに悪化する可能性があると思います(頻繁に再割り当てする必要がないと仮定します)。

最適な解決策は、たとえば10メガバイトまたは20メガバイトを超える可能性がある場合、stringbuilder出力を(システムのメモリに基づいて)あるサイズに成長するときに定期的にストリームに送信することです。そうすれば、メモリの問題を回避できるだけでなく、出力ストリームへの多くの小さな書き込みに関連する潜在的なオーバーヘッドを回避できます。

更新-テストノート:

非常に大きな文字列(> 50メガバイト)を作成するいくつかのテストを実行しましたが、事前にメモリを割り当てることにはほとんど違いはありません。

しかし、もっと重要なことは、可能な限り単純な形式を使用してそのような文字列を作成するためだけに必要な時間です。

  for (int i = 0; i < 10000000; i++)
  {
     builder.AppendLine("a whole bunch of text designed to see how long it takes to build huge strings ");
  }

ほとんど重要ではありません。デスクトップコンピュータのすべてのメモリを数秒でいっぱいにすることができます。

これが意味するのは、StringBuilderのオーバーヘッドはまったく問題ではないということです。このことから、ストリーム書き込みに切り替えても間違いなく役に立たないと推測することもできます。

代わりに、数千回または数万回実行している操作のいくつかを確認する必要があります。このループ::

foreach (DataRow eachRow in table.Rows)
        {
            builder.AppendLine("<Row>");
            foreach (DataColumn eachColumn in table.Columns)
            {
                if (eachColumn.ColumnName != "ID")
                {
                    builder.AppendLine(FormatCell(eachColumn.ColumnName, eachRow[eachColumn]));
                }
            }
            builder.AppendLine("</Row>");
        }
  • 選択から削除して、ColumnName!="ID"のチェックを削除します
  • FormatCellは、単一のデータ要素ごとに1回実行されます。これの効率の小さな変更は大きな影響を与える可能性があります
  • これまでは考えていませんでしたが、DataTableがSQLデータソースからのものである場合は、メモリ内のDataTableの代わりにDataReaderを直接使用してください

FormatCellを改善するための提案:

  • 事前に各列のデータ型のインデックスを作成しておくと、毎回コストのかかる型比較を行う必要がなくなります。
  • タイプとスタイルの文字列値を設定し、データ型に基づいてそれらを変更するには、コストがかかります。代わりに列挙型を使用してから、列挙型の値に基づいてハードコードされた文字列を使用して値を出力します。
  • FormatCell内の変数をメインクラスに移動して、プロシージャを呼び出すたびに変数を作成/割り当てする必要がないようにします。

インデックスを作成するには、以下のコードのように、列番号を各列の型を定義する配列にマップし、FormatCellで作成済みの列番号のデータ型へのマップを使用するのが最も効率的な方法だと思います。

enum DataTypes
    {
        DateTime = 1,
        Float = 2,
        Int = 3,
        String = 4
    }
    DataTypes[] types = new DataTypes[tbl.Columns.Count];
    for (int col=0;i<tbl.Columns.Count;col++) {
        object value = tbl.Rows[0][col];
        if (value is double || value is float || value is decimal) {
            types[col]=DataTypes.Float;
        } else if (value is DateTime) {
            types[col]=DataTypes.DateTime;
        } else if (value is int) {
            types[col]=DataTypes.Int;
        } else {
            types[col]=DataTypes.String;
        }
    }

次に、FormatCellに列記を渡すと、配列からデータ型を検索でき、スイッチで確認できます。

switch(types[colNumber]) {
   case DataTypes.DateTime:
       ...
       break;
   case DataTypes.Int:
...
 /// and so on
}

これにより、オーバーヘッドが大幅に削減されると思います。

于 2010-10-20T17:26:43.643 に答える
1

dotTraceのようなものでコードをプロファイリングして、時間がどこに向かっているのかを確認する必要があります。少なくともタイマーを入れて、各パーツにかかる時間を確認してください。ボトルネックがどこにあるかを知らずに最適化することは、時間の無駄になる可能性があります。例えば:

   DateTime startTime = DateTime.Now;
   Debug.WriteLine("Start : " + startTime);

   //some code

   Debug.WriteLine("End: " + DateTime.Now);
   Debug.WriteLine("Elapsed : " + (DateTime.Now - startTime));

上記のジョンは正しいと思います。ストリームを使用します。例えば。

StreamWriter streamWriter = System.IO.File.CreateText("c:\\mynewfile.xls");

streamWriter.AutoFlush = false;

//lots of writes

streamWriter.Flush();
streamWriter.Close();

自動フラッシュfalseおよびtrueでテストする必要があります。また、メモリストリームを試してみることもできます。

StreamWriter streamWriter = new StreamWriter(new MemoryStream());
于 2010-10-19T23:39:59.027 に答える
0

さて、あなたはメモリ内にますます大きな文字列を作成しているだけです...サイズが大きくなるにつれてそれはますます悪化するでしょう。

GIANT文字列を作成し、それをファイルにシリアル化する代わりに、これをファイルにストリーミングしない理由はありますか?

詳細を追加した後に編集します。

ConvertToXLSに文字列を返す代わりに、そのストリームライターをconvertToXLSメソッドに渡します。

public void ConvertToXLS( DataTable table, StreamWriter stream )
{
    ...
}

ConverToXLS内で、そのStringBuilderを取り除き、toのすべての呼び出しを置き換えますbuilder.AppendLine( x )

stream.WriteLine(x); 

そうすれば、巨大な文字列を作成する代わりに、ストリームに書き込むことになります。

于 2010-10-11T21:12:37.393 に答える
0

行を2回書き出すのではなく、1回はメモリに書き込んでからディスクに書き込むのではなく、1回の書き込み操作にまとめてみてください。ディスクに直接。

.netのxmlオブジェクトとstringbuilderのパフォーマンスの比較がどのようなものかはわかりませんが、Xmlを書き出すことを知っていれば、xmlオブジェクトソリューション、xmlwriterxlinqなどを使用する傾向があります。作成するデータは、毎回xmlに準拠しているため、非常に安心できます。

SSに関する他の投稿では、StringBuilderよりもXmlTextWriterを使用した方が速いと考えていると述べています。

StringBuilderとXmlTextWriter

バッファサイズの変更と書き込みの遅延に関する回答は機能しますが、非常に失敗する可能性があります。すべてをメモリで実行すると操作が高速になりますが、メモリフットプリントが非常に大きくなる可能性があるため、OSが一部を実行する可能性があります。マシン全体に影響を与えるディスクスワッピング(マシンで実行しているものによって異なります)。満足のいく妥協点を見つけて、本番システムが満足できる書き込み速度でデータをストリーミングします。

于 2010-10-21T10:53:03.823 に答える