C #에서 스트림을 사용하여 큰 텍스트 파일 읽기
응용 프로그램의 스크립트 편집기에로드되는 대용량 파일을 처리하는 방법을 알아내는 멋진 작업이 있습니다 ( 빠른 매크로를위한 내부 제품의 VBA 와 같습니다 ). 대부분의 파일은 약 300-400KB로 잘로드됩니다. 그러나 100MB를 초과하면 프로세스가 어렵습니다 (예상대로).
무슨 일이 일어나고 있는지 파일을 읽고 RichTextBox로 밀어 넣은 다음 탐색합니다.이 부분에 대해 너무 걱정하지 마십시오.
초기 코드를 작성한 개발자는 단순히 StreamReader를 사용하여
[Reader].ReadToEnd()
완료하는 데 시간이 꽤 걸릴 수 있습니다.
내 작업은이 코드를 분할하고, 청크 단위로 버퍼로 읽고, 취소 옵션이있는 진행률 표시 줄을 표시하는 것입니다.
몇 가지 가정 :
- 대부분의 파일은 30-40MB입니다.
- 파일의 내용은 텍스트 (바이너리 아님)이고 일부는 Unix 형식이고 일부는 DOS입니다.
- 내용이 검색되면 어떤 터미네이터가 사용되는지 알아냅니다.
- 리치 텍스트 상자에서 렌더링하는 데 걸리는 시간이로드되면 아무도 걱정하지 않습니다. 텍스트의 초기로드 일뿐입니다.
이제 질문 :
- StreamReader를 사용한 다음 Length 속성 (ProgressMax)을 확인하고 설정된 버퍼 크기에 대해 Read를 실행 하고 백그라운드 작업자 내부에서 WHILST 를 반복 하여 기본 UI 스레드를 차단하지 않도록 할 수 있습니까? 그런 다음 stringbuilder가 완료되면 메인 스레드로 반환합니다.
- 내용은 StringBuilder로 이동합니다. 길이를 사용할 수있는 경우 스트림 크기로 StringBuilder를 초기화 할 수 있습니까?
(전문적인 의견으로는) 좋은 아이디어입니까? 나는 과거에 Streams에서 콘텐츠를 읽는 데 몇 가지 문제가있었습니다. 항상 마지막 몇 바이트 또는 무언가를 놓칠 것이기 때문입니다. 그러나 이것이 사실이라면 다른 질문을 할 것입니다.
다음과 같이 BufferedStream을 사용하여 읽기 속도를 향상시킬 수 있습니다.
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
string line;
while ((line = sr.ReadLine()) != null)
{
}
}
2013 년 3 월 업데이트
나는 최근에 1GB 크기의 텍스트 파일 (여기에 포함 된 파일보다 훨씬 큼)을 읽고 처리 (텍스트 검색)하기위한 코드를 작성했으며 생산자 / 소비자 패턴을 사용하여 상당한 성능 향상을 달성했습니다. 생산자 작업은를 사용하여 텍스트 줄을 읽고 BufferedStream
검색을 수행 한 별도의 소비자 작업에 전달했습니다.
이 패턴을 빠르게 코딩하는 데 매우 적합한 TPL Dataflow를 배울 기회로 사용했습니다.
BufferedStream이 더 빠른 이유
버퍼는 데이터를 캐시하는 데 사용되는 메모리의 바이트 블록이므로 운영 체제에 대한 호출 수를 줄입니다. 버퍼는 읽기 및 쓰기 성능을 향상시킵니다. 버퍼는 읽기 또는 쓰기에 사용할 수 있지만 동시에 둘 다 사용할 수는 없습니다. BufferedStream의 Read 및 Write 메서드는 자동으로 버퍼를 유지합니다.
2014 년 12 월 업데이트 : 마일리지가 다를 수 있음
주석에 따라 FileStream은 내부적 으로 BufferedStream을 사용해야합니다 . 이 답변이 처음 제공되었을 때 BufferedStream을 추가하여 상당한 성능 향상을 측정했습니다. 당시 저는 32 비트 플랫폼에서 .NET 3.x를 대상으로했습니다. 현재 64 비트 플랫폼에서 .NET 4.5를 대상으로했지만 개선되지 않았습니다.
관련
생성 된 대용량 CSV 파일을 ASP.Net MVC 작업에서 응답 스트림으로 스트리밍하는 것이 매우 느린 경우를 발견했습니다. 이 인스턴스에서 BufferedStream을 추가하면 성능이 100 배 향상되었습니다. 자세한 내용은 버퍼링되지 않은 출력 매우 느림을 참조하십시오.
이 웹 사이트 에서 성능 및 벤치 마크 통계 를 읽으면 텍스트 파일 을 읽는 가장 빠른 방법 (읽기, 쓰기 및 처리가 모두 다르기 때문에)이 다음 코드 스 니펫임을 알 수 있습니다.
using (StreamReader sr = File.OpenText(fileName))
{
string s = String.Empty;
while ((s = sr.ReadLine()) != null)
{
//do your stuff here
}
}
약 9 개의 다른 방법이 모두 벤치마킹되었지만 다른 독자들이 언급 한 것처럼 버퍼링 된 리더 를 수행하는 경우에도 대부분의 경우 앞서 나온 것 같습니다 .
You say you have been asked to show a progress bar while a large file is loading. Is that because the users genuinely want to see the exact % of file loading, or just because they want visual feedback that something is happening?
If the latter is true, then the solution becomes much simpler. Just do reader.ReadToEnd()
on a background thread, and display a marquee-type progress bar instead of a proper one.
I raise this point because in my experience this is often the case. When you are writing a data processing program, then users will definitely be interested in a % complete figure, but for simple-but-slow UI updates, they are more likely to just want to know that the computer hasn't crashed. :-)
For binary files, the fastest way of reading them I have found is this.
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(file);
MemoryMappedViewStream mms = mmf.CreateViewStream();
using (BinaryReader b = new BinaryReader(mms))
{
}
In my tests it's hundreds of times faster.
Use a background worker and read only a limited number of lines. Read more only when the user scrolls.
And try to never use ReadToEnd(). It's one of the functions that you think "why did they make it?"; it's a script kiddies' helper that goes fine with small things, but as you see, it sucks for large files...
Those guys telling you to use StringBuilder need to read the MSDN more often:
Performance Considerations
The Concat and AppendFormat methods both concatenate new data to an existing String or StringBuilder object. A String object concatenation operation always creates a new object from the existing string and the new data. A StringBuilder object maintains a buffer to accommodate the concatenation of new data. New data is appended to the end of the buffer if room is available; otherwise, a new, larger buffer is allocated, data from the original buffer is copied to the new buffer, then the new data is appended to the new buffer. The performance of a concatenation operation for a String or StringBuilder object depends on how often a memory allocation occurs.
A String concatenation operation always allocates memory, whereas a StringBuilder concatenation operation only allocates memory if the StringBuilder object buffer is too small to accommodate the new data. Consequently, the String class is preferable for a concatenation operation if a fixed number of String objects are concatenated. In that case, the individual concatenation operations might even be combined into a single operation by the compiler. A StringBuilder object is preferable for a concatenation operation if an arbitrary number of strings are concatenated; for example, if a loop concatenates a random number of strings of user input.
That means huge allocation of memory, what becomes large use of swap files system, that simulates sections of your hard disk drive to act like the RAM memory, but a hard disk drive is very slow.
The StringBuilder option looks fine for who use the system as a mono-user, but when you have two or more users reading large files at the same time, you have a problem.
This should be enough to get you started.
class Program
{
static void Main(String[] args)
{
const int bufferSize = 1024;
var sb = new StringBuilder();
var buffer = new Char[bufferSize];
var length = 0L;
var totalRead = 0L;
var count = bufferSize;
using (var sr = new StreamReader(@"C:\Temp\file.txt"))
{
length = sr.BaseStream.Length;
while (count > 0)
{
count = sr.Read(buffer, 0, bufferSize);
sb.Append(buffer, 0, count);
totalRead += count;
}
}
Console.ReadKey();
}
}
Have a look at the following code snippet. You have mentioned Most files will be 30-40 MB
. This claims to read 180 MB in 1.4 seconds on an Intel Quad Core:
private int _bufferSize = 16384;
private void ReadFile(string filename)
{
StringBuilder stringBuilder = new StringBuilder();
FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
using (StreamReader streamReader = new StreamReader(fileStream))
{
char[] fileContents = new char[_bufferSize];
int charsRead = streamReader.Read(fileContents, 0, _bufferSize);
// Can't do much with 0 bytes
if (charsRead == 0)
throw new Exception("File is 0 bytes");
while (charsRead > 0)
{
stringBuilder.Append(fileContents);
charsRead = streamReader.Read(fileContents, 0, _bufferSize);
}
}
}
You might be better off to use memory-mapped files handling here.. The memory mapped file support will be around in .NET 4 (I think...I heard that through someone else talking about it), hence this wrapper which uses p/invokes to do the same job..
Edit: See here on the MSDN for how it works, here's the blog entry indicating how it is done in the upcoming .NET 4 when it comes out as release. The link I have given earlier on is a wrapper around the pinvoke to achieve this. You can map the entire file into memory, and view it like a sliding window when scrolling through the file.
An iterator might be perfect for this type of work:
public static IEnumerable<int> LoadFileWithProgress(string filename, StringBuilder stringData)
{
const int charBufferSize = 4096;
using (FileStream fs = File.OpenRead(filename))
{
using (BinaryReader br = new BinaryReader(fs))
{
long length = fs.Length;
int numberOfChunks = Convert.ToInt32((length / charBufferSize)) + 1;
double iter = 100 / Convert.ToDouble(numberOfChunks);
double currentIter = 0;
yield return Convert.ToInt32(currentIter);
while (true)
{
char[] buffer = br.ReadChars(charBufferSize);
if (buffer.Length == 0) break;
stringData.Append(buffer);
currentIter += iter;
yield return Convert.ToInt32(currentIter);
}
}
}
}
You can call it using the following:
string filename = "C:\\myfile.txt";
StringBuilder sb = new StringBuilder();
foreach (int progress in LoadFileWithProgress(filename, sb))
{
// Update your progress counter here!
}
string fileData = sb.ToString();
As the file is loaded, the iterator will return the progress number from 0 to 100, which you can use to update your progress bar. Once the loop has finished, the StringBuilder will contain the contents of the text file.
Also, because you want text, we can just use BinaryReader to read in characters, which will ensure that your buffers line up correctly when reading any multi-byte characters (UTF-8, UTF-16, etc.).
This is all done without using background tasks, threads, or complex custom state machines.
All excellent answers! however, for someone looking for an answer, these appear to be somewhat incomplete.
As a standard String can only of Size X, 2Gb to 4Gb depending on your configuration, these answers do not really fulfil the OP's question. One method is to work with a List of Strings:
List<string> Words = new List<string>();
using (StreamReader sr = new StreamReader(@"C:\Temp\file.txt"))
{
string line = string.Empty;
while ((line = sr.ReadLine()) != null)
{
Words.Add(line);
}
}
Some may want to Tokenise and split the line when processing. The String List now can contain very large volumes of Text.
The bellow link contains the code that read a piece of file easily:
참고URL : https://stackoverflow.com/questions/2161895/reading-large-text-files-with-streams-in-c-sharp
'program story' 카테고리의 다른 글
Reader를 InputStream으로, Writer를 OutputStream으로 변환하는 방법은 무엇입니까? (0) | 2020.09.11 |
---|---|
패키지에있는 Python 모듈의 이름을 나열하는 표준 방법이 있습니까? (0) | 2020.09.11 |
안드로이드에서 확인 버튼을 클릭하면 URL 열기 (0) | 2020.09.11 |
MongoDB 집계 프레임 워크 일치 또는 (0) | 2020.09.11 |
잘못된 종류의 값을 보유한 키에 대한 WRONGTYPE 작업 PHP (0) | 2020.09.11 |