Файл нь өгөгдөл хадгалах үндсэн нэгж юм. Файлыг байтуудын цуваа гэж үзэж болно. Файл нь ихэнхидээ диск төхөөрөмж дээр байрлана, гэхдээ зарим тохиолдолд терминал, хэвлэгч, соронзон тууз зэргийг файл гэж үздэг.
Файлын тухай судлахын өмнө Go хэлний io
пакетыг ойлгох хэрэгтэй. Энэ пакет нь цөөн тооны функцтэй, ихэвчлэн интерфэйсийн тодорхойлолт агуулсан байдаг. Хамгийн чухал хоёр интерфэйс нь Reader
болон Writer
. Reader
нь Read
методоор дамжуулах унших, Writer
нь Write
методоор дамжуулан бичих боломжоор хангана.
Эдгээр уншигч, бичигч нь бусад функцэд өргөн хэрэглэгддэг. Тухайлбал io
сангийн Copy()
функц нь Reader
, Writer
төрлийн параметрүүд хүлээн авдаг:
func Copy(dst Writer, src Reader) (written int64, err error)
Файл нээхийн тулд os.Open()
функцийг ашиглана. Файлаас өгөгдөл уншиж дэлгэц дээр харуулах кодыг доор харуулав:
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
// алдааг энд боловсруулна
return
}
defer file.Close()
// файлын хэмжээг авах
stat, err := file.Stat()
if err != nil {
return
}
// файлыг унших
bs := make([]byte, stat.Size())
_, err = file.Read(bs)
if err != nil {
return
}
str := string(bs)
fmt.Println(str)
}
Файлыг нээсний дараа defer file.Close()
зааврыг бичсэн байна. Энэ заавар нь функцийн ажиллагаа дуусмагц файлыг хаах болно.
Файл унших үйлдэл маш олон давтагддаг учраас арай хялбар арга бас бий. Тухайлбал ioutil.ReadFile()
функц нь файл уншилтыг ихээхэн хялбар болгодог.
package main
import (
"fmt"
"io/ioutil"
)
func main() {
bs, err := ioutil.ReadFile("test.txt")
if err != nil {
return
}
str := string(bs)
fmt.Println(str)
}
Файл үүсгэх:
package main
import "os"
func main() {
file, err := os.Create("test.txt")
if err != nil {
// алдааг энд боловсруулна
return
}
defer file.Close()
file.WriteString("test")
}
Хавтасны агуулгыг уншихдаа мөн os.Open()
функцийг ашиглана, гэхдээ файлын нэрний оронд хавтасын замыг заах хэрэгтэй. Нээсний дараа Readdir()
функцийг дуудна:
package main
import (
"fmt"
"os"
)
func main() {
dir, err := os.Open(".")
if err != nil {
return
}
defer dir.Close()
fileInfos, err := dir.Readdir(-1)
if err != nil {
return
}
for _, fi := range fileInfos {
fmt.Println(fi.Name())
}
}
Хавтас унших үед ихэвчлэн дэд хавтас руу орж рекурсивээр давтах шаардлага гардаг. Үүнийг хялбарчлах зорилгоор Walk()
функцийг ашиглаж болно.
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
// Walk функцэд дамжуулсан функц нь бүх хавтасын хувьд дуудагдана
filepath.Walk(".",
func(path string, info os.FileInfo, err error) error {
fmt.Println(path)
return nil
})
}
Файлд өгөгдлийг форматтай бичихэд fmt.Fprintf()
, fmt.Sprintf()
функцуудыг ашиглаж болно. Эдгээр функц нь бидний мэдэх fmt.Printf()
функцтэй ижил зарчмаар ажилладаг. Ялгаа нь эхний параметр файл эсвэл санах ойн буфер рүү форматлаж бичдэг. Файл руу бичих бол fmt.Fprintf
, тэмдэгт мөр буфер руу бичих бол fmt.Sprintf
функцийг ашиглана.
Жишээ нь:
fmt.Sprintf("%[2]d %[1]d\n", 11, 22)
fmt.Scanf
функцтэй төстэй мөн fmt.Fscanf
, fmt.Sscanf
хувилбарууд бий.
Буфертэй оролт/гаралт (Input/Output буюу товчоор I/O) нь шууд файл руу бичилт хийхгүй. Харин өгөгдөл нь бичихэд хангалттай том болтол буферт хадгалж байгаад бичилт хийдэг. Эсвэл зориудаар буферыг шавхаж бичиж болно.
Ихэнх тохиолдолд буфертэй I/O ашиглах хэрэгтэй. Буфергүй I/O хийх бүрт системийн дуудалт хийдэг. Үйлдлийн систем рүү хийж байгаа дуудалт бүр нь үнэтэй зүйл юм. Буфертэй I/O нь энэ дуудалтыг багасгана.
Төсөөлвөл, шалан дээр унагасан цаасуудыг цуглуулж байна гэж бодоё, үүнийг буфертэй болон буфергүй горимд хийж болно. Буфергүй горимд цаасуудыг нэг нэгээр нь авч ширээн дээр тавина гэсэн үг. Ингэвэл олон дахин доош тонгойх, дээш босох хэрэг гарна. Буфертэй горимд баруун гараараа цаасуудыг түүж, түүнийгээ зүүн гар (буфер) дээрээ хураана. Ингээд зүүн гар дүүрсэн буюу дааж чадах хязгаарт хүрсэн үед тэдгээрийг ширээн дээр тавина. Энэ тохиолдолд мэдээж цөөн удаа тонгойх, босох учраас зөв шийдэл болж байна.
Өгөгдлийг буферт хураах шаардлагатай үед bytes
сангийн Buffer
обектыг ашиглаж болно:
var buf bytes.Buffer
buf.Write([]byte("test"))
Буферээс буцаан өгөгдлөө гаргаж авах бол buf.Bytes()
гэж дуудна, тэмдэгт мөр гаргаж авах бол strings
хувиргагч ашиглаж болно. Эсвэл буферын WriteTo()
функцээр файл болон бусад төрлийн урсгал руу шууд бичиж болно.
buf.WriteTo(os.Stdout)
bufio
пакет нь буфертэй оролт, гаралт хийхэд зориулагдсан байдаг. Ө.х автоматаар цаанаа буфер ашигладаг гэсэн үг.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
w := bufio.NewWriter(os.Stdout)
fmt.Fprint(w, "Hello, ")
fmt.Fprint(w, "world!")
w.Flush() // шавхах
}
Буферт байгаа өгөгдөл дүүрэх эсвэл зориудаар Flush()
функц дуудах хүртэл өгөгдөл буферт хураагдана. Энэ нь эцсийн төхөөрөмж рүү бичилтийн тоог цөөлөх ач холбогдолтой.
Ихэнхи тохиолдолд буфер ашиглах нь зөв байдаг. Гэхдээ зарим тохиолдолд буфер ашиглахаас татгалзах хэрэгтэй. Жишээ нь програмын алдааг дэлгэцэнд мэдээлэхэд буфер ашиглах нь тохиромжгүй байж болно. Учир нь алдааг гарсан дариуд нь хэрэглэгч мэдэх хэрэгтэй шүү дээ. Эсвэл ойр ойрхон Flush()
функцийг дуудаж байх хэрэгтэй.