質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

VB.NET

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

Q&A

解決済

6回答

8263閲覧

.Net Framework Compact 3.5でのCSVファイルからListへの登録を早くしたい

k47

総合スコア12

CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

VB.NET

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

0グッド

0クリップ

投稿2016/01/19 13:37

編集2016/01/27 00:39

お世話になっております。

現在、ハンディターミナルの開発を行っていて、CSVファイルのデータをListに登録したいと考えています。
Listへの登録はできているのですが、時間がかかってしまっているのをどうにかして早くすることは
できないでしょうか?

CSVファイルは、ヘッダー:有、括り:有(")、区切り文字:, 、文字コード:SJIS です。
2つのファイルを連続して読込み、それぞれ別のListに登録しています。
1つ目のファイルは14項目、2つ目のファイルは18項目あり、共に1万5千件のデータ量です。
1つ目のファイルに11秒ほど、2つ目のファイルに35秒ほどかかってしまっています。
これを数秒まで縮めることは可能でしょうか?
宜しくお願いします。

以下現状のコード(1ファイル目)です。2ファイル目も同様の処理です。
行データを配列に変換している部分のソースは、サイトからのコピペを使用してます。
(ArrayListの部分をList(Of string)に変更していますが)
CsvToListの部分の元ソース

環境:Visual Studio 2008 (vb.net)

Public M_MODEL As New List(Of model) Public Structure model Dim item_id As String Dim bumon_cd As String Dim mgt_num As String Dim model_cd As String Dim pert_cd As String Dim type_cd As String Dim gds_cmpt_cd As String Dim model_nm As String Dim pert_nm As String Dim type_nm As String Dim gds_cmpt_nm As String Dim gds_kbn As Integer Dim JAN As String Dim criterion As Integer End Structure Public Function Import_M_MODEL() As Boolean Dim ret As Boolean = True Dim row As model Dim Reader As IO.StreamReader = Nothing Dim line As String Dim strTemp As String() Dim header As Integer = 0 Try Reader = New IO.StreamReader('ファイルパス', SJIS) Do Until Reader.EndOfStream 'ヘッダーは読まない If header = 0 Then line = Reader.ReadLine header = 1 End If line = Reader.ReadLine '行単位データを,で分割し、配列へ格納 strTemp = CsvToList(line)(0) row = New model '各列に値をセット row.item_id = strTemp(0) row.bumon_cd = strTemp(1) row.mgt_num = strTemp(2) row.model_cd = strTemp(3) row.pert_cd = strTemp(4) row.type_cd = strTemp(5) row.gds_cmpt_cd = strTemp(6) row.model_nm = strTemp(7) row.pert_nm = strTemp(8) row.type_nm = strTemp(9) row.gds_cmpt_nm = strTemp(10) If strTemp(11).Length = 0 Then row.gds_kbn = Nothing Else row.gds_kbn = CInt(strTemp(11)) End If row.JAN = strTemp(12) If strTemp(13).Length = 0 Then row.criterion = Nothing Else row.criterion = CInt(strTemp(13)) End If 'Listに行を追加 M_MODEL.Add(row) Loop Catch ex As Exception ret = False Finally Reader.Close() End Try Return ret End Function

以下はCsvToListのソースです。

''' <summary> ''' CSVをListに変換 ''' </summary> ''' <param name="csvText">CSVの内容が入ったString</param> ''' <returns>変換結果のList</returns> Public Function CsvToList(ByVal csvText As String) As List(Of String()) '前後の改行を削除しておく csvText = csvText.Trim(New Char() {ControlChars.Cr, ControlChars.Lf}) Dim csvRecords As New List(Of String()) Dim csvFields As New List(Of String) Dim csvTextLength As Integer = csvText.Length Dim startPos As Integer = 0 Dim endPos As Integer = 0 Dim field As String = "" While True '空白を飛ばす While startPos < csvTextLength _ AndAlso (csvText.Chars(startPos) = " "c _ OrElse csvText.Chars(startPos) = ControlChars.Tab) startPos += 1 End While 'データの最後の位置を取得 If startPos < csvTextLength AndAlso csvText.Chars(startPos) = ControlChars.Quote Then '"で囲まれているとき '最後の"を探す endPos = startPos While True endPos = csvText.IndexOf(ControlChars.Quote, endPos + 1) If endPos < 0 Then Throw New ApplicationException("""が不正") End If '"が2つ続かない時は終了 If endPos + 1 = csvTextLength OrElse _ csvText.Chars((endPos + 1)) <> ControlChars.Quote Then Exit While End If '"が2つ続く endPos += 1 End While '一つのフィールドを取り出す field = csvText.Substring(startPos, endPos - startPos + 1) '""を"にする field = field.Substring(1, field.Length - 2). _ Replace("""""", """") endPos += 1 '空白を飛ばす While endPos < csvTextLength AndAlso _ csvText.Chars(endPos) <> ","c AndAlso _ csvText.Chars(endPos) <> ControlChars.Lf endPos += 1 End While Else '"で囲まれていない 'カンマか改行の位置 endPos = startPos While endPos < csvTextLength AndAlso _ csvText.Chars(endPos) <> ","c AndAlso _ csvText.Chars(endPos) <> ControlChars.Lf endPos += 1 End While '一つのフィールドを取り出す field = csvText.Substring(startPos, endPos - startPos) '後の空白を削除 field = field.TrimEnd() End If 'フィールドの追加 csvFields.Add(field) '行の終了か調べる If endPos >= csvTextLength OrElse csvText.Chars(endPos) = ControlChars.Lf Then '行の終了 'レコードの追加 'csvFields.TrimExcess() csvRecords.Add(csvFields.ToArray) csvFields.Clear() If endPos >= csvTextLength Then '終了 Exit While End If End If '次のデータの開始位置 startPos = endPos + 1 End While 'csvRecords.TrimExcess() Return csvRecords End Function

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

自己解決

すみません。
こんなに時間が空いてしまいました...。

みなさんの修正点を参考にさせていただきましたが、
ファイルは"なしになったので、splitで区切ることにしました。

そして、最初に言われていたマスタ件数より多いファイル件数の為、
どっちにしろマスタの精査が入ることになり、件数が少なくなるようです。

みなさん、ありがとうございました。

投稿2016/04/21 11:46

k47

総合スコア12

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

動作検証
検証土台がアレなんで参考データ程度です。
参考にもならんか。

csv-reader 部:RFC-4180 準拠、セル内改行、エスケープ文字認識、行末改行コードは、windows,unix,旧mac 3種類判別
cav-writer 部:ダブルクオート囲み、カンマ区切り、文字列内エスケープ

動作環境:
windows core i5 dual core, 3.3mhz, 4gb memory
java8
実行時間:
reader 420ms
writer 1240ms
csv データ内容:
18 カラム、ヘッダ無し、1カラムあたり最大40文字、全角半角混在、ファイルサイズ平均 5mb
15000 行、各カラムはダブルクオートで囲まれる、半角カンマ前後にスペースは無い、
サンプルデータ作り込むのがしんどいので、幾つか条件付けました。

ジャンヌ.netの csvtolist メソッドは、1文字ずつ解析してます。上記javaのcsv-reader/writerも1文字ずつ解析しながらやっています。
で、幾つかの前提条件をつけることで、読み込み処理を高速化できます。

  1. すべてのカラムはダブルクオートで囲まれること
  2. すべてのカラムは半角カンマ区切りで前後にスペースがないこと
  3. カラム中のデータにダブルクオートが含まれないこと
  4. カラム中のデータに改行コードが含まれないこと
  5. カラム中のデータに半角カンマが含まれないこと

幾つかの前提条件次第では、
1行読み込み、カンマ分割、先頭+1〜末尾-1をカラムデータとして抽出
これを繰り返すだけで文字列リストになりますね。

入力csvのデータ仕様を再確認して、このcsvさえ読めればいい、特化した読み込み処理で十分だと思いますがどうででょうか。

投稿2016/01/27 06:44

ipadcaron

総合スコア1693

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ipadcaron

2016/01/27 06:56

ファイル読み込みは、一括で読み込み、1行分ずつ抽出する方がディスクアクセスが減らせまづ。 ハードディスクの場合、1バイトの読み込みでも、1ms以下にはなりません。ssdや、flashromなら1行ずつ読んでも気にするほどの速度劣化は無いででょう。 書き込みもバッファリング使い、書き込み回数を減らす工夫を。csv読むだけだから書き込みは無いのか。 1. 一括読込、1行ずつ切り分け。 2. 半分読んで、 3. 10回読んで、 4.毎回 readline で、 兎に角遅いって言っても今の状態では工夫の余地が一杯あります。 頑張ってください。
guest

0

文字列の操作(Replaceなど)は、結構重い処理になります。
バイト志向でCSVファイルを解析することが、解析に関しては一番速く処理できそうです。一度、こちらのコードを実行していただいて、その速度がどんなものか試されると、マシンスペック上の限界を知るご参考になるかもしれません。

(Windowsフォームにボタンを貼っています)

VB

1Public Class Form1 2 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 3 Dim sjisEncoding As System.Text.Encoding = System.Text.Encoding.GetEncoding(932) 4 5 Dim filePath As String = "C:\SAMPLE_SJIS.CSV" ' 入力ファイルパス 6 Dim result As List(Of Object) = New List(Of Object) ' 出力データ 7 8 Dim readHeader As Boolean = False ' ヘッダ行を読み終わったらTrue 9 10 Using fs As New System.IO.FileStream(filePath, IO.FileMode.Open) 11 Dim record As List(Of String) = New List(Of String) 12 Dim byteBuffer(256) As Byte 13 Dim byteBufferPosition As Integer = 0 14 Dim inQuotes As Boolean = False 15 Dim leadingByteData As Byte = 0 16 Dim byteData As Integer 17 18 Do 19 byteData = fs.ReadByte() 20 If byteData = -1 Then Exit Do 21 22 If byteBuffer.Length < byteBufferPosition - 1 Then Throw New ApplicationException("1フィールドの長さがバッファサイズ付近に達しています") ' -1 は、SJISコード処理などで2バイト書く可能性から 23 24 If IsSJisLeadingByte(leadingByteData) Then 25 ' SHIFT JIS の2バイト目 26 byteBuffer(byteBufferPosition) = leadingByteData 27 byteBufferPosition += 1 28 byteBuffer(byteBufferPosition) = CType(byteData, Byte) 29 byteBufferPosition += 1 30 leadingByteData = 0 31 Continue Do 32 End If 33 34 Select Case byteData 35 Case 10 ' LFコード 36 If leadingByteData = 13 Then ' 1つ前はCR 37 If 0 < byteBufferPosition Then 38 Dim v As String = sjisEncoding.GetString(byteBuffer, 0, byteBufferPosition) 39 record.Add(v) 40 byteBufferPosition = 0 41 leadingByteData = 0 42 End If 43 44 ' 1レコード終了 45 If Not readHeader Then 46 readHeader = True 47 Else 48 result.Add(record) 49 End If 50 record = New List(Of String) 51 Continue Do 52 End If 53 Throw New ApplicationException("不自然な箇所にLFコードが見つかりました") 54 Case 13 ' CRコード 55 If leadingByteData = 34 Then 56 ' 直前は終了の引用符 57 inQuotes = False 58 End If 59 If inQuotes Then Throw New ApplicationException("フィールドの途中にCRコードが見つかりました") 60 leadingByteData = 13 61 Continue Do 62 Case 32 ' 空白 63 If Not inQuotes Then 64 ' 二重引用符内でなければ、無視する 65 Continue Do 66 End If 67 Case 34 ' 二重引用符 68 If Not inQuotes Then 69 ' 開始の引用符 70 inQuotes = True 71 Else 72 If leadingByteData = 34 Then ' 1つ前も二重引用符だった 73 byteBuffer(byteBufferPosition) = 34 ' 1つだけにする 74 byteBufferPosition += 1 75 leadingByteData = 0 76 Else 77 leadingByteData = 34 ' 次まで保留 78 End If 79 End If 80 Continue Do 81 Case 44 ' カンマ 82 If leadingByteData = 34 Then 83 ' 直前は終了の引用符 84 inQuotes = False 85 End If 86 If Not inQuotes Then 87 ' 二重引用符内でなければ、フィールドの区切りとみなす 88 ' byteBuffer(byteBufferPosition) = 0 89 Dim v As String = sjisEncoding.GetString(byteBuffer, 0, byteBufferPosition) 90 record.Add(v) 91 byteBufferPosition = 0 92 leadingByteData = 0 93 Continue Do 94 End If 95 ' 二重引用符内なら、通常の文字扱い 96 End Select 97 If IsSJisLeadingByte(byteData) Then 98 ' SHIFT JIS の1バイト目 99 leadingByteData = CType(byteData, Byte) 100 Continue Do 101 End If 102 103 ' 1バイト文字 104 byteBuffer(byteBufferPosition) = CType(byteData, Byte) 105 byteBufferPosition += 1 106 leadingByteData = 0 107 Loop 108 fs.Close() 109 End Using 110 111 ' result に、結果(List(Of String)型)が入っています。 112 End Sub 113 114 ' バイト値が SHIFT JIS の1バイト目とみなせればTrue、それ以外はFalseを返します。 115 Private Shared Function IsSJisLeadingByte(ByVal s As Byte) As Boolean 116 Return (&H81 <= s AndAlso s <= &H9F) OrElse (&HE0 <= s AndAlso s <= &HFC) 117 End Function 118End Class

こんにちは。
私もCompactFrameworkを使用したことがないので、とんちんかんなことがありましたらご容赦ください。
次のような箇所で改善できるかな、と思いました(「元のソース」の内容と重複したものもあります)

http://www.atmarkit.co.jp/fdotnet/dotnettips/487csvparser/csvparser.html

私の経験上、結構速くておすすめです。CSVの書式に注意ですが...

  • 1行ずつ走査して分離させ、改行コードを除く処理をするかわりに、System.IO.File.ReadAllLinesを使う
  • Listオブジェクトの初期化時に初期要素数を指定しておき、拡張処理の発生を防ぐ

切り分けをされた結果からすると、あまり関係なさそうはありますが...

他にも思いつき次第、挙げてみます。

ただ、限界がありそうでしょうから、dojikkoさんの仰られるように、ワーカースレッドとUIスレッドと分離して、操作感を確保することが一番有用だったりするかもしれません。

追加で...
CsvToList関数に汎用性を持たせたい理由がないならば、CsvToList関数内でmodelデータを生成してそれを戻り値にすると、速度はいかがでしょう?
現状のコードですと、CSVのフィールド数がどこかのレコードで万一不足していると、どのみち配列のOutOfRange例外が発生してしまいます。

投稿2016/01/27 03:45

編集2016/01/29 04:42
hsk

総合スコア728

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k47

2016/01/27 04:14

コメントありがとうございます。 TextFieldParserを使用することに関してですが、.NetCompactでは対応してないんです...。 なのでTextFieldParserが実装されていなくてもできる処理で行っています。 ODBCドライバはできなかったような...?確認してみます! ReadAllLinesは、やってみたんですが、メモリ不足で...。 CsvToListの処理は、別でも使っているので中で生成は難しいですが、 見直す必要有ですね... もう少し頑張ってみます。
hsk

2016/01/27 04:37

なるほど、メモリを大量に使う手段はとりずらいのですね。 組み込み系開発の道は厳しいですね...
hsk

2016/01/29 04:48

ベタに組んでいたC言語開発時代が懐かしくなって...サンプルコードを組んでみました。 組み込み系のようなメモリリソースが潤沢にない環境では、ちょこちょこと動かしていくことが速い気がしました。 試してはありますが、エラーなど出てしまいましたら修正をしていただければと思います(Compact Framework に対応していなかったりしましたら、ご容赦ください。OrElseやAndAlsoは、VS2008で解釈不能でしたら Or や And にしても差し支えないです)。
guest

0

いくつか不思議に思える部分があるので聞いてもいいですか?


番号リストDim ret As String = True
なぜ、String型 の変数に Boolean型 の値を入れているのでしょうか?


CsvToListメソッドの ArrayList を List(Of string) にしていると書いてありますが、
CsvToListメソッドの戻り値を受ける strTemp が
List(Of string)型 でなく String()型 なのはなぜでしょうか?


strTemp = CsvToList(line)(0)
この末尾の(0)の意味が分からないのですが、これは何でしょうか?
できれば CsvToList のソースも記述していただいてよろしいでしょうか?


私は .Net Framework 3.5 を使用していますが、
.Net Compact Framework Compact 3.5 は使用したことがないので、
私の知識が足りないだけかもしれません。
もしよろしければ教えてください。

投稿2016/01/26 10:37

twck

総合スコア314

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k47

2016/01/27 00:27

確認が遅くなりすみません。 質問の回答ですが、変数retはBoolean型でした。(最初Stringにしようと思っていてそのままに...。) 修正しておきます! CsvToListですが、参考にしたサイトでは、ArrayListを戻り値としていました。 ただ今回はListを使用しているのでその形に合わせようとした結果です。 CsvToListのソースを記載します。
twck

2016/01/28 01:37

CsvToListメソッドのArrayListをListに変更していることで バグが混入していますね。 戻り値が List(Of String()) ということは二次元配列みたいになっています。 データ中に LF を含む値があった場合は結果がおかしくなると思います。 結果に問題ないということは入力データの値の中に LF を含むものが ないということなのでしょう。 バグを直すにはCsvToListメソッドを以下のようにします。 誤:Public Function CsvToList(ByVal csvText As String) As List(Of String()) 正:Public Function CsvToList(ByVal csvText As String) As List(Of String) 誤:Dim csvRecords As New List(Of String()) 正:Dim csvRecords As New List(Of String) 誤:csvRecords.AddRange(csvFields) 正:csvRecords.Add(csvFields.ToArray) Import_M_MODELメソッドは以下のようにします。 誤:Dim strTemp As String() 正:Dim strTemp As List(Of String) 誤:strTemp = CsvToList(line)(0) 正:strTemp = CsvToList(line) ただしバグを直したからといって速度が速くなるわけではないのですが・・・
twck

2016/01/28 01:39

CsvToListメソッドをパフォーマンスチューニングするには。 値の中に LF を含むことがないのであれば、 CsvToListメソッドのなかで LF の判定をしている部分を削除したり、 csvFields から csvRecords に移し替える部分を削除できるでしょう。 列の数が決まっているのであれば List(Of String) じゃなくて String(n) を使用すれば多少は速くなると思います。 でもよくて1割くらいしか早くならないかな?
guest

0

最適化をする場合ボトルネックを探したほうがいいです。
パレートの法則で一部のコードがリソースを大量に消費しているはずです。

あたりをつけて計測してみてください。私がならとりあえず下記を計測します。
・全体時間
・ファイルの読み込み時間
・csvへのパースの時間
・構造体へのアロケート
・構造体の編集
・Listへの追加

私の予想では、読み込みか、Listへの追加が時間かかっているのではないかとおもっています。

読み込み時間が長い場合は、ファイルを分割して必要なファイルしか読まないように構成を考える必要があります。

Listへの追加の時間が問題なら、Listの初期値(何行分のメモリを確保するか)を大きくして、メモリ確保の時間をへらすか、件数を数えて配列することが必要です。

投稿2016/01/20 03:10

iwamoto_takaaki

総合スコア2883

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k47

2016/01/20 09:03

回答ありがとうございます。 そうですね。どの処理に時間がかかっているか調べてみることも手でした! ファイルの分割は要件的に難しいので、Listへの追加の時間を短くする方で検討してみます。
iwamoto_takaaki

2016/01/20 12:16

処理時間の計測は最適化では必須の作業です。 35秒の処理時間のうち、30秒がある処理にかかっていれば、ほかの処理は合計しても5秒となります。ほかのすべての処理をチューニングによって5分の1にしたとしても全体の5分の1も改善しないことになります。 そして処理時間というのは大体においてそうした偏りがあるものです。 まったく改善できないとしても、処理時間の計測をすることをお勧めします。
k47

2016/01/27 00:35

さらに追加のコメントありがとうございます。 すみません。気付かなくて...。 時間を計測してみました。(秒) 1つ目のファイル データ読込(readLine)の合計:2.0 CSV解析(CsvToList)の合計:6.9 構造体にセット(rowへの代入)の合計:3.0 リストに追加(list.add)の合計:0.06 2つ目のファイル データ読込(readLine)の合計:3.4 CSV解析(CsvToList)の合計:19.3 構造体にセット(rowへの代入)の合計:11.9 リストに追加(list.add)の合計:0.07 この数字を見る限り、CSV解析と構造体へのセットをどうにかしないと駄目みたいです。 調整してみます。
guest

0

ハンディターミナル側で処理しているなら、限られたリソースの中でやり繰りしていると思うので自ずと処理能力に限界があるのでは?
そもそもプアリソース下ではCompactとはいえ.NetFramework自体が激遅じゃないですか?

要件によりますが

  • 非同期化してできるだけUIスレッドを止めない→早くなるわけではない
  • CSVを取り込まずに別環境でDBに取り込んでおいてDBを渡してあげる→用途によりけりですが
  • ネイティブのバイナリを書く

あたりで落とし所を見つけるとか

投稿2016/01/19 15:37

dojikko

総合スコア3939

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k47

2016/01/20 00:17

回答ありがとうございます。 やはりそうなってしまいますか...。 今回の事案では、別環境にDBを用意できないので難しいですが、一番手っ取り早いのは この方法そうですね...。 参考になります。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問