입출력의 정의 및 사용 이유
1. 입출력(IO) 이란?
: 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터 연동을 위한 자바 라이브러리
- 단방향 데이터 송수신을 위해 스트림을 활용하여 데이터를 서로 주고 받는다.
2. 스트림이란?
: 입출력 장치에서 데이터를 읽고 쓰기 위한 단방향 통로로, 자바에서 제공하는 클래스
- 각각의 장치마다 연결할 수 있는 스트림이 존재하며, 바이트 단위 처리와 문자 단위 처리를 위한 스트림 등이 존재한다.
- 스트림은 기본적으로 1바이트 단위의 데이터만 지나가게 되는데,
주고 받는 데이터의 기본 단위가 1바이트이므로 단방향 처리만 가능하다.
따라서 입력 스트림과 출력 스트림을 따로 구성해야 한다.
- 영어, 숫자, 특수기호들은 한 글자가 1바이트이지만, 한글과 같은 언어들은 한 글자를 2바이트 또는 3바이트로 표현한다. 인코딩 방식에 따라 정해지는데, 해당 인코딩 방식에 맞춰서 2바이트 또는 3바이트 단위를 구분하여 입출력하기 위해선 문자 단위를 처리할 수 있는 스트림을 활용해야 한다.
3. 입출력을 사용하는 이유
- 입출력을 사용함으로써 사용자로부터 입력을 받거나 화면이나 스피커로 출력해줄 수 있다. 또한 파일 형태로 프로그램의 종료 여부와 상관없이 영구적으로 데이터를 저장할 수도 있다.
파일 관련 입출력
1. 파일 클래스란?
: 파일 시스템의 파일을 다루기 위한 클래스
- 파일의 크기나 속성, 이름 등의 정보를 확인할 수 있고, 파일 생성 및 삭제 기능 등을 제공한다.
(1) 파일 클래스 관련 메소드
반환 타입 | 메소드 | 설명 |
boolean | createNewFile() | 새로운 파일 생성 |
boolean | mkdir() | 새로운 디렉토리 생성 |
boolean | mkdirs() | 경로 상에 없는 모든 디렉토리 생성 |
boolean | delete() | 파일 또는 디렉토리 삭제 |
boolean | canExecute() | 실행할 수 있는 파일인지 여부 |
boolean | canRead() | 읽을 수 있는 파일인지 여부 |
boolean | canWrite() | 수정/저장할 수 있는 파일인지 여부 |
String | getName() | 파일 이름 반환 |
String | getParent() | 부모 디렉토리 반환 |
File | getParentFile() | 부모 디렉토리를 File 객체로 생성 후 반환 |
String | getPath() | 전체 경로 반환 |
boolean | isDirectory() | 디렉토리인지 여부 |
boolean | isFile() | 파일인지 여부 |
boolean | isHidden() | 숨김 파일인지 여부 |
long | lastModified() | 마지막 수정 날짜 및 시간 반환 |
long | length() | 파일 크기 리턴 |
/* 대상 파일이 존재하지 않더라도 인스턴스를 생성할 수 있다. */
File file = new File("test.txt");
try {
/* 파일 생성 후 성공 실패 여부 boolean 반환 */
boolean createSuccess = file.createNewFile();
/* 최초 실행 시 파일이 새롭게 생성 되어 true, 파일이 존재할 경우 새롭게 만들지 않아 false 반환 */
System.out.println("createSuccess: " + createSuccess);
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("파일의 크기: " + file.length());
System.out.println("파일의 경로: " + file.getPath());
System.out.println("현재 파일의 상위 경로: " + file.getParent());
System.out.println("파일의 절대 경로: " + file.getAbsolutePath());
/*파일 삭제(삭제 후 성공 여부를 boolean으로 반환)*/
boolean deleteSuccess = file.delete();
System.out.println("deleteSuccess: " + deleteSuccess);
2. 파일 입출력 관련 기반 스트림의 종류
(1) 바이트 단위(영어, 숫자, 특수기호 사용 시)
1) FileInputStream
FileInputStream fis = null;
try {
/* 읽어올 대상 파일이 존재하지 않는 경우에 대해서 FileNotFoundException 처리 필요 */
fis = new FileInputStream("testInputStream.txt");
/* read() : 파일에 기록 된 값을 순차적으로 읽어오고 더 이상 읽어올 데이터가 없으면 -1 반환 */
int value;
while((value = fis.read()) != -1) {
System.out.print((char)value);
}
/* byte 배열을 이용해 한 번에 읽어올 수 있다. */
byte[] bar = new byte[100];
fis.read(bar);
System.out.println(Arrays.toString(bar));
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
if(fis != null) {
try {
// 자원 반납
fis.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
* 자원 반납이 필요한 이유
1. 장기간 실행 중인 프로그램에서 스트림을 닫지 않으면 누수(leak) 발생 가능성
2. 버퍼의 잔류 데이터가 남은 상태에서 추가로 스트림을 사용하면 데드락(deadlock) 발생 가능성
2) FileOutputStream
/* 경로 문제 없이 파일이 존재하지 않을 경우에는 자동으로 파일이 생성 되지만
* 존재하지 않는 경로가 포함 되면 FileNotFoundException이 발생한다.
* 두 번째 인자로 true를 추가하면 존재하는 파일의 맨 끝에서부터 추가 된다. */
try(FileOutputStream fos = new FileOutputStream("testOutputStream.txt", true)) { // try with resource 문
// write() 메소드는 IOException을 핸들링해야 한다.
fos.write(97);
byte[] bytes = new byte[] {98, 99, 100, 101, 102, 10}; // 10은 개행문자(엔터)이다.
fos.write(bytes);
fos.write(bytes, 1, 3);
} catch (IOException e) {
System.out.println(e.getMessage());
}
- testOutputStream.txt의 결과는 아래와 같다.
abcdef
cde
(2) 문자 단위(한글까지 사용 시)
1) FileReader
// FileInputStream과 사용법은 거의 동일하나 1byte 단위가 아니라 character 단위로 읽어온다. */
try (FileReader fr = new FileReader("testReader.txt")) {
int value;
while ((value = fr.read()) != -1) {
System.out.print((char) value);
}
char[] carr = new char[100];
fr.read(carr);
for(char c : carr) System.out.print(c);
} catch (IOException e) {
System.out.println(e.getMessage());
}
2) FileWriter
try (FileWriter fw = new FileWriter("testWriter.txt")) {
fw.write(97);
fw.write('A');
fw.write(new char[] {'a', 'b', 'c', 'd', 'e', 'f'});
fw.write("안녕하세요");
} catch (IOException e) {
System.out.println(e.getMessage());
}
3. 파일 입출력 관련 보조 스트림의 종류
- 기반 스트림에 추가되어 스트림의 기능 향상 또는 기능 추가를 위해 사용한다.
- 보조 스트림만으로 는 입출력 처리가 불가능하고, 기반 스트림에 추가로 적용돼야 한다.
(1) 입출력 성능 향상(BufferedReader/BufferedWriter)
- 버퍼 공간을 이용하여 데이터를 쌓아 두었다가 입출력하여 입출력 횟수를 줄이고 성능을 향상시킨다.
1) BufferedReader
try(BufferedReader reader = new BufferedReader(new FileReader("testBuffered.txt"))) {
/* readLine() 메소드 기능을 추가로 제공하고 있다.
* 버퍼의 한 줄을 읽어와서 문자열로 반환하며 더 이상 읽어올 값이 없을 경우 null이 반환 된다. */
String temp;
while((temp = reader.readLine()) != null) {
System.out.println(temp);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
2) BufferedWriter
BufferedWriter writer = null;
try {
/* 보조 스트림 객체 생성 시에는 생성자의 인자로 연결 될 기반 스트림을 전달한다. */
writer = new BufferedWriter(new FileWriter("testBuffered.txt"));
writer.write("안녕하세요.\n");
writer.write("반갑습니다.\n");
/* 버퍼를 이용하는 경우 버퍼가 가득 차면 자동으로 내보내기를 하지만
가득 차지 않은 경우 flush() 로 내보내기를 해야 출력이 이루어진다. */
// writer.flush();
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
if(writer != null) {
try {
/* close()를 호출하면 내부적으로 flush()를 실행하고 난 뒤 자원을 반납한다. */
writer.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
(2) 형변환 보조스트림(InputStreamReader/OutputStreamWriter)
- 기반 스트림이 byte 스트림, 보조 스트림이 char 스트림인 경우 사용한다.
cf. InputStreamReader, OutputStreamWriter는 byte 스트림, BufferedReader, BufferedWriter는 char 스트림이다.
* 표준 스트림
- 자바에서는 콘솔, 키보드 같은 표준 입출력 장치로부터 데이터를 입출력하기 위한 스트림을 System.in, System.out, System.err와 같은 형태로 제공하고 있다. -> 자주 사용되는 자원에 대해 미리 생성해둔 스트림으로, 별도로 생성하지 않아도 된다.
1) InputStreamReader
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
System.out.print("문자열 입력 : ");
String value = br.readLine();
System.out.println("입력 받은 문자열 : " + value);
} catch (IOException e) {
System.out.println(e.getMessage());
}
2) OutputStreamWriter
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))) {
bw.write("출력 테스트");
} catch (IOException e) {
System.out.println(e.getMessage());
}
(3) 기본 자료형 데이터 입출력 스트림(DataInputStream/DataOutputStream)
1) DataInputStream
try (DataInputStream dis = new DataInputStream(new FileInputStream("score.txt"))) {
while(true) {
/* 파일에 기록한 순서 대로 읽어오지 않는 경우 에러가 발생하거나 의미 없는 데이터를 읽어오게 된다. */
System.out.println(dis.readUTF());
System.out.println(dis.readInt());
System.out.println(dis.readChar());
}
/* read자료형() 메소드는 파일에서 더 이상 읽어올 값이 없는 경우 EOFException(End Of File) 발생 */
} catch (EOFException e) {
System.out.println("파일 읽기가 완료 되었습니다.");
} catch (IOException e) {
System.out.println(e.getMessage());
}
2) DataOutputStream
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("score.txt"))) {
dos.writeUTF("홍길동");
dos.writeInt(95);
dos.writeChar('A');
dos.writeUTF("유관순");
dos.writeInt(85);
dos.writeChar('B');
dos.writeUTF("이순신");
dos.writeInt(75);
dos.writeChar('C');
} catch (IOException e) {
System.out.println(e.getMessage());
}
(4) 객체 자료형 데이터 입출력(ObjectInputStream/ObjectOutputStream)
- 객체 입출력 처리를 위해서는 반드시 직렬화 처리를 해주어야 한다. 직렬화는 객체를 바이트 스트림으로 변환하는 것이다. (직렬화 처리를 해주지 않으면 NotSerializableException이 발생한다.)
- 직렬화 대상 클래스에 Serializable 인터페이스만 구현하면 직렬화가 필요한 경우 데이터 직렬화 처리가 된다. (implements Serializable)
/* 객체 입출력 처리를 위해서는 반드시 직렬화 처리를 해야 한다.
* 직렬화 대상 클래스에 Serializable 인터페이스만 구현하면 직렬화가 필요한 상황인 경우 데이터 직렬화 처리가 된다. */
public class MemberDTO implements java.io.Serializable {
private String id;
private String pwd;
private int age;
private char gender;
/* 해당 필드는 직렬화 처리에서 제외하겠다는 의미 */
private transient double point;
public MemberDTO(){}
public MemberDTO(String id, String pwd, int age, char gender, double point) {
this.id = id;
this.pwd = pwd;
this.age = age;
this.gender = gender;
this.point = point;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public double getPoint() {
return point;
}
public void setPoint(double point) {
this.point = point;
}
@Override
public String toString() {
return "MemberDTO{" +
"id='" + id + '\'' +
", pwd='" + pwd + '\'' +
", age=" + age +
", gender=" + gender +
", point=" + point +
'}';
}
}
public class Application4 {
public static void main(String[] args) {
MemberDTO[] members = {
new MemberDTO("user01", "pass01", 25, '남', 95.7),
new MemberDTO("user02", "pass02", 30, '여', 84.2),
new MemberDTO("user03", "pass03", 35, '여', 72.6),
};
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("member.txt"))) {
for(MemberDTO member: members) {
// MemberDTO 타입 객체를 하나씩 출력
oos.writeObject(member);
}
// MemberDTO[] 타입 객체를 출력
// oos.writeObject(members);
} catch (IOException e) {
e.printStackTrace();
}
MemberDTO[] inputMembers = new MemberDTO[members.length];
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("member.txt"))) {
for(int i = 0; i < inputMembers.length; i++) {
inputMembers[i] = (MemberDTO) ois.readObject();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
/* 역직렬화(바이트 스트림 -> 객체) 시 해당하는 클래스가 없으면 실패하므로 Exception handling이 필수이다. */
e.printStackTrace();
}
for(MemberDTO member: inputMembers) {
System.out.println(member);
}
}
}
'Java' 카테고리의 다른 글
[Java] 13. Collection (0) | 2024.08.22 |
---|---|
[Java] 12. 제네릭 프로그래밍 (0) | 2024.08.21 |
[Java] 10. 예외처리 (1) (0) | 2024.08.21 |
[Java] 배열의 값을 출력하기(반복문, Arrays.toString()) (0) | 2024.08.11 |
Java(9) 자주 쓰는 API - Object 클래스, String 클래스, 이스케이프 문자 (0) | 2024.08.08 |