Skip to content

File

Paket standar yang disediakan Go untuk penanganan file kira-kira beberapa berikut:

  • Paket os, bertanggung jawab atas implementasi spesifik interaksi sistem file OS
  • Paket io, lapisan abstraksi untuk IO baca-tulis
  • Paket fs, lapisan abstraksi untuk sistem file

Artikel ini akan menjelaskan cara melakukan penanganan file dasar dalam bahasa Go.

Membuka

Dua cara umum untuk membuka file adalah menggunakan dua fungsi yang disediakan paket os, fungsi Open mengembalikan pointer file dan error,

go
func Open(name string) (*File, error)

Fungsi OpenFile yang kedua dapat memberikan kontrol yang lebih granular, fungsi Open adalah enkapsulasi sederhana dari fungsi OpenFile.

go
func OpenFile(name string, flag int, perm FileMode) (*File, error)

Pertama perkenalkan cara penggunaan pertama, cukup sediakan nama file yang sesuai, kode sebagai berikut

go
func main() {
   file, err := os.Open("README.txt")
   fmt.Println(file, err)
}

Path pencarian file default adalah path folder go.mod proyek, karena di bawah proyek tidak ada file README.txt, tentu akan mengembalikan error.

<nil> open README.txt: The system cannot find the file specified.

Karena tipe error IO ada banyak, perlu manual menentukan apakah file ada, sama juga paket os juga menyediakan fungsi kemudahan untuk ini, kode yang dimodifikasi sebagai berikut

go
func main() {
  file, err := os.Open("README.txt")
  if os.IsNotExist(err) {
    fmt.Println("File tidak ada")
  } else if err != nil {
    fmt.Println("Akses file abnormal")
  } else {
    fmt.Println("Baca file berhasil", file)
  }
}

Jalankan lagi output sebagai berikut

File tidak ada

Sebenarnya fungsi pertama hanya membaca file read-only, tidak dapat dimodifikasi

go
func Open(name string) (*File, error) {
  return OpenFile(name, O_RDONLY, 0)
}

Melalui fungsi OpenFile dapat mengontrol lebih banyak detail, seperti memodifikasi file descriptor dan permission file. Tentang file descriptor, paket os menyediakan konstanta berikut untuk digunakan.

go
const (
   // Hanya baca, hanya tulis, baca tulis  tiga jenis harus menentukan satu
   O_RDONLY int = syscall.O_RDONLY // Buka file dengan mode hanya baca
   O_WRONLY int = syscall.O_WRONLY // Buka file dengan mode hanya tulis
   O_RDWR   int = syscall.O_RDWR   // Buka file dengan mode baca tulis
   // Nilai sisa digunakan untuk mengontrol perilaku
   O_APPEND int = syscall.O_APPEND // Saat menulis file, tambahkan data ke akhir file
   O_CREATE int = syscall.O_CREAT  // Buat file jika file tidak ada
   O_EXCL   int = syscall.O_EXCL   // Gunakan dengan O_CREATE, file harus tidak ada
   O_SYNC   int = syscall.O_SYNC   // Buka file dengan mode IO sinkron
   O_TRUNC  int = syscall.O_TRUNC  // Truncate file yang dapat ditulis saat membuka
)

Tentang permission file menyediakan konstanta berikut.

go
const (
   ModeDir        = fs.ModeDir        // d: direktori
   ModeAppend     = fs.ModeAppend     // a: hanya tambah
   ModeExclusive  = fs.ModeExclusive  // l: eksklusif
   ModeTemporary  = fs.ModeTemporary  // T: file sementara
   ModeSymlink    = fs.ModeSymlink    // L: symbolic link
   ModeDevice     = fs.ModeDevice     // D: file device
   ModeNamedPipe  = fs.ModeNamedPipe  // p: named pipe (FIFO)
   ModeSocket     = fs.ModeSocket     // S: Unix domain socket
   ModeSetuid     = fs.ModeSetuid     // u: setuid
   ModeSetgid     = fs.ModeSetgid     // g: setgid
   ModeCharDevice = fs.ModeCharDevice // c: Unix character device, prasyarat ModeDevice diset
   ModeSticky     = fs.ModeSticky     // t: sticky bit
   ModeIrregular  = fs.ModeIrregular  // ?: file tidak teratur

   // Masker bit tipe. Untuk file reguler, tidak ada yang diset.
   ModeType = fs.ModeType

   ModePerm = fs.ModePerm // Bit permission Unix, 0o777
)

Berikut adalah contoh kode membuka file dengan mode baca tulis, permission 0666,表示 semua orang dapat membaca dan menulis file ini, dan akan otomatis dibuat saat tidak ada.

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
  if os.IsNotExist(err) {
    fmt.Println("File tidak ada")
  } else if err != nil {
    fmt.Println("Akses file abnormal")
  } else {
    fmt.Println("Buka file berhasil", file.Name())
    file.Close()
  }
}

Output sebagai berikut

Buka file berhasil README.txt

Jika hanya ingin mendapatkan beberapa informasi file ini, tidak ingin membaca file ini, dapat menggunakan fungsi os.Stat() untuk operasi, contoh kode sebagai berikut

go
func main() {
  fileInfo, err := os.Stat("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(fmt.Sprintf("%+v", fileInfo))
  }
}

Output sebagai berikut

&{name:README.txt FileAttributes:32 CreationTime:{LowDateTime:3603459389 HighDateTime:31016791} LastAccessTime:{LowDateTime:3603459389 HighDateTime:31016791} LastWriteTime:{LowDateTime:3603459389 HighDateTime:31016791} FileSizeHigh
:0 FileSizeLow:0 Reserved0:0 filetype:0 Mutex:{state:0 sema:0} path:README.txt vol:0 idxhi:0 idxlo:0 appendNameToPath:false}

WARNING

Setelah membuka file selalu ingat untuk menutup file ini, biasanya operasi tutup akan diletakkan di statement defer

go
defer file.Close()

Membaca

Setelah berhasil membuka file, dapat melakukan operasi baca. Tentang operasi baca file, tipe *os.File menyediakan beberapa metode publik berikut

go
// Baca file ke dalam slice byte yang传入
func (f *File) Read(b []byte) (n int, err error)

// Dibandingkan yang pertama dapat membaca dari offset yang ditentukan
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

Sebagian besar kasus yang pertama lebih banyak digunakan. Untuk metode pertama, perlu menulis logika sendiri untuk melakukan ekspansi dinamis slice saat membaca, kode sebagai berikut

go
func ReadFile(file *os.File) ([]byte, error) {
  buffer := make([]byte, 0, 512)
  for {
    // Saat kapasitas tidak cukup
    if len(buffer) == cap(buffer) {
      // Ekspansi
      buffer = append(buffer, 0)[:len(buffer)]
    }
    // Lanjut baca file
    offset, err := file.Read(buffer[len(buffer):cap(buffer)])
    // Masukkan data yang sudah ditulis ke slice
    buffer = buffer[:len(buffer)+offset]
    // Saat terjadi error
    if err != nil {
      if errors.Is(err, io.EOF) {
        err = nil
      }
      return buffer, err
    }
  }
}

Logika sisa sebagai berikut

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Akses file abnormal")
   } else {
      fmt.Println("Buka file berhasil", file.Name())
      bytes, err := ReadFile(file)
      if err != nil {
         fmt.Println("Baca file abnormal", err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}

Output adalah

Buka file berhasil README.txt
hello world!

Selain itu, dapat juga menggunakan dua fungsi kemudahan untuk membaca file, masing-masing adalah fungsi ReadFile di paket os, dan fungsi ReadAll di paket io. Untuk os.ReadFile, cukup sediakan path file, sedangkan untuk io.ReadAll, perlu menyediakan implementasi tipe io.Reader.

os.ReadFile

go
func ReadFile(name string) ([]byte, error)

Contoh penggunaan sebagai berikut

go
func main() {
  bytes, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(string(bytes))
  }
}

Output sebagai berikut

hello world!

io.ReadAll

go
func ReadAll(r Reader) ([]byte, error)

Contoh penggunaan sebagai berikut

go
func main() {

   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Akses file abnormal")
   } else {
      fmt.Println("Buka file berhasil", file.Name())
      bytes, err := io.ReadAll(file)
      if err != nil {
         fmt.Println(err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}
Buka file berhasil README.txt
hello world!

Struct os.File menyediakan beberapa metode berikut untuk menulis data

go
// Tulis slice byte
func (f *File) Write(b []byte) (n int, err error)

// Tulis string
func (f *File) WriteString(s string) (n int, err error)

// Mulai menulis dari posisi yang ditentukan, saat dibuka dengan mode os.O_APPEND akan mengembalikan error
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

Jika ingin menulis data ke file, harus membuka dengan mode O_WRONLY atau O_RDWR, jika tidak tidak akan berhasil menulis file. Berikut adalah contoh membuka file dengan mode os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, dan permission 0666 untuk menulis data yang ditentukan

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println("Akses file abnormal")
  } else {
    fmt.Println("Buka file berhasil", file.Name())
    for i := 0; i < 5; i++ {
      offset, err := file.WriteString("hello world!\n")
      if err != nil {
        fmt.Println(offset, err)
      }
    }
    fmt.Println(file.Close())
  }
}

Karena membuka file dengan mode os.O_APPEND, saat menulis file akan menambahkan data ke akhir file, setelah eksekusi selesai konten file sebagai berikut

txt
hello world!
hello world!
hello world!
hello world!
hello world!

Menulis slice byte ke file juga operasi serupa, tidak akan diulang lagi. Untuk operasi menulis file paket standar juga menyediakan fungsi kemudahan, masing-masing adalah os.WriteFile dan io.WriteString

os.WriteFile

go
func WriteFile(name string, data []byte, perm FileMode) error

Contoh penggunaan sebagai berikut

go
func main() {
  err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
  if err != nil {
    fmt.Println(err)
  }
}

Saat ini konten file sebagai berikut

txt
hello world!

io.WriteString

go
func WriteString(w Writer, s string) (n int, err error)

Contoh penggunaan sebagai berikut

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
   if err != nil {
      fmt.Println("Akses file abnormal")
   } else {
      fmt.Println("Buka file berhasil", file.Name())
      for i := 0; i < 5; i++ {
         offset, err := io.WriteString(file, "hello world!\n")
         if err != nil {
            fmt.Println(offset, err)
         }
      }
      fmt.Println(file.Close())
   }
}
hello world!
hello world!
hello world!
hello world!
hello world!

Fungsi os.Create digunakan untuk membuat file, pada dasarnya juga enkapsulasi dari OpenFile.

go
func Create(name string) (*File, error) {
   return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

WARNING

Saat membuat file, jika direktori parent tidak ada, akan gagal membuat dan mengembalikan error.

Menyalin

Untuk menyalin file, perlu membuka dua file sekaligus, metode pertama adalah membaca data dari file asli, lalu menulis ke file target, contoh kode sebagai berikut

go
func main() {
    // Baca data dari file asli
  data, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
    return
  }
    // Tulis file target
  err = os.WriteFile("README(1).txt", data, 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Salin berhasil")
  }
}

*os.File.ReadFrom

Metode lain adalah menggunakan metode ReadFrom yang disediakan os.File, saat membuka file, satu hanya baca, satu hanya tulis.

go
func (f *File) ReadFrom(r io.Reader) (n int64, err error)

Contoh penggunaan sebagai berikut

go
func main() {
  // Buka file asli dengan hanya baca
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Buka file salinan dengan hanya tulis
  target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer target.Close()
  // Baca data dari file asli, lalu tulis ke file salinan
  offset, err := target.ReadFrom(origin)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println("Salin file berhasil", offset)
}

Cara menyalin ini perlu membaca semua konten source file ke memori terlebih dahulu, lalu menulis ke file target, tidak disarankan melakukan ini saat file sangat besar.

io.Copy

Metode lain adalah menggunakan fungsi io.Copy, fungsi ini baca sambil tulis, pertama baca konten ke buffer, lalu tulis ke file target, ukuran buffer default adalah 32KB.

go
func Copy(dst Writer, src Reader) (written int64, err error)

Contoh penggunaan sebagai berikut

go
func main() {
  // Buka file asli dengan hanya baca
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Buka file salinan dengan hanya tulis
  target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer target.Close()
  // Salin
  written, err := io.Copy(target, origin)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(written)
  }
}

Anda juga dapat menggunakan io.CopyBuffer untuk menentukan ukuran buffer.

Mengganti Nama

Mengganti nama juga dapat dipahami sebagai memindahkan file, akan menggunakan fungsi Rename di paket os.

go
func Rename(oldpath, newpath string) error

Contoh sebagai berikut

go
func main() {
  err := os.Rename("README.txt", "readme.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Ganti nama berhasil")
  }
}

Fungsi ini untuk folder juga efek yang sama.

Menghapus

Operasi hapus jauh lebih sederhana dibandingkan operasi lain, hanya akan menggunakan dua fungsi di paket os

go
// Hapus file tunggal atau direktori kosong, saat direktori tidak kosong akan mengembalikan error
func Remove(name string) error

// Hapus semua file dan direktori di direktori yang ditentukan termasuk sub direktori dan sub file
func RemoveAll(path string) error

Penggunaannya sangat sederhana, berikut adalah contoh menghapus direktori

go
func main() {
  // Hapus semua file dan sub direktori di direktori saat ini
  err := os.RemoveAll(".")
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("Hapus berhasil")
  }
}

Berikut adalah contoh menghapus file tunggal

go
func main() {
  // Hapus semua file dan sub direktori di direktori saat ini
  err := os.Remove("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Hapus berhasil")
  }
}

Flush

Fungsi os.Sync adalah enkapsulasi dari system call底层 Fsync, digunakan untuk menerapkan IO yang di-cache di sistem operasi ke disk

go
func main() {
  create, err := os.Create("test.txt")
  if err != nil {
    panic(err)
  }
  defer create.Close()

  _, err = create.Write([]byte("hello"))
  if err != nil {
    panic(err)
  }

    // Flush ke disk
  if err := create.Sync();err != nil {
    return
  }
}

Folder

Banyak operasi folder mirip dengan operasi file

Membaca

Untuk folder, ada dua cara membuka,

os.ReadDir

Cara pertama adalah menggunakan fungsi os.ReadDir

go
func ReadDir(name string) ([]DirEntry, error)
go
func main() {
   // Direktori saat ini
   dir, err := os.ReadDir(".")
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dir {
         fmt.Println(entry.Name())
      }
   }
}

*os.File.ReadDir

Cara kedua adalah menggunakan fungsi *os.File.ReadDir, os.ReadDir pada dasarnya hanya enkapsulasi sederhana dari *os.File.ReadDir.

go
// Saat n < 0, baca semua konten di folder
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
func main() {
   // Direktori saat ini
   dir, err := os.Open(".")
   if err != nil {
      fmt.Println(err)
   }
   defer dir.Close()
   dirs, err := dir.ReadDir(-1)
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dirs {
         fmt.Println(entry.Name())
      }
   }
}

Membuat

Operasi membuat folder akan menggunakan dua fungsi di paket os

go
// Buat direktori dengan nama yang ditentukan menggunakan permission yang ditentukan
func Mkdir(name string, perm FileMode) error

// Dibandingkan yang pertama fungsi ini akan membuat semua direktori parent yang diperlukan
func MkdirAll(path string, perm FileMode) error

Contoh sebagai berikut

go
func main() {
  err := os.Mkdir("src", 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Buat berhasil")
  }
}

Menyalin

Kita dapat menulis fungsi sendiri untuk recursively traversing seluruh folder, tetapi paket standar filepath sudah menyediakan fungsi dengan fungsi serupa, oleh karena itu dapat langsung digunakan, contoh kode sederhana untuk menyalin folder sebagai berikut.

go
func CopyDir(src, dst string) error {
    // Periksa status folder source
  _, err := os.Stat(src)
  if err != nil {
    return err
  }

  return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
    if err != nil {
      return err
    }

        // Hitung path relatif
    rel, err := filepath.Rel(src, path)
    if err != nil {
      return err
    }

        // Gabungkan path target
    destpath := filepath.Join(dst, rel)

        // Buat folder
    var dirpath string
    var mode os.FileMode = 0755
    if info.IsDir() {
      dirpath = destpath
      mode = info.Mode()
    } else if info.Mode().IsRegular() {
      dirpath = filepath.Dir(destpath)
    }

    if err := os.MkdirAll(dirpath, mode); err != nil {
      return err
    }

        // Buat file
    if info.Mode().IsRegular() {
      srcfile, err := os.Open(path)
      if err != nil {
        return err
      }
            // Selalu ingat untuk menutup file
      defer srcfile.Close()
      destfile, err := os.OpenFile(destpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
      if err != nil {
        return err
      }
      defer destfile.Close()

            // Salin konten file
      if _, err := io.Copy(destfile, srcfile); err != nil {
        return err
      }
      return nil
    }

    return nil
  })
}

filepath.Walk akan recursively traversing seluruh folder, dalam proses, bertemu folder buat folder, bertemu file buat file baru dan salin, kode dibandingkan menyalin file sedikit banyak tetapi tidak terlalu kompleks.

Golang by www.golangdev.cn edit