Я пытаюсь работать с FileStream.Seek, чтобы быстро перейти к строке и прочитать ее.
Однако я не получаю правильных результатов.Я пытался посмотреть на это некоторое время и не могу понять, что я делаю неправильно.
Среда:
ОС: Windows 7
Framework: .NET 4.0
IDE: VisualC # Express 2010
Пример данных в расположении файла: C: \ Temp \ Temp.txt
0001|100!2500
0002|100!2500
0003|100!2500
0004|100!2500
0005|100!2500
0006|100!2500
0007|100!2500
0008|100!2500
0009|100!2500
0010|100!2500
Код:
class PaddedFileSearch
{
private int LineLength { get; set; }
private string FileName { get; set; }
public PaddedFileSearch()
{
FileName = @"C:\Temp\Temp.txt"; // This is a padded file. All lines are of the same length.
FindLineLength();
Debug.Print("File Line length: {0}", LineLength);
// TODO: This purely for testing. Move this code out.
SeekMethod(new int[] { 5, 3, 4 });
/* Expected Results:
* Line No Position Line
* ------- -------- -----------------
* 3 30 0003|100!2500
* 4 15 0004|100!2500
* 5 15 0005|100!2500 -- This was updated after the initial request.
*/
/* THIS DOES NOT GIVE THE EXPECTED RESULTS */
SeekMethod(new int[] { 5, 3 });
/* Expected Results:
* Line No Position Line
* ------- -------- -----------------
* 3 30 0003|100!2500
* 5 30 0005|100!2500
*/
}
private void FindLineLength()
{
string line;
// Add check for FileExists
using (StreamReader reader = new StreamReader(FileName))
{
if ((line = reader.ReadLine()) != null)
{
LineLength = line.Length + 2;
// The 2 is for NewLine(\r\n)
}
}
}
public void SeekMethod(int[] lineNos)
{
long position = 0;
string line = null;
Array.Sort(lineNos);
Debug.Print("");
Debug.Print("Line No\t\tPosition\t\tLine");
Debug.Print("-------\t\t--------\t\t-----------------");
using (FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
using (StreamReader reader = new StreamReader(fs))
{
foreach (int lineNo in lineNos)
{
position = (lineNo - 1) * LineLength - position;
fs.Seek(position, SeekOrigin.Current);
if ((line = reader.ReadLine()) != null)
{
Debug.Print("{0}\t\t\t{1}\t\t\t\t{2}", lineNo, position, line);
}
}
}
}
}
}
Вывод, который я получаю:
File Line length: 15
Line No Position Line
------- -------- -----------------
3 30 0003|100!2500
4 15 0004|100!2500
5 45 0005|100!2500
Line No Position Line
------- -------- -----------------
3 30 0003|100!2500
5 30 0004|100!2500
Моя проблема со следующим выводом:
Line No Position Line
------- -------- -----------------
5 30 0004|100!2500
вывод для строки должен быть: 0005 | 100! 2500
Я не понимаю, почему это происходит.
Я что-то не так делаю?Есть ли обходной путь?Кроме того, существуют ли более быстрые способы сделать это, используя что-то вроде поиска?
(я ищу варианты кода и НЕ Oracle или SQL Server. Для аргументации давайте такжескажем, что размер файла 1 ГБ.)
Любая помощь с благодарностью.
Спасибо.
ОБНОВЛЕНИЕ:
Я нашел 4 отличных ответа здесь.Большое спасибо.
Пример времени:
На основе нескольких прогонов ниже приведены методы от лучшего к хорошему.Даже хорошее очень близко к лучшему.
В файле, который содержит 10К строк, 2,28 МБ.Я искал те же 5000 случайных строк, используя все параметры.
- Seek4: прошедшее время: 00: 00: 00.0398530 мс - Ritch Melton
- Seek3: прошедшее время: 00:00: 00.0446072 мс - Валентин Кузуб
- Seek1: истекшее время: 00: 00: 00.0538210 мс - Джейк
- Seek2: истекшее время: 00: 00: 00.0889589 мс - побитовое
Ниже показан код.После сохранения кода вы можете просто позвонить, набрав TestPaddedFileSeek.CallPaddedFileSeek();
.Вам также нужно будет указать пространство имен и «использование ссылок».
`
/// <summary>
/// This class multiple options of reading a by line number in a padded file (all lines are the same length).
/// The idea is to quick jump to the file.
/// Details about the discussions is available at: http://stackoverflow.com/questions/5201414/having-a-problem-while-using-filestream-seek-in-c-solved
/// </summary>
class PaddedFileSeek
{
public FileInfo File {get; private set;}
public int LineLength { get; private set; }
#region Private methods
private static int FindLineLength(FileInfo fileInfo)
{
using (StreamReader reader = new StreamReader(fileInfo.FullName))
{
string line;
if ((line = reader.ReadLine()) != null)
{
int length = line.Length + 2; // The 2 is for NewLine(\r\n)
return length;
}
}
return 0;
}
private static void PrintHeader()
{
/*
Debug.Print("");
Debug.Print("Line No\t\tLine");
Debug.Print("-------\t\t--------------------------");
*/
}
private static void PrintLine(int lineNo, string line)
{
//Debug.Print("{0}\t\t\t{1}", lineNo, line);
}
private static void PrintElapsedTime(TimeSpan elapsed)
{
Debug.WriteLine("Time elapsed: {0} ms", elapsed);
}
#endregion
public PaddedFileSeek(FileInfo fileInfo)
{
// Possibly might have to check for FileExists
int length = FindLineLength(fileInfo);
//if (length == 0) throw new PaddedProgramException();
LineLength = length;
File = fileInfo;
}
public void CallAll(int[] lineNoArray, List<int> lineNoList)
{
Stopwatch sw = new Stopwatch();
#region Seek1
// Create new stopwatch
sw.Start();
Debug.Write("Seek1: ");
// Print Header
PrintHeader();
Seek1(lineNoArray);
// Stop timing
sw.Stop();
// Print Elapsed Time
PrintElapsedTime(sw.Elapsed);
sw.Reset();
#endregion
#region Seek2
// Create new stopwatch
sw.Start();
Debug.Write("Seek2: ");
// Print Header
PrintHeader();
Seek2(lineNoArray);
// Stop timing
sw.Stop();
// Print Elapsed Time
PrintElapsedTime(sw.Elapsed);
sw.Reset();
#endregion
#region Seek3
// Create new stopwatch
sw.Start();
Debug.Write("Seek3: ");
// Print Header
PrintHeader();
Seek3(lineNoArray);
// Stop timing
sw.Stop();
// Print Elapsed Time
PrintElapsedTime(sw.Elapsed);
sw.Reset();
#endregion
#region Seek4
// Create new stopwatch
sw.Start();
Debug.Write("Seek4: ");
// Print Header
PrintHeader();
Seek4(lineNoList);
// Stop timing
sw.Stop();
// Print Elapsed Time
PrintElapsedTime(sw.Elapsed);
sw.Reset();
#endregion
}
/// <summary>
/// Option by Jake
/// </summary>
/// <param name="lineNoArray"></param>
public void Seek1(int[] lineNoArray)
{
long position = 0;
string line = null;
Array.Sort(lineNoArray);
using (FileStream fs = new FileStream(File.FullName, FileMode.Open, FileAccess.Read, FileShare.None))
{
using (StreamReader reader = new StreamReader(fs))
{
foreach (int lineNo in lineNoArray)
{
position = (lineNo - 1) * LineLength;
fs.Seek(position, SeekOrigin.Begin);
if ((line = reader.ReadLine()) != null)
{
PrintLine(lineNo, line);
}
reader.DiscardBufferedData();
}
}
}
}
/// <summary>
/// option by bitxwise
/// </summary>
public void Seek2(int[] lineNoArray)
{
string line = null;
long step = 0;
Array.Sort(lineNoArray);
using (FileStream fs = new FileStream(File.FullName, FileMode.Open, FileAccess.Read, FileShare.None))
{
// using (StreamReader reader = new StreamReader(fs))
// If you put "using" here you will get WRONG results.
// I would like to understand why this is.
{
foreach (int lineNo in lineNoArray)
{
StreamReader reader = new StreamReader(fs);
step = (lineNo - 1) * LineLength - fs.Position;
fs.Position += step;
if ((line = reader.ReadLine()) != null)
{
PrintLine(lineNo, line);
}
}
}
}
}
/// <summary>
/// Option by Valentin Kuzub
/// </summary>
/// <param name="lineNoArray"></param>
#region Seek3
public void Seek3(int[] lineNoArray)
{
long position = 0; // totalPosition = 0;
string line = null;
int oldLineNo = 0;
Array.Sort(lineNoArray);
using (FileStream fs = new FileStream(File.FullName, FileMode.Open, FileAccess.Read, FileShare.None))
{
using (StreamReader reader = new StreamReader(fs))
{
foreach (int lineNo in lineNoArray)
{
position = (lineNo - oldLineNo - 1) * LineLength;
fs.Seek(position, SeekOrigin.Current);
line = ReadLine(fs, LineLength);
PrintLine(lineNo, line);
oldLineNo = lineNo;
}
}
}
}
#region Required Private methods
/// <summary>
/// Currently only used by Seek3
/// </summary>
/// <param name="stream"></param>
/// <param name="length"></param>
/// <returns></returns>
private static string ReadLine(FileStream stream, int length)
{
byte[] bytes = new byte[length];
stream.Read(bytes, 0, length);
return new string(Encoding.UTF8.GetChars(bytes));
}
#endregion
#endregion
/// <summary>
/// Option by Ritch Melton
/// </summary>
/// <param name="lineNoArray"></param>
#region Seek4
public void Seek4(List<int> lineNoList)
{
lineNoList.Sort();
using (var fs = new FileStream(File.FullName, FileMode.Open))
{
lineNoList.ForEach(ln => OutputData(fs, ln));
}
}
#region Required Private methods
private void OutputData(FileStream fs, int lineNumber)
{
var offset = (lineNumber - 1) * LineLength;
fs.Seek(offset, SeekOrigin.Begin);
var data = new byte[LineLength];
fs.Read(data, 0, LineLength);
var text = DecodeData(data);
PrintLine(lineNumber, text);
}
private static string DecodeData(byte[] data)
{
var encoding = new UTF8Encoding();
return encoding.GetString(data);
}
#endregion
#endregion
}
static class TestPaddedFileSeek
{
public static void CallPaddedFileSeek()
{
const int arrayLenght = 5000;
int[] lineNoArray = new int[arrayLenght];
List<int> lineNoList = new List<int>();
Random random = new Random();
int lineNo;
string fileName;
fileName = @"C:\Temp\Temp.txt";
PaddedFileSeek seeker = new PaddedFileSeek(new FileInfo(fileName));
for (int n = 0; n < 25; n++)
{
Debug.Print("Loop no: {0}", n + 1);
for (int i = 0; i < arrayLenght; i++)
{
lineNo = random.Next(1, arrayLenght);
lineNoArray[i] = lineNo;
lineNoList.Add(lineNo);
}
seeker.CallAll(lineNoArray, lineNoList);
lineNoList.Clear();
Debug.Print("");
}
}
}
`