Seek 方法必须配合 FileStream 使用
Seek是
FileStream类的实例方法,不是
File或
StreamReader/
StreamWriter的方法。直接用
File.ReadAllBytes或封装好的高层 API 无法调用
Seek—— 它们内部不暴露底层流位置控制能力。
常见错误是试图对
StreamWriter调用
Seek,结果编译报错:
'StreamWriter' does not contain a definition for 'Seek'。正确做法是显式创建
FileStream,并确保以可读写模式(
FileMode.Open+
FileAccess.ReadWrite)打开:
using var fs = new FileStream("data.bin", FileMode.Open, FileAccess.ReadWrite);
注意:如果文件只读打开(
FileAccess.Read),调用
Seek虽不报错,但后续
Write会抛出
NotSupportedException。
SeekOrigin 参数决定偏移基准点
Seek方法签名是
Seek(long offset, SeekOrigin origin),其中
origin控制“从哪开始算”:
SeekOrigin.Begin:从文件开头(位置 0)开始计算,
offset为正表示向后跳,常用于定位到指定字节地址
SeekOrigin.Current:从当前读写位置开始计算,
offset可正可负,适合前后微调
SeekOrigin.End:从文件末尾开始计算,
offset通常为负值(如
-4表示倒数第 4 字节),注意
offset = 0指向末尾之后一个位置(即追加点)
容易踩的坑:用
SeekOrigin.End时传入正数,会导致位置落在文件数据之外;读写前未检查
Position是否越界,可能引发
IOException或静默截断。
读写前务必校验当前位置和剩余长度
随机读写最易出错的环节是“以为能读/写,其实越界了”。例如想从位置 1000 读取 4 字节 int,但文件只有 1002 字节长,实际只能读 2 字节——
Read返回值可能小于预期,不检查就直接解析会出错。
安全做法是:
用fs.Length获取文件总长度 用
fs.Position查看当前偏移 读之前确认
fs.Position + bytesToRead写之前确认
fs.Position + bytesToWrite (若不想扩展文件)或提前用 <code>SetLength扩容
示例:安全读取固定长度结构体
if (fs.Position + sizeof(int) > fs.Length) throw new InvalidOperationException("Not enough data to read int");<br>var buffer = new byte[sizeof(int)];<br>int read = fs.Read(buffer, 0, buffer.Length);<br>if (read != buffer.Length) throw new IOException($"Short read: expected {buffer.Length}, got {read}");<br>int value = BitConverter.ToInt32(buffer, 0);
Seek 后的读写行为受缓冲区影响
默认
FileStream启用内部缓冲(
bufferSize=4096),这会导致两个现象:
Seek后立即
Read,可能触发整块缓冲区加载,但只返回你需要的部分;多次小
Seek+ 小读不会明显变慢 若用
Write修改已缓存的数据区域,缓冲区内容会延迟刷新到底层文件,
Flush或
Dispose才真正落盘
对实时性要求高的场景(如日志修补、内存映射替代方案),可关缓冲:
new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None, bufferSize: 1),但 I/O 开销会上升。一般情况保持默认即可。
复杂点在于:Seek 不是原子操作,多线程同时读写同一文件需自行加锁;另外,部分文件系统(如某些网络共享)对随机写支持不佳,
Seek后写入可能比顺序写慢得多——这点容易被忽略。
