<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; IO编程</title>
	<atom:link href="https://blog.gmem.cc/tag/io%e7%bc%96%e7%a8%8b/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 08:03:10 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Go语言IO编程</title>
		<link>https://blog.gmem.cc/go-io-programming</link>
		<comments>https://blog.gmem.cc/go-io-programming#comments</comments>
		<pubDate>Wed, 01 Feb 2017 09:18:17 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[IO编程]]></category>
		<category><![CDATA[文本处理]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=17909</guid>
		<description><![CDATA[<p>相关包 io 该包为IO操作原语提供了基本的接口，它包装了IO操作原语的实现（例如os包中的类型）。除非特别说明，调用者不能假设接口中的方法可以被并行调用。 常量变量 [crayon-69dd68fd3268d395627134/] 函数类型 函数 说明 Copy [crayon-69dd68fd32694339099053-i/] 从src读取数据并写入到dst，直到src的EOF。返回写入dst的字节数，操作成功则err为nil 实现方式：如果src实现了WriterTo接口，则调用src.WriteTo(dst)；如果dst实现了ReaderFrom接口，则调用dst.ReadFrom(src) 示例代码： [crayon-69dd68fd32697971868897/] CopyBuffer [crayon-69dd68fd32699569126194-i/] 和上面类似，只是拷贝时使用给定的缓冲区。如果buf为nil则自动创建一个缓冲区，如果buf长度为0则panic CopyN  [crayon-69dd68fd3269b913973705-i/] 拷贝N个字节，或知道遇到错误。返回拷贝的字节数和遇到的错误 ReadAtLeast  [crayon-69dd68fd3269d098109625-i/] <a class="read-more" href="https://blog.gmem.cc/go-io-programming">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/go-io-programming">Go语言IO编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">相关包</span></div>
<div class="blog_h2"><span class="graybg">io</span></div>
<p>该包为IO操作原语提供了基本的接口，它包装了IO操作原语的实现（例如os包中的类型）。除非特别说明，调用者不能假设接口中的方法可以被并行调用。</p>
<div class="blog_h3"><span class="graybg">常量变量</span></div>
<pre class="crayon-plain-tag">const (
        SeekStart   = 0 // 相对于文件的起始位置进行Seek
        SeekCurrent = 1 // 相对于当前读取位置进行Seek
        SeekEnd     = 2 // 相对于尾部进行Seek
)

// 错误定义
// 由Read函数返回，表示没有更多可读的数据。用于优雅的结束文件读取（读到尾部了）
var EOF = errors.New("EOF")
// 在关闭的管道上进行读写
var ErrClosedPipe = errors.New("io: read/write on closed pipe")
var ErrNoProgress = errors.New("multiple Read calls return no data or error")、
// 当提供的缓冲区不够存放读取到的数据时
var ErrShortBuffer = errors.New("short buffer")
// 当写操作所要求的字节数不足时
var ErrShortWrite = errors.New("short write")
// 在读取固定长度的块或者数据结构时，没有到预期的结尾位置即发生EOF
var ErrUnexpectedEOF = errors.New("unexpected EOF")</pre>
<div class="blog_h3"><span class="graybg">函数类型</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Copy</td>
<td>
<p><pre class="crayon-plain-tag">func Copy(dst Writer, src Reader) (written int64, err error)</pre></p>
<p>从src读取数据并写入到dst，直到src的EOF。返回写入dst的字节数，操作成功则err为nil</p>
<p>实现方式：如果src实现了WriterTo接口，则调用src.WriteTo(dst)；如果dst实现了ReaderFrom接口，则调用dst.ReadFrom(src)</p>
<p>示例代码：</p>
<p><pre class="crayon-plain-tag">sr := strings.NewReader("Read after me, please!\n")
n, err := io.Copy(os.Stderr, sr)
if err == nil {
    fmt.Printf("%v bytes read.\n", n)
}</pre>
</td>
</tr>
<tr>
<td>CopyBuffer</td>
<td>
<p><pre class="crayon-plain-tag">func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)</pre></p>
<p>和上面类似，只是拷贝时使用给定的缓冲区。如果buf为nil则自动创建一个缓冲区，如果buf长度为0则panic</p>
</td>
</tr>
<tr>
<td>CopyN </td>
<td>
<p><pre class="crayon-plain-tag">func CopyN(dst Writer, src Reader, n int64) (written int64, err error)</pre></p>
<p>拷贝N个字节，或知道遇到错误。返回拷贝的字节数和遇到的错误</p>
</td>
</tr>
<tr>
<td>ReadAtLeast </td>
<td>
<p><pre class="crayon-plain-tag">func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)</pre></p>
<p>从r读取至少min字节到buf，返回读取的字节数，如果没有读取到至少min字节数，则返回错误：</p>
<ol>
<li>如果一个字节也没有读到，返回EOF</li>
<li>如果读到一部分字节，返回ErrUnexpectedEOF</li>
</ol>
<p>示例：</p>
<p><pre class="crayon-plain-tag">r := bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 7, 8, 9})
buf := make([]byte, 5)
io.ReadAtLeast(r, buf, 3)
fmt.Printf("%v", buf)  // [0 1 2 3 4] 读满了切片容量 </pre>
</td>
</tr>
<tr>
<td>ReadFull</td>
<td>
<p><pre class="crayon-plain-tag">func ReadFull(r Reader, buf []byte) (n int, err error)</pre></p>
<p>读取r的全部内容到buf中，返回实际读取的字节数，如果：</p>
<ol>
<li>如果一个字节也没有读到，返回EOF</li>
<li>如果读到一部分字节（而非r的全部字节），返回ErrUnexpectedEOF</li>
</ol>
</td>
</tr>
<tr>
<td> WriteString</td>
<td>
<p><pre class="crayon-plain-tag">func WriteString(w Writer, s string) (n int, err error)</pre></p>
<p>将字符串s的内容写入到w中，如果w实现了WriteString方法则直接调用之，否则调用其Write一次</p>
</td>
</tr>
<tr>
<td>ByteReader</td>
<td>
<pre class="crayon-plain-tag">type ByteReader interface {
    ReadByte() (byte, error)
}</pre>
</td>
</tr>
<tr>
<td>ByteScanner </td>
<td>
<pre class="crayon-plain-tag">type ByteScanner interface {
    ByteReader
    // 导致下一次ReadByte返回和上一次ReadByte一样的内容
    UnreadByte() error
} </pre>
</td>
</tr>
<tr>
<td>ByteWriter</td>
<td>
<pre class="crayon-plain-tag">type ByteWriter interface {
    WriteByte(c byte) error
} </pre>
</td>
</tr>
<tr>
<td>Closer </td>
<td>
<pre class="crayon-plain-tag">type Closer interface {
    // 此接口包装基本的Close方法
    Close() error
}</pre>
</td>
</tr>
<tr>
<td>LimitedReader </td>
<td>
<pre class="crayon-plain-tag">type LimitedReader struct {
    R Reader // 此结构使用的Reader
    N int64  // 最大读取字节数。每次调用R后等会更新N，来反应还可以读取多少字节
}</pre></p>
<p>该结构上定义的方法：</p>
<p><pre class="crayon-plain-tag">func (l *LimitedReader) Read(p []byte) (n int, err error)</pre></p>
</td>
</tr>
<tr>
<td>Pipe</td>
<td>
<p><pre class="crayon-plain-tag">func Pipe() (*PipeReader, *PipeWriter)</pre></p>
<p>创建一个同步的、内存中的管道，用于连接Reader和Writer</p>
<p>每个PipeWriter.Write调用都会阻塞，直到一个或多个PipeReader.Read了其写入的全部数据，没有内部缓冲区</p>
<p>并行调用Read/Write是安全的</p>
</td>
</tr>
<tr>
<td>PipeReader</td>
<td>
<pre class="crayon-plain-tag">type PipeReader struct {}</pre></p>
<p> 表示管道能读的那一端。该结构上定义以下方法：</p>
<pre class="crayon-plain-tag">// 关闭管道的读端，后续写端的写操作会返回ErrClosedPipe
func (r *PipeReader) Close() error
// 关闭管道的读端，后续写端的写操作会返回该方法提供的err
func (r *PipeReader) CloseWithError(err error) error
// 读取数据
func (r *PipeReader) Read(data []byte) (n int, err error)</pre>
</td>
</tr>
<tr>
<td>PipeWriter</td>
<td>
<pre class="crayon-plain-tag">type PipeWriter struct {}</pre>
<p>表示管道能写的那一端。 该结构上定义以下方法：</p>
<p><pre class="crayon-plain-tag">func (w *PipeWriter) Close() error
func (w *PipeWriter) CloseWithError(err error) error
func (w *PipeWriter) Write(data []byte) (n int, err error) </pre>
</td>
</tr>
<tr>
<td>MultiReader</td>
<td>
<p><pre class="crayon-plain-tag">func MultiReader(readers ...Reader) Reader</pre></p>
<p>串联多个Reader，示例：</p>
<pre class="crayon-plain-tag">r1 := strings.NewReader("Hello")             
r2 := strings.NewReader(" World")            
rm := io.MultiReader(r1, r2)                 
buf := make([]byte, 20)                      
io.ReadFull(rm, buf)                         
fmt.Printf("%v", string(buf)) // Hello World</pre>
</td>
</tr>
<tr>
<td>TeeReader</td>
<td>
<p>&nbsp;
<p><pre class="crayon-plain-tag">func TeeReader(r Reader, w Writer) Reader</pre></p>
<p>返回一个Reader，它会把所有读取到的东西写入到一个Writer中，没有内部缓冲</p>
</td>
</tr>
<tr>
<td>ReaderAt </td>
<td>
<pre class="crayon-plain-tag">type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}</pre></p>
<p>包装ReadAt函数，支持从指定的偏移量开始读取。它从底层数据源偏移量off处开始，读取最多len(p)字节到缓冲区p中</p>
</td>
</tr>
<tr>
<td>ReaderFrom</td>
<td>
<pre class="crayon-plain-tag">type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}</pre></p>
<p> 包装ReadFrom函数，它从r读取数据，直到EOF或者错误</p>
</td>
</tr>
<tr>
<td>NewSectionReader</td>
<td>
<p><pre class="crayon-plain-tag">func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader</pre></p>
<p>返回一个Reader，它从off读取r，读取最多n字节</p>
</td>
</tr>
<tr>
<td>SectionReader</td>
<td>
<pre class="crayon-plain-tag">iotype SectionReader struct {
}
// 读取
func (s *SectionReader) Read(p []byte) (n int, err error)
// 从指定位置开始读取
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
// Seek到指定位置
func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
返回节段中包含的字节数
func (s *SectionReader) Size() int64</pre></p>
<p> 示例：</p>
<pre class="crayon-plain-tag">r := bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
s := io.NewSectionReader(r, 2, 3)
buf := make([]byte, 10)
s.Read(buf)
fmt.Printf("%v", buf) // [2 3 4 0 0 0 0 0 0 0] 最多读取3字节

s = io.NewSectionReader(r, 0, 10)
s.ReadAt(buf, 5)
fmt.Printf("%v", buf) // [5 6 7 8 9 0 0 0 0 0] </pre>
</td>
</tr>
<tr>
<td> RuneReader</td>
<td>
<pre class="crayon-plain-tag">type RuneReader interface {
    ReadRune() (r rune, size int, err error)
}</pre>
<p>包装RuneReader函数。ReadRune读取单个UTF-8字符，返回一个rune及其长度 </p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">bufio</span></div>
<p>该包实现带缓冲的IO功能。执行很多碎小的写操作会影响性能，因为每次写操作都对应了系统调用，过多的系统调用会加重CPU负担。类似磁盘这样的设备，处理块对齐的数据时性能更好，因此有必要为IO提供缓冲功能。</p>
<div class="blog_h3"><span class="graybg">缓冲写</span></div>
<p>简单的示例： </p>
<pre class="crayon-plain-tag">// 实现一个仿冒Writer，它能够打印写出的字节数量
type Writer int

func (*Writer) Write(p []byte) (n int, err error) {
    fmt.Printf("Bytes write: %v\n", len(p))
    return len(p), nil
}
func main() {
    w := new(Writer)
    w.Write([]byte{1}) // 一字节无缓冲写出
    w.Write([]byte{2})

    // 创建带缓冲的Writer，缓冲区大小5字节
    bw := bufio.NewWriterSize(w, 5)
    bw.Write([]byte{1, 2, 3}) // 不会写出，因为缓冲区没有满
    bw.Write([]byte{4, 5, 6}) // 写出，输出5
    fmt.Printf("Available size of buffer: %v", bw.Available())
    bw.Flush() // 写出，输出1
}</pre>
<div class="blog_h3"><span class="graybg">缓冲读 </span></div>
<pre class="crayon-plain-tag">r := strings.NewReader("既然选择了远方，就应该风雨兼程")
br := bufio.NewReaderSize(r, 18)
rune, size, err := br.ReadRune()
if err == nil {
    fmt.Printf("Character: %c, Size: %v\n", rune, size) // Character: 既, Size: 3
}
buf := make([]byte, 18)
br.Read(buf)
fmt.Printf("String: %v", string(buf)) // String: 然选择了远
br.Discard(6)
rune, _, _ = br.ReadRune()
fmt.Printf("%c", rune) // 就</pre>
<div class="blog_h3"><span class="graybg">缓冲扫描 </span></div>
<pre class="crayon-plain-tag">func main() {
    // 扫描器可以方便进行扫描，例如逐行读取、逐字符读取
    bs := bufio.NewScanner(strings.NewReader("既然选择了远方，就应该风雨兼程"))
    // 每读取一个字符即停止
    bs.Split(bufio.ScanRunes)
    for bs.Scan() {
        // 以字符串形式返回读取到的内容
        fmt.Print(bs.Text())
    }
}</pre>
<div class="blog_h2"><span class="graybg">ioutil</span></div>
<p>包含一些工具函数</p>
<pre class="crayon-plain-tag">// 读取全部内容
bytes, err := ioutil.ReadAll(strings.NewReader("Hello darkness my old friend"))
if err == nil {
    fmt.Printf("%c", bytes)
}

// 读取目录的全部条目
infos, err := ioutil.ReadDir("/etc/mysql")
for _, info := range infos {
    // 打印目录中的内容
    fmt.Println(info.Name())
}

// 读取文件的内容并返回
const FNAME = "/etc/mysql/my.cnf"
content, _ := ioutil.ReadFile(FNAME)
// 写入文件内容
ioutil.WriteFile(FNAME, content, 0777)

// 创建临时文件和目录
dirName, _ := ioutil.TempDir("", "go")
fmt.Println(dirName) // 输出目录/tmp/go154074736，注意随机后缀</pre>
<div class="blog_h2"><span class="graybg">fmt</span></div>
<p>该包提供类似C语言风格的printf、scanf</p>
<div class="blog_h3"><span class="graybg">格式化动词</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 10%; text-align: center;">动词</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>%v</td>
<td>值的默认形式</td>
</tr>
<tr>
<td>%#v</td>
<td>值的Go语法展现形式</td>
</tr>
<tr>
<td>%T</td>
<td>值类型的Go语法展现形式</td>
</tr>
<tr>
<td>%t</td>
<td>true或false</td>
</tr>
<tr>
<td>%%</td>
<td>转义%且不消耗参数</td>
</tr>
<tr>
<td colspan="2"><em>整数格式化</em></td>
</tr>
<tr>
<td>%b</td>
<td>二进制整数</td>
</tr>
<tr>
<td>%c</td>
<td>Unicode代码点对应的字符</td>
</tr>
<tr>
<td>%d</td>
<td>十进制</td>
</tr>
<tr>
<td>%o</td>
<td>八进制</td>
</tr>
<tr>
<td>%q</td>
<td>用引号包围的字符串形式，转义已经处理好</td>
</tr>
<tr>
<td>%x</td>
<td>十六进制a-f</td>
</tr>
<tr>
<td>%X</td>
<td>十六进制A-F</td>
</tr>
<tr>
<td>%U</td>
<td>Unicode格式</td>
</tr>
<tr>
<td colspan="2"><em>浮点数和复数</em></td>
</tr>
<tr>
<td>%e</td>
<td>科学计数法，使用e</td>
</tr>
<tr>
<td>%E</td>
<td>科学计数法，使用E</td>
</tr>
<tr>
<td>%f  %F</td>
<td>小数格式，示例：<pre class="crayon-plain-tag">fmt.Printf("%f",12.22) // 12.220000</pre> </td>
</tr>
<tr>
<td>%g  %G</td>
<td>对于大的指数使用%e %E，否则使用%f</td>
</tr>
<tr>
<td colspan="2"><em>字符串和字节切片</em></td>
</tr>
<tr>
<td>%s</td>
<td>未解释的原始字节</td>
</tr>
<tr>
<td>%q</td>
<td>引号包围的已经转义的字符串</td>
</tr>
<tr>
<td>%x  %X</td>
<td>十六进制表示，每个字节2字符</td>
</tr>
<tr>
<td colspan="2"><em>指针</em></td>
</tr>
<tr>
<td>%p</td>
<td>0x开头的十六进制</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">复合类型 </span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">格式化为</td>
</tr>
</thead>
<tbody>
<tr>
<td>结构</td>
<td>{field0 field1 ...}</td>
</tr>
<tr>
<td>数组和切片</td>
<td>[elem0 elem1 ...]</td>
</tr>
<tr>
<td>映射</td>
<td>map[key1:value1 key2:value2]</td>
</tr>
<tr>
<td>上述类型的指针</td>
<td>&amp;{}, &amp;[], &amp;map[]</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">指定宽度</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">示例</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>%f</td>
<td>默认宽度和精度</td>
</tr>
<tr>
<td>%9f</td>
<td>宽度为9，默认精度</td>
</tr>
<tr>
<td>%.2f</td>
<td>默认宽度，精度为2</td>
</tr>
<tr>
<td>%9.2f</td>
<td>宽度为9，精度为2</td>
</tr>
<tr>
<td>%9.f</td>
<td>宽度为9，精度为0</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">文件处理</span></div>
<div class="blog_h2"><span class="graybg">打开文件</span></div>
<pre class="crayon-plain-tag">// 打开并得到os.File对象
f, err := os.Open("/etc/rc.local")

// 关闭文件
defer f.Close()</pre>
<div class="blog_h2"><span class="graybg">读取文件</span></div>
<div class="blog_h3"><span class="graybg">读取到缓冲区</span></div>
<pre class="crayon-plain-tag"># 读取最多5字节到缓冲
b1 := make([]byte, 5)
# 返回实际读取的字节数
n1, err := f.Read(b1)</pre>
<div class="blog_h3"><span class="graybg">随机访问 </span></div>
<pre class="crayon-plain-tag">// 寻道到第6字节处，第二参数：0相对于文件头，1 相对于当前位置，2相对于文件尾
o2, err := f.Seek(6, 0)
b2 := make([]byte, 2) 
n2, err := f.Read(b2)

// Rewind
f.Seek(0, 0)</pre>
<div class="blog_h3"><span class="graybg">缓冲读</span></div>
<pre class="crayon-plain-tag">r4 := bufio.NewReader(f)
// 向前偷窥
b4, err := r4.Peek(5)</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/go-io-programming">Go语言IO编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/go-io-programming/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux IO编程</title>
		<link>https://blog.gmem.cc/linux-io-programming</link>
		<comments>https://blog.gmem.cc/linux-io-programming#comments</comments>
		<pubDate>Fri, 19 Jun 2009 05:55:48 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[IO编程]]></category>
		<category><![CDATA[Linux编程]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=6830</guid>
		<description><![CDATA[<p>文件访问 Linux系统中每个运行的进程，具有与之关联的文件描述符，通过这些描述符可以访问打开的文件或者设备。当一个进程打开时，一般会有三个已经打开的文件描述符：  描述符 说明  0 代表标准输入 1  代表标准输出  2 代表标准错误 系统调用 下表是与文件访问有关的系统调用 系统调用  说明  write 将缓冲区buf的前n个字节写入到文件描述符fildes关联的文件中，返回实际写入的字节数，如果底层设备对数据块长度比较敏感，返回值可能小于n。如果出现错误，返回-1，可以通过全局变量errno访问错误代码。 函数原型：[crayon-69dd68fd330e7183931023-i/]  示例代码： [crayon-69dd68fd330eb823639452/] read  从文件描述符fildes关联的文件中读取n字节数据，并把它们放入缓冲区中，返回实际读入的字节数。如果返回0表示未读入任何数据，已经到达文件结尾；如果返回-1表示出错。 函数原型：[crayon-69dd68fd330ed917885064-i/]  <a class="read-more" href="https://blog.gmem.cc/linux-io-programming">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/linux-io-programming">Linux IO编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">文件访问</span></div>
<p>Linux系统中每个运行的进程，具有与之关联的<span style="background-color: #c0c0c0;">文件描述符</span>，通过这些描述符可以<span style="background-color: #c0c0c0;">访问打开的文件或者设备</span>。当一个进程打开时，一般会有三个已经打开的文件描述符：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;"> 描述符</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>代表标准输入</td>
</tr>
<tr>
<td>1 </td>
<td>代表标准输出 </td>
</tr>
<tr>
<td>2</td>
<td>代表标准错误</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">系统调用</span></div>
<p>下表是与文件访问有关的系统调用</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;">系统调用 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>write</td>
<td>
<p>将缓冲区buf的前n个字节写入到文件描述符fildes关联的文件中，返回实际写入的字节数，如果底层设备对数据块长度比较敏感，返回值可能小于n。如果出现错误，返回-1，可以通过全局变量errno访问错误代码。</p>
<p>函数原型：<pre class="crayon-plain-tag">size_t write( int fildes, const void *buf, size_t nbytes );</pre> </p>
<p>示例代码：</p>
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
write( 1, "stdout", 6 );
write( 2, "stderr", 6 );</pre>
</td>
</tr>
<tr>
<td>read </td>
<td>
<p>从文件描述符fildes关联的文件中读取n字节数据，并把它们放入缓冲区中，返回实际读入的字节数。如果返回0表示未读入任何数据，已经到达文件结尾；如果返回-1表示出错。
<p>函数原型：<pre class="crayon-plain-tag">size_t read( int fildes, void *buf, size_t nbytes );</pre> </p>
<p>示例代码：</p>
<pre class="crayon-plain-tag">char buf[128];
int nread;
//阻塞的从标准输入中读取数据
nread = read( 0, buf, 16 );
write( 2, buf, nread );</pre>
</td>
</tr>
<tr>
<td>open</td>
<td>
<p>该函数用于创建一个新的文件描述符。该调用会返回一个文件描述符，其它进程即使打开同一个文件，也不会使用相同的描述符。如果两个进程同时写一个文件，那么写入的内容可能会相互覆盖（两个进程读写偏移量独立维护）。
<p>函数原型：</p>
<pre class="crayon-plain-tag">#include &lt;fcntl.h&gt;
//严格的说， 在遵守POSIX规范的系统上，下面两个头文件不需要包含
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
//oflags可以指定打开模式
//O_RDONLY   只读
//O_WRONLY   只写
//O_RDWR     读写
//oflags还可以按位或以下选项：
//O_APPEND   写入数据追加在文件结尾
//O_TRUNC    设置文件长度为0，丢弃已有内容
//O_CREAT    如果需要，根据mode中给出的模式创建文件
//O_EXCL     与O_CREAT联用，防止其它进程创建同一个文件
//           如果其它进程创建同一文件，该调用将失败
int open(const char *path, int oflags);
//mode在使用O_CREAT选项时有意义，代表文件的模式（权限）
//模式由S_开头的若干常量来表示，使用时按位或
// S_ISUID 04000 文件的 (set user-id on execution)位
// S_ISGID 02000 文件的 (set group-id on execution)位
// S_ISVTX 01000 文件的sticky 位
// S_IRUSR (S_IREAD) 00400 文件所有者具可读取权限
// S_IWUSR (S_IWRITE)00200 文件所有者具可写入权限
// S_IXUSR (S_IEXEC) 00100 文件所有者具可执行权限
// S_IRGRP 00040 用户组具可读取权限
// S_IWGRP 00020 用户组具可写入权限
// S_IXGRP 00010 用户组具可执行权限
// S_IROTH 00004 其他用户具可读取权限
// S_IWOTH 00002 其他用户具可写入权限
// S_IXOTH 00001 其他用户具可执行权限
int open(const char *path, int oflags, mode_t mode);</pre>
<p>open调用在成功时返回一个非负整数，作为文件描述符。失败时返回-1并设置全局变量errno。新文件描述符总是使用<span style="background-color: #c0c0c0;">未用描述符的最小值</span>。该特性被用于重定向：关闭标准输出后，再次调用open，则文件描述符1被重新使用，从而实现重定向。</p>
<p>任何运行中的程序能够打开的文件数量是有限制的，该限制在头文件limits.h中的OPEN_MAX定义，POSIX要求最少可以打开16个，在Linux中，该限制可以在运行时动态调整，因此OPEN_MAX是变量</p>
</td>
</tr>
<tr>
<td>close</td>
<td>
<p>可以终止文件描述符与对应文件的关联，文件描述符被释放，可以重新使用。成功关闭返回0，否则返回-1</p>
</td>
</tr>
<tr>
<td>lseek</td>
<td>
<p>可以对文件描述符的读写指针进行设置，即设置文件的读写位置。返回文件头到指针设置处的字节偏移量，失败时返回-1</p>
<p>函数原型：</p>
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
#include &lt;sys/types.h&gt;
//whence可以为：
// SEEK_SET: 表示偏移量为绝对位置
// SEEK_CUR: 表示偏移量是相对于当前位置的位置
// SEEK_END: 表示偏移量是相对于文件结尾的位置
off_t lseek( int fildes, off_t offset, int whence );</pre>
</td>
</tr>
<tr>
<td>fstat<br />stat<br />lstat</td>
<td>
<p>返回与打开文件描述符关联的文件的状态信息
<p>函数原型：</p>
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;sys/types.h&gt;
//该函数通过描述符定位文件
int fstat( int fildes, struct stat *buf );
//下面两个函数通过文件路径定位
int stat( const char *path, struct stat *buf );
//该函数与stat类似，但是如果文件是符号链接，它会
//返回符号链接本身的信息，而stat返回链接目标的信息
int lstat( const char *path, struct stat *buf );</pre>
</td>
</tr>
<tr>
<td>dup<br />dup2</td>
<td>
<p>用于复制文件描述符，可以让多个文件描述符指向同一文件，从而允许在文件的不同位置进行读写。通过管道在多个进程间进行通信时，这些调用很有用
<p>函数原型：</p>
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
//复制并返回新的文件描述符
int dup( int fildes );
//复制文件描述符为fildes2
int dup2( int fildes, int fildes2 );</pre>
</td>
</tr>
</tbody>
</table>
<div id="stdio" class="blog_h3"><span class="graybg">标准I/O库</span></div>
<p>标准I/O库（stdio）及其头文件stdio.h为底层I/O系统提供通用的对外接口，现在这个库已经是ANSI C标准库的一部分。标准I/O库提供多种复杂的函数用于格式化输出、扫描输入，还负责设备缓冲的处理。
<p>在标准I/O库中，操控文件的方式与系统调用类似，其中与底层文件描述符对应的是流（Stream），被实现为指向结构体FILE的指针。</p>
<p>在程序启动时，stdin、stdout、stderr这三个文件已经打开。</p>
<p>标准I/O库关于文件读写API的使用，参考<a href="/c-study-note#file-io">Linux I/O编程</a></p>
<div class="blog_h2"><span class="graybg">目录管理</span></div>
<p>相关系统调用或函数：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 系统调用</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>chmod</td>
<td>修改目录或者文件的模式（权限），是chmod命令的基础：<br />
<pre class="crayon-plain-tag">#include &lt;sys/stat.h&gt;
int chmod(const char *path, mode_t mode);</pre>
</td>
</tr>
<tr>
<td>chown</td>
<td>超级用户可以修改文件的所有者：<br />
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
int chown(const char *path, uid_t owner, gid_t group);
//支持数字格式的uid、gid，这些ID可以通过getuid、getgid获得</pre>
</td>
</tr>
<tr>
<td>unlink<br />link<br />symlink</td>
<td>
<p>unlink 可以用来删除文件，该调用减少文件的链接数，成功返回0否则返回-1，要求用户具有文件所属目录的写和执行权限。如果链接数为0且没有进程打开之，则文件就会被删除<br />link 创建指向已有文件的新链接，新的目录项由path2参数给出<br />symlink 创建指向已有文件的符号链接，该系统调用不会增加目标文件的链接数</p>
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);

//创建临时文件的技巧，创建后立即unlink
open(...);
unlink(...);
//该文件会在当前进程退出后自动清理删除</pre>
</td>
</tr>
<tr>
<td>mkdir<br />rmdir</td>
<td> 分别用于创建和删除目录，rmdir只有在目录为空的时候才能删除<br />
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
int mkdir(const char *path, mode_t mode);

#include &lt;unistd.h&gt;
int rmdir(const char *path);</pre>
</td>
</tr>
<tr>
<td>chdir</td>
<td>修改当前目录：<br />
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
int chdir(const char *path);</pre>
</td>
</tr>
<tr>
<td style="text-align: center;"><strong>函数</strong></td>
<td style="text-align: center;"><strong>说明</strong></td>
</tr>
<tr>
<td>getcwd</td>
<td>获取当前工作目录，如果缓冲区长度不够，返回NULL，否则返回缓冲区<br />
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
char *getcwd(char *buf, size_t size);</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">目录扫描</span></div>
<p>虽然可以将目录作为普通文件一样打开读写，但是这种方式不具有可移植性。Linux下用于目录访问的标准库函数定义在<span style="background-color: #c0c0c0;">dirent.h</span>头中，这些函数使用结构体<span style="background-color: #c0c0c0;">DIR</span>作为目录操作的基础，DIR*被称为<span style="background-color: #c0c0c0;">目录流</span>。这些函数中常用的包括：
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 函数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>opendir</td>
<td>
<p>opendir打开一个目录并建立目录流，如果失败，返回NULL，在底层，目录流打开目录的文件描述符</p>
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;dirent.h&gt;
DIR *opendir(const char *name);</pre>
</td>
</tr>
<tr>
<td>readdir</td>
<td>
<p>返回一个包含了下一个目录项信息的dirent指针，每次调用返回下一个，遇到目录结尾则返回NULL。如果在迭代期间，其他进程创建/删除了文件，那么readdir不能保证所有文件被列出。
<p>dirent指针包含两个数据项：</p>
<p><pre class="crayon-plain-tag">ino_t d_ino;/*文件Inode节点号*/char d_name[];/*文件的名字*/</pre> </p>
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;dirent.h&gt;
struct dirent *readdir(DIR *dirp);</pre>
</td>
</tr>
<tr>
<td>telldir</td>
<td>
<p>返回当前目录流迭代的位置，可以供后续seekdir调用进行重置
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;dirent.h&gt;
long int telldir(DIR *dirp);</pre>
</td>
</tr>
<tr>
<td>seekdir</td>
<td>
<p>设置目录流指针的位置
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;dirent.h&gt;
void seekdir(DIR *dirp, long int loc);</pre>
</td>
</tr>
<tr>
<td>closedir</td>
<td>
<p> 关闭目录流，并释放与之相关的资源
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;dirent.h&gt;
int closedir(DIR *dirp);</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">错误处理</span></div>
<p>很多文件I/O函数在失败后会设置外部变量errno的值，以提示失败的原因，程序必须在函数报告出错后立即检查errno，因为它可能被下一个函数调用覆盖。常见的错误代码包括：
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">错误代码</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td> EPERM</td>
<td>操作不允许</td>
</tr>
<tr>
<td> ENOENT</td>
<td>文件或目录不存在</td>
</tr>
<tr>
<td>EINTR</td>
<td>系统调用被中断</td>
</tr>
<tr>
<td>EIO</td>
<td>I/O错误</td>
</tr>
<tr>
<td>EBUSY</td>
<td>设备或资源忙</td>
</tr>
<tr>
<td>EEXIST</td>
<td>文件存在</td>
</tr>
<tr>
<td>EINVAL</td>
<td>无效参数</td>
</tr>
<tr>
<td>EMFILE</td>
<td>打开的文件过多</td>
</tr>
<tr>
<td>ENODEV</td>
<td>设备不存在</td>
</tr>
<tr>
<td>EISDIR</td>
<td>是一个目录</td>
</tr>
<tr>
<td>ENOTDIR</td>
<td>不是一个目录</td>
</tr>
</tbody>
</table>
<p>下面两个函数用于错误代码的处理：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">函数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>strerror</td>
<td>将整型错误代码转换为字符串表示：<br />
<pre class="crayon-plain-tag">#include &lt;string.h&gt;
char *strerror(int errnum);</pre>
</td>
</tr>
<tr>
<td>perror</td>
<td>将当前错误的字符串形式添加到缓冲区后面，在前缀后面添加冒号和空格：<br />
<pre class="crayon-plain-tag">#include &lt;stdio.h&gt;
void perror(const char *s);</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">其它主题</span></div>
<div class="blog_h3"><span class="graybg">fcntl系统调用</span></div>
<p>该系统调用允许对底层的文件描述符进行更加细致的控制：</p>
<pre class="crayon-plain-tag">#include &lt;fcntl.h&gt;
int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);

//cmd表示需要执行的动作：
//F_DUPFD 复制并返回一个新的文件描述符
//F_GETFD 返回fcntl.h中定义的文件描述符标记
//F_SETFD 设置文件描述符标记
//F_GETFL 获取文件状态标记、访问模式</pre>
<div class="blog_h3"><span class="graybg">mmap函数</span></div>
<p>内存映射。该函数用于创建一段供多个程序共享的内存，其中一个程序对其的修改，另外一个程序会立即看到。</p>
<p>该函数创建了一个指向了一段内存区域的指针，该内存区域与文件描述符指向的文件的内容关联：</p>
<pre class="crayon-plain-tag">#include &lt;sys/mman.h&gt;
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
//addr 起始内存地址，如果设置为0，则自动分配
//len 共享内存的长度
//prot设置共享内存的访问权限，以下位或：
//PROT_READ 该内存段可读
//PROT_WRITE 该内存段可写
//PROT_EXEC 该内存段可执行
//PROT_NONE 该内存段不能被访问
//flags控制程序对内存段的改变造成的影响：
//MAP_PRIVATE 内存段是私有的，修改是本地的，仅对当前进程有效
//MAP_SHARED 对内存段的修改被保留到磁盘
//MAP_FIXED 该段必须位于addr指定的地址
//fildes 共享内存关联的文件描述符
//off 共享内存访问文件内容的偏移值</pre>
<p>使用msync函数可以把内存段中的部分或者全部写回到被映射的文件中，或者从文件中读出：</p>
<pre class="crayon-plain-tag">#include &lt;sys/mman.h&gt;
int msync(void *addr, size_t len, int flags);
//addr 起始地址
//len 长度
//flags 标记位：
//MS_ASYNC 执行异步写
//MS_SYNC 执行同步写
//MS_INVALIDATE 把数据读回到内存段</pre>
<p>使用munmap函数可以释放内存段：</p>
<pre class="crayon-plain-tag">#include &lt;sys/mman.h&gt;
int munmap(void *addr, size_t len);</pre>
<div class="blog_h3"><span class="graybg">select系统调用</span></div>
<p>该系统调用在Linux下不仅仅可以用于网络I/O，普通文件I/O也被支持：</p>
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;sys/time.h&gt;
#include &lt;stdio.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;sys/ioctl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdlib.h&gt;
int main()
{
    char buffer[128];
    int result, nread;
    fd_set inputs, testfds;
    struct timeval timeout;
    FD_ZERO( &amp;inputs ); //初始化为空白文件描述符集
    FD_SET( 0, &amp;inputs ); //设置标准输入
    while ( 1 )
    {
        testfds = inputs;
        timeout.tv_sec = 2;
        timeout.tv_usec = 500000;
        //等待标准输入上具有数据可读，超时2.5秒
        result = select( FD_SETSIZE, &amp;testfds, ( fd_set * ) NULL, ( fd_set * ) NULL, &amp;timeout );
        switch ( result )
        {
            case 0 :
                printf( "超时\n" );
                break;
            case -1 :
                perror( "出错" );
                exit( 1 );
            default :
                if ( FD_ISSET( 0, &amp;testfds ) )
                {
                    //标准输入已经就绪，可以读取
                }
                break;
        }
    }
}</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/linux-io-programming">Linux IO编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/linux-io-programming/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
