Notice
Recent Posts
Recent Comments
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Archives
Today
Total
관리 메뉴

SYDev

[이것이 자바다] Chapter 18. 데이터 입출력 본문

Programming Lang/Java

[이것이 자바다] Chapter 18. 데이터 입출력

시데브 2025. 2. 6. 16:16

1. 입출력 스트림

  • 데이터 입출력: 데이터는 키보드를 통해 입력될 수도 있고, 파일 또는 프로그램으로부터 입력되거나, 모니터로 출력될 수 있고, 파일에 저장되거나 다른 프로그램으로 전송될 수 있음
  • Java는 입력 스트림과 출력 스트림을 통해 데이터를 입출력함
  • Stream: 단방향으로 데이터가 흐르는 것을 의미
  • 어떤 데이터를 입출력하느냐에 따라 두 종류로 구분
    • 바이트 스트림: 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할 때 사용
      • InputStream & OutputStream
    • 문자 스트림: 문자만 입출력할 때 사용
      • Reader & Writer

 

2. 바이트 출력 스트림

  • OutputStream: 바이트 출력 스트림의 최상위 클래스이자 추상 클래스

2.1. 1 바이트 출력

  • write(int b) method는 매개값 int(4 byte)에서, 끝 1byte만 출력
  • OutputStream은 내부에 작은 buffer를 가짐
    • write() method 호출 -> 버퍼에 바이트를 우선 저장, 버퍼가 차면 순서대로 바이트를 출력
    • flush(): 내부 버퍼에 잔류하는 모든 바이트를 출력하고, 버퍼를 비우는 역할
  • OutputStream을 더이상 사용하지 않을 때, close() method를 호출하여 outputStream이 사용한 메모리를 해제

package ch18.sec2.exam1;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class WriteExample {
	public static void main(String[] args) {
		try {
			OutputStream os = new FileOutputStream("경로/test1.db");
			
			byte a = 10;
			byte b = 20;
			byte c = 30;
			
			os.write(a);
			os.write(b);
			os.write(c);
			
			os.flush();
			os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

-> test1.db 파일 생성

2.2. 바이트 배열 출력

  • write(byte[] b): 매개값으로 주어진 배열의 모든 바이트를 출력
  • write(byte[] b , int off, int len): b[off]부터 len개의 바이트를 출력

 

3. 바이트 입력 스트림

  • InputStream: 바이트 입력 스트림의 최상위 클래스이자, 추상 클래스

3.1. 1 바이트 읽기

  • read(): inputStream으로부터 1byte를 읽고, int (4byte) 타입으로 리턴 -> 리턴된 4byte 중 끝 1byte에만 데이터가 들어 있음
  • 더 이상 입력 스트림으로부터 바이트를 읽을 수 없으면, -1을 리턴
InputStream is = ...;
while(true) {
    int data = is.read();    // 1 바이트를 읽고 리턴
    if(data == -1) break;    // -1을 리턴한 경우 while문 종료
}

- 예제 

package ch18.sec2.exam1;

import java.io.InputStream;
import java.io.*;

public class ReadExample {
	public static void main(String[] agrs) {
		try {
			InputStream is = new FileInputStream("경로/test1.db");
			
			while(true) {
				int data = is.read();
				if(data == -1) {
					break;
				}
				System.out.println(data);
			}
			
			is.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

3.2. 바이트 배열로 읽기

  • read(byte[] b): inputStream으로부터 주어진 배열의 길이만큼 바이트를 읽고, 배열에 저장한 다음 바이트 수를 리턴

InputStream is = ...;
byte[] data = new byte[100];
while (true) {
    int num = is.read(data);    // 최대 100byte를 읽고, 읽은 바이트는 배열 data 저장, 읽은 수는 return
    
    if(num == -1) break;        // -1을 리턴하면 while문 종료
}

- 파일 복사 예제

package ch18.sec3.exam3;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class CopyExample {
	public static void main(String[] args) throws Exception {
		String originalFileName = "경로/test.jpg";
		String targetFileName = "경로/test2.jpg";
		
		InputStream is = new FileInputStream(originalFileName);
		OutputStream os = new FileOutputStream(targetFileName);
		
                is.transferTo(os);   // 아래 주석 대체 가능
                /*
		byte[] data = new byte[1024];
		while(true) {
			int num = is.read(data);
			if(num == -1) break;
			os.write(data, 0 , num);
		}
                */
		
		os.flush();
		os.close();
		is.close();
		
		System.out.println("복사 완료");
	}
}

 

4. 문자 입출력 스트림

4.1. 문자 출력

  • InputStream & OutputStream vs. Reader & Writer -> 입출력 단위가 문자인 것을 제외하고, 사용 방법 동일

package ch18.sec4.exam1;

import java.io.IOException;
import java.io.Writer;
import java.io.FileWriter;

public class WriteExample {
	public static void main(String[] args) {
		try {
			// 문자 기반 출력 스트림 생성
			Writer writer = new FileWriter("경로/test.txt");
			
			// 1 문자씩 출력
			char a = 'A';
			writer.write(a);
			char b = 'B';
			writer.write(b);
			
			// char 배열 출력
			char[] arr = { 'C', 'D', 'E' };
			writer.write(arr);
			
			// 문자열 출력
			writer.write("FGH");
			
			// 버퍼에 잔류하고 있는 문자열들 출력, 버퍼를 비움
			writer.flush();
			
			// 출력 스트림을 닫고 메모리 해제
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

4.2. 문자 읽기

package ch18.sec4.exam1;

import java.io.Reader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class ReadExample {
	public static void main(String[] args) {
		try {
			Reader reader = null;
			
			// 1 문자씩 읽기
			reader = new FileReader("path/test.txt");
			while(true) {
				int data = reader.read();
				if(data == -1) break;
				System.out.print((char)data);
			}
			
			reader.close();
			System.out.println();
			
			// 문자 배열로 읽기
			reader = new FileReader("path/test.txt");
			char[] data = new char[100];
			while(true) {
				int num = reader.read(data);
				if(num == -1) break;
				for(int i = 0; i < num; i++) {
					System.out.print(data[i]);
				}
			}
			
			reader.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

5. 보조 스트림

  • 보조 스트림: 다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림
  • 자체적으로 입출력 수행 불가능 -> 입출력 소스로부터 직접 생성된 입출력 스트림에 연결해서 사용

보조스트림1 변수 = new 보조스트림1(입출력스트림);
보조스트림2 변수 = new 보조스트림2(보조스트림1);	// stream chain

- 예제

InputStream is = new FileInputStrema("...");
InputStreamReader reader = new InputStreamReader( is );	// 바이트 스트림을 문자 스트림으로 변환
BufferReader br = new BufferedReader( reder );

 

6. 문자 변환 스트림

  • 바이트 스트림(InputStream, OutputStream)에서 입출력할 데이터가 문자인 경우, 문자 스트림(Reader, Writer)로 변환해서 사용하는 것이 좋음
    • 문자로 바로 입출력하는 편리함
    • 문자셋의 종류를 지정 가능

6.2. InputStream을 Reader로 변환

  • InputStream을 Reader로 변환하려면 InputStreamReader 보조 스트림 연결

InputStream is = new FileInputStream("path/test.txt");
Reader reader = new InputStreamReader(is);	// (is, "UTF-8")로 문자셋 전달 가능

6.3. OutputStream을 Writer로 변환

  • OutputStream을 Writer로 변환하려면 OutputStreamWriter 보조 스트림 연결

OutputStream os = new FileOutputStream("path");
Writer writer = new OutputStreamWriter(os);	// (os, "UTF-8")로 문자셋 전달 가능

 

7. 성능 향상 스트림

  • cpu와 메모리가 아무리 뛰어나도, 하드 디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드 디스크의 처리 속도에 맞춰짐
  • 네트워크로 데이터를 전송할 때도, 느린 네트워크 환경이라면 컴퓨터 사양과 별개로 성능이 낮아짐
  • 프로그램이 입출력 소스와 직접 작업하지 않고, 중간에 메모리 버퍼와 작업함으로써, 실행 성능 향상 가능
  • 버퍼에 데이터를 쌓다가, 한꺼번에 하드 디스크로 보냄으로써 디스크 접근 횟수 감소

  • 메모리 버퍼를 제공하여 프로그램의 실행 성능을 향상시키는 보조 스트림
    • 바이트 스트림의 경우, BufferedInputStream, BufferedOutputStream
    • 문자 스트림의 경우, BufferedReaeder, BufferedWriter
BufferedInputStream bis = new BufferedInputStream(바이트 입력 스트림);
BufferedOutputStream bos = new BufferedOutputStream(바이트 입력 스트림);

BufferedReader br = new BufferedReader(문자 입력 스트림);
BufferedWriter bw = new BufferedWriter(문자 출력 스트림);

- 예제

package ch18.sec7.exam1;

import java.io.*;

public class BufferExample {
	public static void main(String[] args) throws Exception {
		// 입출력 스트림 생성
		String originalFilePath1 = 
				BufferExample.class.getResource("originalFile1.jpg").getPath();
		String targetFilePath1 = "path/targetFile1.jpg";
		
		FileInputStream fis = new FileInputStream(originalFilePath1);
		FileOutputStream fos = new FileOutputStream(targetFilePath1);
		
		// 입출력 스트림 + 버퍼 스트림 생성 
		String originalFilePath2 = 
				BufferExample.class.getResource("originalFile2.jpg").getPath();
		String targetFilePath2 = "path/targetFile2.jpg";
		FileInputStream fis2 = new FileInputStream(originalFilePath2);
		FileOutputStream fos2 = new FileOutputStream(targetFilePath2);
		BufferedInputStream bis = new BufferedInputStream(fis2);
		BufferedOutputStream bos = new BufferedOutputStream(fos2);
		
		// 버퍼를 사용하지 않고 복사
		long nonBufferTime = copy(fis, fos);
		System.out.println("버퍼 미사용: \t" + nonBufferTime + " ns");
		
		// 버퍼를 사용하고 복사
		long bufferTime = copy(bis, bos);
		System.out.println("버퍼 사용: \t" + bufferTime + " ns");
		
		fis.close();
		fos.close();
		bis.close();
		bos.close();
	}
	
	public static long copy(InputStream is, OutputStream os) throws Exception {
		// 시작 시간 저장
		long start = System.nanoTime();
		// 1 바이트를 읽고 1 바이트 출력
		while(true) {
			int data = is.read();
			if(data == -1) break;
			os.write(data);
		}
		os.flush();
		// 끝 시간 저장
		long end = System.nanoTime();
		// 복사 시간 리턴
		return (end - start);
	}
}

  • 문자 입력 스트림 Reader에 BufferedReader를 연결하면, 행 단위로 문자열을 읽는 readLine() method 제공
BufferedReader br = new BufferReader(new FileReader("..."));
while(true) {
    String str = br.readLine();    // 파일에서 한 행씩 읽음
    if(str == null) break;         // 파일 끝 -> while 문 종료
}

 

8. 기본 타입 스트림

  • 바이트 스트림에 DataInputStream과 DataOutputStream 보조 스트림 연결 -> 기본 타입인 boolean, char, short, int, long, float, double 값을 입출력 가능

기본 타입 스트림 제공 메소드

- 예제

FileOutputStreame fos = new FileOutputStream("path");
PrintStream ps = new PrintStream(fos);

ps.print("마치 ");
ps.println("프린터가 출력하는 것처럼 ");
ps.println("데이터를 출력합니다.");
ps.printf("| %6d | %-10s | %10s | \n", 1, "홍길동", "도적");
ps.printf("| %6d | %-10s | %10s | \n", 2, "김자바", "학생");

 

10. 객체 스트림

  • Java는 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있음
  • 객체를 출력하려면, 필드값을 일렬로 늘어선 바이트로 변경해야 함 -> 이를 직렬화(serialization)라 함
  • 반대로 직렬화된 바이트 객체필드값으로 복원하는 것을 역직렬화(deserialization)
  • 객체 입출력 보조 스트림
    • ObjectInputStream: 바이트 입력 스트림과 연결되어 객체로 복원하는 역직렬화
    • ObjectOutputStream: 바이트 출력 스트림과 연결되어 객체를 직렬화

ObjectInputStream ois = new ObjectInputStream(바이트 입력 스트림);
ObjectOutputStream oos = new ObjectOutputStream(바이트 출력 스트림);

oos.writeObject(객체);
객체타입 변수 = (객체타입) ois.readObject();

 

- 예제

package ch18.sec10;

import java.io.Serializable;

public class Member implements Serializable {
	private static final long serialVersionUID = -622284561026719240L;
	private String id;
	private String name;
	
	public Member(String id, String name) {
		this.id = id;
		this.name = name;
	}
	
	@Override
	public String toString() { return id + ": " + name; }
}

package ch18.sec10;

import java.io.Serializable;

public class Product implements Serializable {
	private static final long serialVersionUID = -621812868570078544L;
	private String name;
	private int price;
	
	public Product(String name, int price) {
		this.name = name;
		this.price = price;
	}
	
	@Override
	public String toString() { return name + ": " + price; }
}
package ch18.sec10;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class ObjectInputOutputStreamExample {
	public static void main(String[] args) throws Exception {
		// FileOutputStream에 ObjectOutputStream 보조 스트림 연결
		FileOutputStream fos = new FileOutputStream("object.dat");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		// 객체 생성
		Member m1 = new Member("fall", "단풍이");
		Product p1 = new Product("노트북", 1500000);
		int[] arr1 = { 1, 2, 3 };
		
		// 객체를 역직렬화해서 파일에 저장
		oos.writeObject(m1);
		oos.writeObject(p1);
		oos.writeObject(arr1);
		
		oos.flush(); oos.close(); fos.close();
		
		// FileInputStream에 ObjectInputStream 보조 스트림 연결
		FileInputStream fis = new FileInputStream("object.dat");
		ObjectInputStream ois = new ObjectInputStream(fis);
		
		// 파일을 읽고 역직렬화해서 객체로 복원
		Member m2 = (Member) ois.readObject();
		Product p2 = (Product) ois.readObject();
		int[] arr2 = (int[]) ois.readObject();
		
		ois.close(); fis.close();
		
		// 복원된 객체 내용 확인
		System.out.println(m2);
		System.out.println(p2);
		System.out.println(Arrays.toString(arr2));
	}
}

10.1. Serializable 인터페이스 

  • java에서는 Serializable interface를 구현한 클래스만 직렬화할 수 있도록 제한
  • Serializable interface는 멤버가 없는 빈 인터페이스이지만, 객체를 직려로하할 수 있다고 표시하는 역할
  • 객체가 직렬화될 때, 인스턴스 필드값은 직렬화 대상이지만, static 필드값과 transient로 선언된 필드값은 직렬화에서 제외

10.2. serialVersionUID 필드

  • 직렬화할 때 사용된 클래스와 역직렬화할 때 사용된 클래스기본적으로 동일한 클래스여야 함

  • 클래스 내용이 다르더라도 직렬화된 필드를 공통으로 포함하고, 두 클래스가 동일한 serialVersionUI 상수값을 가지고 있는 경우, 역직렬화 가능

 

11. File과 Files 클래스

11. File 클래스

  • 경로 구분자는 os마다 조금씩 다른데, 윈도우는 \\ 또는 /, 맥os 및 리눅스에서는 / 사용
  • 경로에 실제 파일이나 디렉토리가 없어도 예외 발생 X -> exists() method 호출로 검증
File file = new File("경로");
boolean isExist = file.exit();	// 파일이나 폴더가 존재하면 true 리턴

 

exists() method 리턴값이 false일 경우 사용 가능한 methods

 

exists() method 리턴값이 true일 경우 사용 가능한 methods

- 예제

package ch18.sec11;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileExample {
	public static void main(String[] args) throws Exception {
		// File 객체 생성
		File dir = new File("src/ch18/sec11/Temp/images");
		File file1 = new File("src/ch18/sec11/Temp/file1.txt");
		File file2 = new File("src/ch18/sec11/Temp/file2.txt");
		File file3 = new File("src/ch18/sec11/Temp/file3.txt");
		
		// 존재하지 않으면 디렉토리 또는 파일 생성
		if(dir.exists() == false) { dir.mkdirs(); }
		if(file1.exists() == false) { file1.createNewFile(); }
		if(file2.exists() == false) { file2.createNewFile(); }
		if(file3.exists() == false) { file3.createNewFile(); }
		
		// temp 폴더의 내용 출력
		File temp = new File("src/ch18/sec11/Temp");
		File[] contents = temp.listFiles();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm");
		for(File file : contents) {
			System.out.printf("%-25s", sdf.format(new Date(file.lastModified())));
			if(file.isDirectory()) {
				System.out.printf("%-10s%-20s", "<DIR>", file.getName());
			} else {
				System.out.printf("%-10s%-20s", file.length(), file.getName());
			}
			System.out.println();
		}
	}
}

11.2. Files 클래스

  • Files 클래스는 static methods로 구성되어 있어, File 클래스처럼 객체로 만들 필요 X
  • Files의 static methods는 os의 파일 시스템에게 파일 작업을 수행하도록 위임

  • 위 methods는 parameter로 Path 객체를 받음
  • Path 객체: 파일이나 디렉토리를 찾기 위한 경로 정보를 가짐, 정적 메소드인 get() method로 얻을 수 있음
Path path = Paths.get(String first, String ... more);
Path path = Paths.get("/Temp/dir/file.txt");
Path path = Paths.get("/Temp/dir", "file.txt");
Path path = Paths.get("Temp", "dir", "file.txt");

- 예제

package ch18.sec11;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FilesExample {
	public static void main(String[] args) {
		try {
			String data = "" 
					+ "id: winter\n" 
					+ "email: winter@mycompany.com\n"
					+ "tel: 010-123-1234";
			
			// Path 객체 생성
			Path path = Paths.get("src/ch18/sec11/Temp/user.txt");
			
			// 파일 생성 및 데이터 저장
			Files.writeString(Paths.get("src/ch18/sec11/Temp/user.txt"), data, Charset.forName("UTF-8"));
			
			// 파일 정보 얻기
			System.out.println("파일 유형: " + Files.probeContentType(path));
			System.out.println("파일 크기: " + Files.size(path) + " bytes");
			
			// 파일 읽기
			String content = Files.readString(path, Charset.forName("UTF-8"));
			System.out.println(content);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


참고자료