자바의 입출력 스트림
- 입력 스트림 : 입력 장치로부터 자바 프로그램으로 데이터를 전달하는 객체
- 출력 스트림 : 자바 프로그램에서 출력 장치로 데이터를 보내는 객체
입출력 스트림의 기본 단위는 바이트이며, 단방향 스트림이고, 선입선출(FIFO)구조 입니다. 자바 프로그램 개발자는 직접 입력 장치에서 읽지 않고 입력 스트림을 통해 읽으며, 스크린 등 출력 장치에 직접 출력하지 않고 출력 스트림에 출력하면 됩니다.
또한 문자 스트림이란 문자만 입출력하는 스트림으로서 문자가 아닌 바이너리 데이터는 스트림에서 처리하지 못합니다. 문자가 아닌 데이터를 문자 스트림으로 출력하면 깨진 기호가 출력됩니다.
JDK의 스트림 계층 구조
스트림 연결
이러한 스트림은 연결될 수 있다는 특징이 있습니다.
파일 입출력
/FileReaderEx.java
package com.company.Iostream;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderEx {
public static void main(String[] args) {
FileReader in = null;
try {
in = new FileReader("./test.txt");
int c;
while((c = in.read()) != -1) {
System.out.print((char)c);
}
in.close();
} catch(IOException e) {
System.out.println("입출력 오류");
}
}
}
//Hello World!
//Hello Hyunseo!
/test.txt
Hello World!
Hello Hyunseo!
이 외에도 배열을 이용해서 스트링을 저장할 수도 있습니다.
/FileReaderEx.java
package com.company.Iostream;
import java.io.FileReader;
public class FileReaderEx {
public static void main(String[] args) throws Exception {
FileReader in = new FileReader("./test.txt");;
int c;
char[] cbuf = new char[2];
String data = "";
while((c = in.read(cbuf)) != -1) {
data += new String(cbuf, 0, c);
}
System.out.println(data);
in.close();
}
}
//Hello World!
//Hello Hyunseo!
읽은 문자 cbuf배열에 저장하고 읽은 문자 개수 c를 리턴합니다. 그리고 문자열을 하나 선언한 다음에 계속 이어 붙여서 마지막에 출력했습니다.
또한 JAVA에서 문자(char)는 2byte인데, 자바에서는 유니 코드를 사용하므로 2byte크기를 가지고 있습니다.
그 다음에는 FileWrite을 선언한 다음에 파일에 문자를 써보는 코드를 작성해 보도록 하겠습니다.
/FileWriterEx.java
package com.company.Iostream;
import java.io.FileWriter;
import java.io.InputStreamReader;
public class FileWriterEx {
public static void main(String[] args) throws Exception{
InputStreamReader in = new InputStreamReader(System.in);
FileWriter fout = null;
int c;
fout = new FileWriter("./test2.txt");
while((c = in.read())!= -1) {
fout.write(c); // 키보드로부터 받은 문자를 파일에 저장.
}
in.close();
fout.close();
}
}
//asdf
//asdfsadf-1
//-1
//EOF
//^D
/test.2txt
asdf
asdfsadf-1
-1
EOF
다음과 같이 정상적으로 코드가 동작하는 것을 확인할 수 있습니다.
이 외에도 배열로 문자를 넣을 수 있는 방법도 있습니다. 그냥 write인수로 charArray를 주면 됩니다.
/FileWriterEx.java
package com.company.Iostream;
import java.io.FileWriter;
import java.io.InputStreamReader;
public class FileWriterEx {
public static void main(String[] args) throws Exception{
FileWriter out = new FileWriter("./test2.txt");
char[] data = "hyunseo is\nhandsome".toCharArray();
out.write(data);
out.flush();
}
}
/test.2txt
hyunseo is
handsome
바이트 스트림
바이트 단위의 바이너리 값을 읽고 쓰는 스트림입니다. InputStream/OutputStream에 있으며 추상클래스입니다. 이는 바이트 스트림을 다루는 모든 클래스의 슈퍼 클래스가 됩니다. FileInputStream/FileOutputStream은 파일로부터 바이트 단위로 읽거나 저장하는 클래스 입니다. 바이너라 파일의 입출력 용도로 쓰입니다 ( 위에서는 문자를 보았죠 )
이미지나 동영상 파일을 읽고 쓰기 위해서는 반드시 바이트 스트림을 사용해야 합니다!!
/FileOutputStreamEx
package com.company.Iostream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamEx {
public static void main(String[] args) {
byte b[] = {7, 51, 3, 4, -1, 24};
try {
FileOutputStream fout = new FileOutputStream("./test.out");
for(int i = 0; i < b.length; i++) {
fout.write(b[i]); // 배열 b의 바이너리를 그대로 기록
}
fout.close();
} catch(IOException e) {}
System.out.println("test.out 저장하였습니다.");
}
}
이에 이어서 test.out파일을 읽는 코드를 작성해 보도록 하겠습니다.
/FileInputStreamEx
package com.company.Iostream;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamEx {
public static void main(String[] args) {
byte b[] = new byte[6];
try {
FileInputStream fin = new FileInputStream("./test.out");
int n = 0, c;
while((c = fin.read()) != -1) {
b[n] = (byte)c;
n++;
}
System.out.println("./test.out에서 읽은 배열을 출력합니다.");
for(int i = 0; i < b.length; i++) {
System.out.print(b[i] + " ");
}
System.out.println();
fin.close();
} catch(IOException e) { }
}
}
//./test.out에서 읽은 배열을 출력합니다.
//7 51 3 4 -1 24
FIle 클래스
java.io.File아래에 있는 클래스로서, 파일과 디렉터리 경로명의 추상적 표현입니다. FIle객체에는 파일 읽기/쓰기 기능이 없습니다.
간단히 FIle객체가 파일인지 디렉터리인지 확인하는 코드와, 디렉터리의 서브 리스트를 출력하는 코드를 작성하도록 하겠습니다.
/FileClassExample
package com.company.Iostream;
import java.io.File;
public class FileClassExample {
public static void listDirectory(File dir) {
System.out.println("-----------" + dir.getPath() + "의 서브 리스트 입니다.-----------");
File[] subFiles = dir.listFiles();
for(int i = 0; i < subFiles.length; i++) {
File f = subFiles[i];
long t = f.lastModified(); // 마지막으로 수정된 시간
System.out.print(f.getName());
System.out.print("\t파일 크기: " + f.length());
System.out.printf("\t수정한 시간: %tb %td %ta %tT\n", t, t, t, t);
}
}
public static void main(String[] args) {
File f1 = new File("./test.txt");
System.out.println(f1.getPath() + ", " + f1.getParent() + ", " + f1.getName());
String res = "";
if(f1.isFile()) {
res = "파일";
} else if(f1.isDirectory()) {
res = "디렉토리";
}
System.out.println(f1.getPath() + "은 " + res + "입니다.");
File f2 = new File("./src/com/company/java_sample");
if(!f2.exists()) {
f2.mkdir();
}
listDirectory(new File("./src/com/company"));
}
}
//./test.txt, ., test.txt
//./test.txt은 파일입니다.
//-----------./src/com/company의 서브 리스트 입니다.-----------
//Interface 파일 크기: 352 수정한 시간: 4월 13 수 21:59:37
//Collection 파일 크기: 416 수정한 시간: 4월 07 목 11:21:58
//Week.java 파일 크기: 135 수정한 시간: 3월 09 수 16:21:33
//BasicAPI 파일 크기: 1312 수정한 시간: 3월 31 목 16:30:07
//Swing 파일 크기: 288 수정한 시간: 4월 21 목 11:24:00
//java_sample 파일 크기: 64 수정한 시간: 5월 26 목 10:20:31
//double2 파일 크기: 352 수정한 시간: 3월 17 목 11:00:30
//SwingEvent 파일 크기: 256 수정한 시간: 4월 28 목 10:42:40
//Inheritance 파일 크기: 256 수정한 시간: 3월 09 수 22:55:40
//Main.java 파일 크기: 97 수정한 시간: 3월 09 수 20:16:00
//Array.java 파일 크기: 1967 수정한 시간: 3월 09 수 16:46:37
//Iostream 파일 크기: 224 수정한 시간: 5월 26 목 10:20:53
//Class 파일 크기: 224 수정한 시간: 3월 09 수 21:09:57
//Thread 파일 크기: 512 수정한 시간: 5월 21 토 15:36:29
//Exception 파일 크기: 384 수정한 시간: 3월 24 목 10:13:18
//JComponent 파일 크기: 384 수정한 시간: 5월 12 목 11:20:55
텍스트 파일 복사
이제 간단히 문자 스트림 FileReader와 FileWriter를 이용하여 파일을 복사하는 프로그램을 작성해 보도록 하겠습니다.
루트 디렉토리에 있는 test.txt를 현제 TextCopy.java가 있는 폴더로 복사 하는 코드입니다.
/TextCopy.java
package com.company.Iostream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
public class TextCopy {
public static void main(String[] args) throws Exception{
File src = new File("./test.txt");
File dest = new File("./src/com/company/iostream/test.txt");
int c;
FileReader fr = new FileReader(src); // 파일 입력 문자 스트림 생성
FileWriter fw = new FileWriter(dest); // 파일 출력 문자 스트림 생성
while((c = fr.read()) != -1) { // 문자 하나 읽고
fw.write((char)c); // 문자 하나 쓰고
}
fr.close();
fw.close();
System.out.println(src.getPath() + "를 " + dest.getPath() + "로 복사하였습니다.");
}
}
// ./test.txt를 ./src/com/company/iostream/test.txt로 복사하였습니다.
바이너리 파일 복사
/BinaryCopy.java
package com.company.Iostream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class BinaryCopy {
public static void main(String[] args) throws Exception{
File src = new File("./images/img.jpg");
File dest = new File("./src/com/company/iostream/img.jpg");
int c;
FileInputStream fi = new FileInputStream(src);
FileOutputStream fo = new FileOutputStream(dest);
while((c = fi.read()) != -1) {
fo.write((byte)c);
}
fi.close();
fo.close();
System.out.println(src.getPath() + "를 " + dest.getPath() + "로 복사하였습니다.");
}
}
//./images/img.jpg를 ./src/com/company/iostream/img.jpg로 복사하였습니다.
버퍼 스트림
- 버퍼를 가진 스트림
- 입출력 데이터를 일시적으로 저장하는 버퍼를 이용하여 입출력 효율 개선
버퍼 입출력의 목적은 입출력 시 운영체제의 API호출 횟수를 줄여 입출력 성능을 개선하는데 있습니다. 입력 시 입력 데이터를 버퍼에 모아두고 한번에 프로그램에 전달합니다.
/BlockBinaryCopy.java
package com.company.Iostream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class BlockBinaryCopy {
public static void main(String[] args) throws Exception{
File src = new File("./images/img.jpg");
File dest = new File("./src/com/company/iostream/img.jpg");
FileInputStream fi = new FileInputStream(src); // 파일 입력 바이트 스트림 생성
FileOutputStream fo = new FileOutputStream(dest); // 파일 출력 바이트 스트림 생성
byte[] buf = new byte [1024 * 10]; // 10K버퍼
while(true) {
System.out.println("doing");
int n = fi.read(buf); // 버퍼 크기만큼 읽기. n은 실제 읽은 바이트
fo.write(buf, 0, n);
if(n < buf.length) break;
}
fi.close();
fo.close();
System.out.println(src.getPath() + "를 " + dest.getPath() + "로 복사하였습니다.");
}
}
//doing
//doing
//doing
//doing
//doing
//./images/img.jpg를 ./src/com/company/iostream/img.jpg로 복사하였습니다.
이미지는 다음과 같은 경우 버퍼 크기를 지정할 경우 5번 만에 저장해서 집어 넣는 것을 보실 수 있습니다. 만약 이 보다 훨씬 큰 동영상 같은 것을 넣게 된다면, 엄청나게 효율적인 작업이 될 수 있겠습니다.
그 다음에는 BufferedOutputStream을 가지고 버퍼 출력 스트림을 생성하고, 키보드에서 받은 문자를 출력 스트림에 출력하고, ㅇ입력의 끝을 알리면 버퍼에 남아 있는 모든 문자를 출력하는 프로그램을 만들어 보도록 하겠습니다.
/BufferedIOEx.java
package com.company.Iostream;
import java.io.BufferedOutputStream;
import java.io.InputStreamReader;
public class BufferedIOEx {
public static void main(String[] args) throws Exception{
InputStreamReader in = new InputStreamReader(System.in);
BufferedOutputStream out = new BufferedOutputStream(System.out, 5);
int c;
while((c = in.read()) != -1) {
out.write(c);
}
out.flush(); // 버퍼에 남아있던 문자 출력
if(in != null) {
in.close();
out.close();
}
}
}
//12345678
//12345^D
//678
객체 직렬화
- 여러 필드로 구성된 레코드를 파일에 저장하면 type정보(예, int, String)는 잃게 됩니다. 전체 객체를 읽고 쓰려고 할 떄 serialization을 사용하게 됩니다.
- 전체 객체를 일고 쓰기 위해서는 ObjectInputStream과 ObjectOutputStream을 사용합니다.
- 스트림 객체를 포장하여 사용합니다.
- 예, ObjectOutputStream(new FileOutputStream("test.txt"))
객체는 Serializable을 implements하여 사용합니다. 인터페이스 Serializable은 메소드 없는 인터페이스, 즉 tagging interface입니다.
이제 객체를 직렬화 해서 파일로 저장해 보고, 이를 역 직렬화를 통해서 출력해 보는 실습을 해 보도록 하겠습니다.
- 직렬화를 사용하기 위해 UserInfo클래스를 만들고
- UserInfo.ser파일에 UserInfo객체의 ArrayList의 정보를 저장하고
- UserInfo.ser파일을 불러오는 역질렬화 하는 과정 순으로 진행하겠습니다.
/UserInfo.java
package com.company.Iostream;
import java.io.Serializable;
public class UserInfo implements Serializable {
String name;
String password;
int age;
public UserInfo() {
this("unknown", "1", 0);
}
public UserInfo(String name, String password, int age) {
this.name = name;
this.password = password;
this.age = age;
}
@Override
public String toString() {
return "(" + name + "," + password + "," + age + ")";
}
}
먼저 Serializable을 구현한 직렬화의 target객체를 만들어 주었습니다.
/CreateSerFile.java
package com.company.Iostream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class CreateSerFile {
public static void main(String[] args) {
ObjectOutputStream output;
try {
output = new ObjectOutputStream(new FileOutputStream("./src/com/company/iostream/UserInfo.ser"));
UserInfo u1 = new UserInfo("JavaMan", "1234", 30);
UserInfo u2 = new UserInfo("JavaWoman", "4321", 20);
ArrayList<UserInfo> list = new ArrayList<>();
list.add(u1);
list.add(u2);
output.writeObject(list);
output.close();
System.out.println("직렬화 완료");
} catch(Exception e) {
e.printStackTrace();
}
}
}
//직렬화 완료
이 과정을 마치면 같은 경로에 UserInfo.ser가 생성되었을 겁니다. 이제 이를 역 직렬화 해보도록 하겠습니다.
/ReadSerFile.java
package com.company.Iostream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
public class ReadSerFile {
public static void main(String[] args) {
ObjectInputStream input;
try {
input = new ObjectInputStream(new FileInputStream("./src/com/company/iostream/UserInfo.ser"));
ArrayList list = (ArrayList) input.readObject();
input.close();
System.out.println(list);
} catch(Exception e) {
e.printStackTrace();
}
}
}
//[(JavaMan,1234,30), (JavaWoman,4321,20)]
Try ~ Catch부분에 Exception이라고 한 이유는
readObject()에서 ClassNotFoundException이 필요하므로 IOException보다 상위 클래스인 Exception을 사용한 것입니다.
성공적으로 객체를 받아 들인 것을 볼 수 있습니다.
'School > Java Programming' 카테고리의 다른 글
Java Programming - Socket (0) | 2022.06.02 |
---|---|
Java Programming - Thread notify-wait() (0) | 2022.05.19 |
Java Programming - Thread basic (0) | 2022.05.19 |
Java Programming - Collection Framework (0) | 2022.04.07 |
Java Programming - Basic API Class (0) | 2022.03.24 |