我正在试验CSVReader
,特别是this code sample,但我确信我做错了。他们的一些other coding samples显示了如何读取任何大小的流文件,这几乎是我想要的(只需要从磁盘读取),但我不知道是什么我可以创建的IDataReader
类型允许这样做。
我正在直接从磁盘读取,我试图通过一次读取太多数据来确保我永远不会耗尽内存。我不禁想到我应该能够使用BufferedFileReader
或类似的东西,我可以指向文件的位置并指定缓冲区大小,然后CsvDataReader
期望IDataReader
作为它的第一个参数,它可以使用它。请告诉我我的方法的错误,让我摆脱我的GetData
方法与它的任意文件分块机制,并帮助我解决这个基本问题。
private void button3_Click(object sender, EventArgs e)
{
totalNumberOfLinesInFile = GetNumberOfRecordsInFile();
totalNumberOfLinesProcessed = 0;
while (totalNumberOfLinesProcessed < totalNumberOfLinesInFile)
{
TextReader tr = GetData();
using (CsvDataReader csvData = new CsvDataReader(tr, '\t'))
{
csvData.Settings.HasHeaders = false;
csvData.Settings.SkipEmptyRecords = true;
csvData.Settings.TrimWhitespace = true;
for (int i = 0; i < 30; i++) // known number of columns for testing purposes
{
csvData.Columns.Add("varchar");
}
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(@"Data Source=XPDEVVM\XPDEV;Initial Catalog=MyTest;Integrated Security=SSPI;"))
{
bulkCopy.DestinationTableName = "work.test";
for (int i = 0; i < 30; i++)
{
bulkCopy.ColumnMappings.Add(i, i); // map First to first_name
}
bulkCopy.WriteToServer(csvData);
}
}
}
}
private TextReader GetData()
{
StringBuilder result = new StringBuilder();
int totalDataLines = 0;
using (FileStream fs = new FileStream(pathToFile, FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fs))
{
string line = string.Empty;
while ((line = sr.ReadLine()) != null)
{
if (line.StartsWith("D\t"))
{
totalDataLines++;
if (totalDataLines < 100000) // Arbitrary method of restricting how much data is read at once.
{
result.AppendLine(line);
}
}
}
}
}
totalNumberOfLinesProcessed += totalDataLines;
return new StringReader(result.ToString());
}
答案 0 :(得分:3)
实际上,您的代码正在从文件读取所有数据并保留在TextReader
(在内存中)。然后,您从TextReader
读取数据以保存服务器。
如果数据太大,TextReader
中的数据大小会导致内存不足。请尝试这种方式。
1)从文件中读取数据(每行)。
2)然后将每一行插入服务器。
内存不足问题将得到解决,因为只有处理时内存中的每条记录。
伪代码
begin tran
While (data = FilerReader.ReadLine())
{
insert into Table[col0,col1,etc] values (data[0], data[1], etc)
}
end tran
答案 1 :(得分:3)
可能不是您正在寻找的答案,但这是BULK INSERT的目的。
答案 2 :(得分:1)
我只是使用readLine方法添加BufferedFileReader,并以上述方式进行exatcly。
在这里基本了解resposnisbilties。
BufferedFileReader是从文件中读取数据的类(buffe wise) 应该还有一个LineReader。 CSVReader是一个用于读取数据的util类,假设它的格式正确。
SQlBulkCopy你是nowsay使用。
第二个选项
您可以直接转到数据库的导入工具。如果文件的格式是正确的,那么程序的漏洞点就是这个。那也会更快。
答案 3 :(得分:1)
我想你可能有一个数据大小的红鲱鱼。每次遇到这个问题时,都不是数据的大小,而是循环数据时创建的对象数量。
查看while循环,在方法button3_Click(object sender,EventArgs e)中向db添加记录:
TextReader tr = GetData();
using (CsvDataReader csvData = new CsvDataReader(tr, '\t'))
在这里,您每次迭代都会声明并实例化两个对象 - 这意味着对于您读取的每个文件块,您将实例化200,000个对象;垃圾收集器无法跟上。
为什么不在while循环之外声明对象?
TextReader tr = null;
CsvDataReader csvData = null;
这样,gc将有一半的机会。您可以通过对while循环进行基准测试来证明这种差异,毫无疑问,在创建了几千个对象后,性能会大幅下降。
答案 4 :(得分:0)
伪代码:
while (!EOF) {
while (chosenRecords.size() < WRITE_BUFFER_LIST_SIZE) {
MyRecord record = chooseOrSkipRecord(file.readln());
if (record != null) {
chosenRecords.add(record)
}
}
insertRecords(chosenRecords) // <== writes data and clears the list
}
WRITE_BUFFER_LIST_SIZE只是您设置的常量...更大意味着更大的批次,更小意味着更小的批次。大小为1是RBAR :)。
如果您的操作足够大以至于中途失败是一种现实的可能性,或者如果中途失败可能会花费一些非常微不足道的金钱,您可能还想写第二个表处理的记录总数到目前为止来自文件(包括您跳过的那些)作为同一交易的一部分,以便您可以在部分完成时从中途停下来。
答案 5 :(得分:0)
不是逐个读取csv行并逐个插入db,我建议读取一个块并将其插入数据库。重复此过程,直到读完整个文件。
您可以在内存中缓冲,一次说1000 csv行,然后将它们插入数据库。
int MAX_BUFFERED=1000;
int counter=0;
List<List<String>> bufferedRows= new ...
while (scanner.hasNext()){
List<String> rowEntries= getData(scanner.getLine())
bufferedRows.add(rowEntries);
if (counter==MAX_BUFFERED){
//INSERT INTO DATABASE
//append all contents to a string buffer and create your SQL INSERT statement
bufferedRows.clearAll();//remove data so it could be GCed when GC kicks in
}
}