我正在尝试使用FileStream.Seek快速跳转到一行并读取它。
然而,我没有得到正确的结果。我已经试着看了一段时间,但是不明白我做错了什么。
环境:
操作系统:Windows 7
框架:.NET 4.0
IDE:Visual C# 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);
}
}
}
}
}
}
我得到的输出:
文件行长度:15我的问题是以下输出:
行号 位置 行 ------- -------- ----------------- 3 30 0003|100!2500 4 15 0004|100!2500 5 45 0005|100!2500
行号 位置 行 ------- -------- ----------------- 3 30 0003|100!2500 5 30 0004|100!2500
行号 位置 行 ------- -------- ----------------- 5 30 0004|100!2500
行的输出应该是:0005|100!2500
我不明白为什么会出现这种情况。
我做错了什么吗?
有没有解决方法?
另外,是否有更快的方法可以使用类似于seek的东西来完成?
(我正在寻找基于代码的选项,而不是Oracle或SQL Server。为了论证的目的,让我们也说文件大小为1 GB。)
非常感谢任何帮助。
谢谢。
更新:
我在这里找到了4个很好的答案。非常感谢。
样本计时:
根据几次运行,以下是从最佳到最好的方法。即使是好的,也非常接近最佳。
在包含10K行、2.28 MB的文件中,我使用所有选项搜索了相同的5000个随机行。
- Seek4: 时间已过:00:00:00.0398530 ms -- Ritch Melton
- Seek3: 时间已过:00:00:00.0446072 ms -- Valentin Kuzub
- Seek1: 时间已过:00:00:00.0538210 ms -- Jake
- Seek2: 时间已过:00:00:00.0889589 ms -- bitxwise
下面是代码。保存代码后,您可以通过输入TestPaddedFileSeek.CallPaddedFileSeek();
来简单地调用它。您还必须指定命名空间和“using引用”。
/// <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: https://dev59.com/qVTTa4cB1Zd3GeqPw-tq
/// </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("");
}
}
}
`