Updated:

14 minute read

개요

파일 입/출력(File I/O)은 데이터를 영구적으로 저장하고 읽어오는 필수적인 기능입니다. Go는 os, io, bufio 패키지를 통해 강력하고 효율적인 파일 처리 기능을 제공합니다.

주요 패키지

  • os: 파일 시스템 기본 작업 (생성, 열기, 삭제 등)
  • io: 입출력 인터페이스 및 유틸리티
  • bufio: 버퍼링된 입출력
  • path/filepath: 경로 처리
  • io/ioutil: 간단한 I/O 유틸리티 (Go 1.16부터 deprecated, os로 통합)


파일 열기와 닫기

파일 열기

os.Open (읽기 전용)

package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Open("test.txt")
	if err != nil {
		fmt.Println("파일 열기 실패:", err)
		return
	}
	defer file.Close()  // 함수 종료 시 자동 닫기
	
	fmt.Println("파일 열기 성공")
}

os.OpenFile (플래그 지정)

package main

import (
	"fmt"
	"os"
)

func main() {
	// 읽기/쓰기 모드로 열기, 없으면 생성
	file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		fmt.Println("파일 열기 실패:", err)
		return
	}
	defer file.Close()
	
	fmt.Println("파일 열기 성공")
}

파일 열기 플래그

플래그 설명
os.O_RDONLY 읽기 전용
os.O_WRONLY 쓰기 전용
os.O_RDWR 읽기/쓰기
os.O_CREATE 파일이 없으면 생성
os.O_TRUNC 파일을 열 때 내용 삭제
os.O_APPEND 파일 끝에 추가
os.O_EXCL O_CREATE와 함께 사용, 파일이 이미 있으면 에러

파일 권한 (Permission)

0644  // rw-r--r-- (소유자: 읽기/쓰기, 그룹/기타: 읽기)
0755  // rwxr-xr-x (소유자: 읽기/쓰기/실행, 그룹/기타: 읽기/실행)
0600  // rw------- (소유자만 읽기/쓰기)


파일 생성

os.Create

package main

import (
	"fmt"
	"os"
)

func main() {
	// 파일 생성 (이미 있으면 내용 삭제)
	file, err := os.Create("new_file.txt")
	if err != nil {
		fmt.Println("파일 생성 실패:", err)
		return
	}
	defer file.Close()
	
	fmt.Println("파일 생성 성공")
}

os.OpenFile로 생성

// 파일이 없을 때만 생성 (이미 있으면 에러)
file, err := os.OpenFile("new_file.txt", 
	os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
if err != nil {
	fmt.Println("파일이 이미 존재하거나 생성 실패:", err)
	return
}
defer file.Close()


파일 읽기

1. os.ReadFile (간단한 방법, 전체 읽기)

Go 1.16+에서 권장되는 방법

package main

import (
	"fmt"
	"os"
)

func main() {
	// 파일 전체를 바이트 슬라이스로 읽기
	data, err := os.ReadFile("test.txt")
	if err != nil {
		fmt.Println("파일 읽기 실패:", err)
		return
	}
	
	fmt.Println("파일 내용:")
	fmt.Println(string(data))
}

2. File.Read (저수준 읽기)

package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Open("test.txt")
	if err != nil {
		fmt.Println("파일 열기 실패:", err)
		return
	}
	defer file.Close()
	
	// 버퍼 생성
	buffer := make([]byte, 100)
	
	// 읽기
	n, err := file.Read(buffer)
	if err != nil {
		fmt.Println("읽기 실패:", err)
		return
	}
	
	fmt.Printf("읽은 바이트 수: %d\n", n)
	fmt.Printf("내용: %s\n", string(buffer[:n]))
}

3. bufio.Scanner (라인별 읽기)

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	file, err := os.Open("test.txt")
	if err != nil {
		fmt.Println("파일 열기 실패:", err)
		return
	}
	defer file.Close()
	
	scanner := bufio.NewScanner(file)
	lineNum := 1
	
	// 한 줄씩 읽기
	for scanner.Scan() {
		line := scanner.Text()
		fmt.Printf("%d: %s\n", lineNum, line)
		lineNum++
	}
	
	// 스캔 에러 확인
	if err := scanner.Err(); err != nil {
		fmt.Println("스캔 에러:", err)
	}
}

4. bufio.Reader (버퍼링된 읽기)

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	file, err := os.Open("test.txt")
	if err != nil {
		fmt.Println("파일 열기 실패:", err)
		return
	}
	defer file.Close()
	
	reader := bufio.NewReader(file)
	
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				// 마지막 줄 처리 (줄바꿈 없이 끝날 수 있음)
				if len(line) > 0 {
					fmt.Print(line)
				}
				break
			}
			fmt.Println("읽기 에러:", err)
			return
		}
		fmt.Print(line)
	}
}

5. io.ReadAll (전체 읽기)

package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	file, err := os.Open("test.txt")
	if err != nil {
		fmt.Println("파일 열기 실패:", err)
		return
	}
	defer file.Close()
	
	data, err := io.ReadAll(file)
	if err != nil {
		fmt.Println("읽기 실패:", err)
		return
	}
	
	fmt.Println(string(data))
}

읽기 방법 비교

방법 장점 단점 사용 상황
os.ReadFile 가장 간단 큰 파일 시 메모리 부담 작은 파일 전체 읽기
File.Read 세밀한 제어 코드 복잡 바이너리 파일, 부분 읽기
bufio.Scanner 라인별 처리 용이 라인 크기 제한 텍스트 파일 라인별 처리
bufio.Reader 효율적, 유연 약간 복잡 대용량 파일, 스트리밍
io.ReadAll 간단 메모리 부담 작은 파일, Reader 읽기


파일 쓰기

1. os.WriteFile (간단한 방법)

package main

import (
	"fmt"
	"os"
)

func main() {
	data := []byte("Hello, World!\n안녕하세요!\n")
	
	// 파일에 쓰기 (덮어쓰기)
	err := os.WriteFile("output.txt", data, 0644)
	if err != nil {
		fmt.Println("쓰기 실패:", err)
		return
	}
	
	fmt.Println("파일 쓰기 성공")
}

2. File.Write (저수준 쓰기)

package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Create("output.txt")
	if err != nil {
		fmt.Println("파일 생성 실패:", err)
		return
	}
	defer file.Close()
	
	data := []byte("Hello, Go!\n")
	n, err := file.Write(data)
	if err != nil {
		fmt.Println("쓰기 실패:", err)
		return
	}
	
	fmt.Printf("%d 바이트 쓰기 완료\n", n)
}

3. File.WriteString

package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Create("output.txt")
	if err != nil {
		fmt.Println("파일 생성 실패:", err)
		return
	}
	defer file.Close()
	
	lines := []string{
		"첫 번째 줄\n",
		"두 번째 줄\n",
		"세 번째 줄\n",
	}
	
	for _, line := range lines {
		_, err := file.WriteString(line)
		if err != nil {
			fmt.Println("쓰기 실패:", err)
			return
		}
	}
	
	fmt.Println("쓰기 완료")
}

4. bufio.Writer (버퍼링된 쓰기)

대량 데이터 쓰기 시 성능 향상

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	file, err := os.Create("output.txt")
	if err != nil {
		fmt.Println("파일 생성 실패:", err)
		return
	}
	defer file.Close()
	
	writer := bufio.NewWriter(file)
	
	// 여러 줄 쓰기
	for i := 1; i <= 1000; i++ {
		fmt.Fprintf(writer, "라인 %d\n", i)
	}
	
	// 버퍼 비우기 (중요!)
	err = writer.Flush()
	if err != nil {
		fmt.Println("플러시 실패:", err)
		return
	}
	
	fmt.Println("쓰기 완료")
}

5. 파일에 추가 (Append)

package main

import (
	"fmt"
	"os"
)

func main() {
	// 추가 모드로 열기
	file, err := os.OpenFile("output.txt", 
		os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("파일 열기 실패:", err)
		return
	}
	defer file.Close()
	
	// 파일 끝에 추가
	_, err = file.WriteString("추가된 내용\n")
	if err != nil {
		fmt.Println("쓰기 실패:", err)
		return
	}
	
	fmt.Println("추가 완료")
}

6. fmt.Fprintf로 포맷 쓰기

package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Create("output.txt")
	if err != nil {
		fmt.Println("파일 생성 실패:", err)
		return
	}
	defer file.Close()
	
	name := "Alice"
	age := 30
	score := 95.5
	
	fmt.Fprintf(file, "이름: %s\n", name)
	fmt.Fprintf(file, "나이: %d\n", age)
	fmt.Fprintf(file, "점수: %.1f\n", score)
	
	fmt.Println("쓰기 완료")
}


파일 정보 확인

os.Stat (파일 정보)

package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	fileInfo, err := os.Stat("test.txt")
	if err != nil {
		fmt.Println("파일 정보 확인 실패:", err)
		return
	}
	
	fmt.Println("파일명:", fileInfo.Name())
	fmt.Println("크기:", fileInfo.Size(), "바이트")
	fmt.Println("권한:", fileInfo.Mode())
	fmt.Println("수정 시간:", fileInfo.ModTime().Format(time.RFC3339))
	fmt.Println("디렉토리 여부:", fileInfo.IsDir())
}

파일 존재 확인

package main

import (
	"fmt"
	"os"
)

func fileExists(filename string) bool {
	_, err := os.Stat(filename)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false  // 다른 에러 (권한 등)
}

func main() {
	if fileExists("test.txt") {
		fmt.Println("파일이 존재합니다.")
	} else {
		fmt.Println("파일이 존재하지 않습니다.")
	}
}


파일 삭제 및 이름 변경

파일 삭제

package main

import (
	"fmt"
	"os"
)

func main() {
	err := os.Remove("test.txt")
	if err != nil {
		fmt.Println("삭제 실패:", err)
		return
	}
	
	fmt.Println("파일 삭제 완료")
}

파일 이름 변경/이동

package main

import (
	"fmt"
	"os"
)

func main() {
	// 파일 이름 변경
	err := os.Rename("old.txt", "new.txt")
	if err != nil {
		fmt.Println("이름 변경 실패:", err)
		return
	}
	
	fmt.Println("이름 변경 완료")
	
	// 파일 이동 (다른 디렉토리로)
	err = os.Rename("file.txt", "subdir/file.txt")
	if err != nil {
		fmt.Println("이동 실패:", err)
		return
	}
}


디렉토리 작업

디렉토리 생성

package main

import (
	"fmt"
	"os"
)

func main() {
	// 단일 디렉토리 생성
	err := os.Mkdir("testdir", 0755)
	if err != nil {
		fmt.Println("디렉토리 생성 실패:", err)
		return
	}
	
	// 중첩 디렉토리 생성 (mkdir -p)
	err = os.MkdirAll("parent/child/grandchild", 0755)
	if err != nil {
		fmt.Println("디렉토리 생성 실패:", err)
		return
	}
	
	fmt.Println("디렉토리 생성 완료")
}

디렉토리 읽기

package main

import (
	"fmt"
	"os"
)

func main() {
	// 디렉토리 엔트리 읽기
	entries, err := os.ReadDir(".")
	if err != nil {
		fmt.Println("디렉토리 읽기 실패:", err)
		return
	}
	
	fmt.Println("현재 디렉토리 내용:")
	for _, entry := range entries {
		if entry.IsDir() {
			fmt.Printf("[DIR]  %s\n", entry.Name())
		} else {
			fmt.Printf("[FILE] %s\n", entry.Name())
		}
	}
}

디렉토리 삭제

package main

import (
	"fmt"
	"os"
)

func main() {
	// 빈 디렉토리 삭제
	err := os.Remove("testdir")
	if err != nil {
		fmt.Println("삭제 실패:", err)
		return
	}
	
	// 디렉토리와 모든 하위 항목 삭제 (rm -rf)
	err = os.RemoveAll("parent")
	if err != nil {
		fmt.Println("삭제 실패:", err)
		return
	}
	
	fmt.Println("디렉토리 삭제 완료")
}


경로 처리

path/filepath 패키지

package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	// 경로 결합
	path := filepath.Join("dir", "subdir", "file.txt")
	fmt.Println("경로:", path)  // dir/subdir/file.txt (또는 dir\subdir\file.txt)
	
	// 절대 경로 변환
	absPath, _ := filepath.Abs("test.txt")
	fmt.Println("절대 경로:", absPath)
	
	// 파일명 추출
	filename := filepath.Base("/path/to/file.txt")
	fmt.Println("파일명:", filename)  // file.txt
	
	// 디렉토리 추출
	dir := filepath.Dir("/path/to/file.txt")
	fmt.Println("디렉토리:", dir)  // /path/to
	
	// 확장자 추출
	ext := filepath.Ext("file.txt")
	fmt.Println("확장자:", ext)  // .txt
	
	// 경로 분리
	dir, file := filepath.Split("/path/to/file.txt")
	fmt.Println("디렉토리:", dir)  // /path/to/
	fmt.Println("파일:", file)     // file.txt
}

경로 패턴 매칭

package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	// 글로브 패턴으로 파일 찾기
	matches, err := filepath.Glob("*.txt")
	if err != nil {
		fmt.Println("에러:", err)
		return
	}
	
	fmt.Println("*.txt 파일들:")
	for _, match := range matches {
		fmt.Println(match)
	}
	
	// 재귀적으로 파일 찾기
	err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && filepath.Ext(path) == ".go" {
			fmt.Println("Go 파일:", path)
		}
		return nil
	})
}


실용 예제

예제 1: 파일 복사

package main

import (
	"fmt"
	"io"
	"os"
)

func copyFile(src, dst string) error {
	// 원본 파일 열기
	sourceFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sourceFile.Close()
	
	// 대상 파일 생성
	destFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destFile.Close()
	
	// 복사
	_, err = io.Copy(destFile, sourceFile)
	if err != nil {
		return err
	}
	
	// 버퍼 플러시
	return destFile.Sync()
}

func main() {
	err := copyFile("source.txt", "destination.txt")
	if err != nil {
		fmt.Println("복사 실패:", err)
		return
	}
	
	fmt.Println("파일 복사 완료")
}

예제 2: CSV 파일 읽기/쓰기

package main

import (
	"encoding/csv"
	"fmt"
	"os"
)

func writeCSV() error {
	file, err := os.Create("data.csv")
	if err != nil {
		return err
	}
	defer file.Close()
	
	writer := csv.NewWriter(file)
	defer writer.Flush()
	
	// 헤더
	writer.Write([]string{"이름", "나이", "도시"})
	
	// 데이터
	records := [][]string{
		{"Alice", "30", "Seoul"},
		{"Bob", "25", "Busan"},
		{"Charlie", "35", "Incheon"},
	}
	
	for _, record := range records {
		if err := writer.Write(record); err != nil {
			return err
		}
	}
	
	return nil
}

func readCSV() error {
	file, err := os.Open("data.csv")
	if err != nil {
		return err
	}
	defer file.Close()
	
	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		return err
	}
	
	for i, record := range records {
		fmt.Printf("Row %d: %v\n", i, record)
	}
	
	return nil
}

func main() {
	if err := writeCSV(); err != nil {
		fmt.Println("쓰기 에러:", err)
		return
	}
	
	if err := readCSV(); err != nil {
		fmt.Println("읽기 에러:", err)
		return
	}
}

예제 3: JSON 파일 처리

package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type Person struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email"`
}

func writeJSON() error {
	people := []Person{
		{"Alice", 30, "alice@example.com"},
		{"Bob", 25, "bob@example.com"},
	}
	
	file, err := os.Create("people.json")
	if err != nil {
		return err
	}
	defer file.Close()
	
	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")  // 보기 좋게 포맷
	
	return encoder.Encode(people)
}

func readJSON() error {
	file, err := os.Open("people.json")
	if err != nil {
		return err
	}
	defer file.Close()
	
	var people []Person
	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&people); err != nil {
		return err
	}
	
	for _, p := range people {
		fmt.Printf("%s (%d세): %s\n", p.Name, p.Age, p.Email)
	}
	
	return nil
}

func main() {
	if err := writeJSON(); err != nil {
		fmt.Println("JSON 쓰기 에러:", err)
		return
	}
	
	if err := readJSON(); err != nil {
		fmt.Println("JSON 읽기 에러:", err)
		return
	}
}

예제 4: 로그 파일 작성

package main

import (
	"fmt"
	"os"
	"time"
)

type Logger struct {
	file *os.File
}

func NewLogger(filename string) (*Logger, error) {
	file, err := os.OpenFile(filename, 
		os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return nil, err
	}
	
	return &Logger{file: file}, nil
}

func (l *Logger) Log(message string) error {
	timestamp := time.Now().Format("2006-01-02 15:04:05")
	logEntry := fmt.Sprintf("[%s] %s\n", timestamp, message)
	
	_, err := l.file.WriteString(logEntry)
	return err
}

func (l *Logger) Close() error {
	return l.file.Close()
}

func main() {
	logger, err := NewLogger("app.log")
	if err != nil {
		fmt.Println("로거 생성 실패:", err)
		return
	}
	defer logger.Close()
	
	logger.Log("애플리케이션 시작")
	logger.Log("사용자 로그인: Alice")
	logger.Log("데이터 처리 완료")
	logger.Log("애플리케이션 종료")
	
	fmt.Println("로그 작성 완료")
}

예제 5: 대용량 파일 처리

package main

import (
	"bufio"
	"fmt"
	"os"
)

func processLargeFile(filename string) error {
	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()
	
	scanner := bufio.NewScanner(file)
	
	// 큰 라인을 위한 버퍼 크기 증가
	const maxCapacity = 1024 * 1024  // 1MB
	buf := make([]byte, maxCapacity)
	scanner.Buffer(buf, maxCapacity)
	
	lineCount := 0
	wordCount := 0
	
	for scanner.Scan() {
		lineCount++
		line := scanner.Text()
		
		// 간단한 단어 수 계산
		inWord := false
		for _, char := range line {
			if char == ' ' || char == '\t' || char == '\n' {
				inWord = false
			} else if !inWord {
				wordCount++
				inWord = true
			}
		}
		
		// 진행 상황 출력
		if lineCount%10000 == 0 {
			fmt.Printf("처리 중: %d 라인\n", lineCount)
		}
	}
	
	if err := scanner.Err(); err != nil {
		return err
	}
	
	fmt.Printf("\n총 라인 수: %d\n", lineCount)
	fmt.Printf("총 단어 수: %d\n", wordCount)
	
	return nil
}

func main() {
	err := processLargeFile("large_file.txt")
	if err != nil {
		fmt.Println("처리 실패:", err)
	}
}


에러 처리

파일 에러 유형

package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	_, err := os.Open("nonexistent.txt")
	
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Println("파일이 존재하지 않습니다.")
		} else if os.IsPermission(err) {
			fmt.Println("권한이 없습니다.")
		} else if errors.Is(err, os.ErrClosed) {
			fmt.Println("파일이 이미 닫혔습니다.")
		} else {
			fmt.Println("알 수 없는 에러:", err)
		}
	}
}

안전한 파일 쓰기 (원자적 쓰기)

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func safeWriteFile(filename string, data []byte) error {
	// 임시 파일에 쓰기
	dir := filepath.Dir(filename)
	tmpFile, err := os.CreateTemp(dir, "tmp-")
	if err != nil {
		return err
	}
	tmpName := tmpFile.Name()
	
	defer func() {
		tmpFile.Close()
		os.Remove(tmpName)  // 정리
	}()
	
	// 데이터 쓰기
	if _, err := tmpFile.Write(data); err != nil {
		return err
	}
	
	// 디스크에 동기화
	if err := tmpFile.Sync(); err != nil {
		return err
	}
	
	// 파일 닫기
	if err := tmpFile.Close(); err != nil {
		return err
	}
	
	// 원자적으로 이름 변경
	return os.Rename(tmpName, filename)
}

func main() {
	data := []byte("중요한 데이터\n")
	err := safeWriteFile("important.txt", data)
	if err != nil {
		fmt.Println("쓰기 실패:", err)
		return
	}
	
	fmt.Println("안전하게 저장 완료")
}


임시 파일

os.CreateTemp

package main

import (
	"fmt"
	"os"
)

func main() {
	// 임시 파일 생성
	tmpFile, err := os.CreateTemp("", "example-*.txt")
	if err != nil {
		fmt.Println("임시 파일 생성 실패:", err)
		return
	}
	defer os.Remove(tmpFile.Name())  // 정리
	defer tmpFile.Close()
	
	fmt.Println("임시 파일:", tmpFile.Name())
	
	// 임시 파일에 쓰기
	if _, err := tmpFile.WriteString("임시 데이터\n"); err != nil {
		fmt.Println("쓰기 실패:", err)
		return
	}
	
	fmt.Println("임시 파일 사용 완료")
}

os.MkdirTemp (임시 디렉토리)

package main

import (
	"fmt"
	"os"
)

func main() {
	tmpDir, err := os.MkdirTemp("", "example-")
	if err != nil {
		fmt.Println("임시 디렉토리 생성 실패:", err)
		return
	}
	defer os.RemoveAll(tmpDir)  // 정리
	
	fmt.Println("임시 디렉토리:", tmpDir)
	
	// 임시 디렉토리 사용
	// ...
}


일반적인 실수

1. defer Close() 누락

// ❌ 나쁜 예
file, _ := os.Open("file.txt")
// Close() 호출 없음 - 리소스 누수!

// ✅ 좋은 예
file, err := os.Open("file.txt")
if err != nil {
	return err
}
defer file.Close()

2. 에러 무시

// ❌ 나쁜 예
data, _ := os.ReadFile("file.txt")

// ✅ 좋은 예
data, err := os.ReadFile("file.txt")
if err != nil {
	log.Fatal(err)
}

3. bufio.Writer Flush 누락

// ❌ 나쁜 예
writer := bufio.NewWriter(file)
writer.WriteString("data")
// Flush() 없음 - 데이터 손실 가능!

// ✅ 좋은 예
writer := bufio.NewWriter(file)
writer.WriteString("data")
writer.Flush()

4. 경로 구분자 하드코딩

// ❌ 나쁜 예 (Windows에서 문제)
path := "dir/subdir/file.txt"

// ✅ 좋은 예
path := filepath.Join("dir", "subdir", "file.txt")

5. 큰 파일 전체 읽기

// ❌ 나쁜 예 - 메모리 부족 가능
data, _ := os.ReadFile("large_file.txt")

// ✅ 좋은 예 - 스트리밍 처리
file, _ := os.Open("large_file.txt")
scanner := bufio.NewScanner(file)
for scanner.Scan() {
	// 라인별 처리
}


성능 고려사항

1. 버퍼링 사용

// 느림
for i := 0; i < 10000; i++ {
	file.WriteString(fmt.Sprintf("Line %d\n", i))
}

// 빠름
writer := bufio.NewWriter(file)
for i := 0; i < 10000; i++ {
	fmt.Fprintf(writer, "Line %d\n", i)
}
writer.Flush()

2. 적절한 버퍼 크기

// 기본 버퍼 (4096 바이트)
reader := bufio.NewReader(file)

// 큰 버퍼 (더 나은 성능)
reader := bufio.NewReaderSize(file, 32*1024)

3. io.Copy 활용

// 직접 읽기/쓰기보다 효율적
io.Copy(destFile, sourceFile)


베스트 프랙티스

  1. 항상 에러 확인: 모든 파일 작업 후 에러 체크
  2. defer로 리소스 정리: defer file.Close() 패턴 사용
  3. bufio 활용: 대량 데이터는 버퍼링된 I/O 사용
  4. filepath 패키지: 크로스 플랫폼 경로 처리
  5. 적절한 권한: 파일 생성 시 최소 권한 원칙
  6. 원자적 쓰기: 중요한 데이터는 임시 파일 → 이름 변경
  7. 스트리밍 처리: 큰 파일은 전체 읽기 대신 라인별 처리
  8. 에러 타입 확인: os.IsNotExist, os.IsPermission 활용
  9. 임시 파일 정리: defer os.Remove() 사용
  10. 동기화: 중요 데이터는 Sync() 호출로 디스크 동기화
  11. 상대 경로 주의: 절대 경로 또는 filepath.Abs 사용 고려
  12. 파일 잠금: 필요시 OS별 파일 잠금 메커니즘 사용


참고 자료