1011 (18일차)
입출력(IO)
JVM 기준
데이터가 들어오면 입력(scanner), 나가면 출력(print).
※바이트/ 문자 기반 마다 정해진 입출력 클래스가 있기 때문에 교차해서 사용할 수 없다.
nodeStream이 필수인 이유:
시스템 입출력과 직접 연결됨.
보조스트림은 그런 필수 스트림을 감싸서 사용함.
-표준 입출력
-System.in : 표준입력 (사용자 키보드 입력)
-System:out : 표준출력 (콘솔 출력)
-System.err : 표준에러
Scanner가 나오기 전 까진 아래의 방법으로 입력을 받았다.
-1은 더 이상 읽어올 값이 없을때 반환되는 값.
try
{
while((input = System.in.read())!=-1)
{
System.out.println(input);
/**
* a
* 97 (알파벳 a)
* 13 \r (맨 앞으로 가라)
* 10 \n (개행)
* 13과 10은 엔터를 치면서 발생.
* 아스키코드표 참고
*/
}
}
catch (IOException e)
{
e.printStackTrace();
}
-입출력 스트림
표준 입출력을 제외한 모든 stream 클래스는 사용 후
close() 통해 자원을 반납해야 한다. os내에서 사용되는 메모리라서 사용이 끝났으면 반환.
상대적 경로 | 절대적 경로 | |
기준 | 현재 디렉토리 기준 (프로젝트 root directory) |
루트 디렉토리 기준 윈도우: C:\~ 맥: /~ |
디렉토리 명에 한글/공백이 있으면 오류가 발생한다. |
byte 예제 코드1
private void test1()
{
FileInputStream fis=null;
FileOutputStream fos = null;
try
{
// fis = new FileInputStream("abc/abc.txt");//생성
// fos = new FileOutputStream("abc/abc_copy.txt",true);//뒤에 t,f는 이어붙이기 여부 확인 생략시 false
fis = new FileInputStream("abc/abc.txt");//생성
//이스케이핑 처리
//fos = new FileOutputStream("C:\\Users\\user1\\Desktop\\abc_copy2.txt",true);
fos = new FileOutputStream("C:/Users/user1/Desktop/abc_copy3.txt",true);
//이어쓰기는 객체입출력에선 불가
int data = 0;
while((data=fis.read())!=-1)
{
System.out.println(data);
/**
* 영문과 숫자 등은 1byte씩 처리되어서 온전히 출력되지만..
* 한글과 한자는 3byte가 합쳐져서 한글자가 되는거라(utf-8)
* 1byte씩 읽어오면 아마 깨질것
*/
fos.write(data);
}
}
catch (IOException e) //FileNotFondException(자식)도 핸들링. 부모클래스라서.
//하지만 NullFoninterException은 잡을수 없어서 Exception으로 해도됨
{
e.printStackTrace();
}
finally
{
try
{
fis.close();//try안에서 선언된 변수라 접근 못함..try위에서 선언.
}
catch (Exception e)
{
e.printStackTrace();
}
try {
fos.close();
//fis.close(); 밑에 적으면 좋겠지만..예외는 따로 처리하는것이 좋다.
//fis.close()에서 예외처리 당하면 아랫줄은 skip되기 때문임.
}
catch (Exception e)
//IOException에서 Exception으로 바꾸는 이유.
//fos=null인상태에서 예외처리 하기 위함.io면 nullpointEx 감지 못함
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
byte 말고 byte 배열로도 입출력 작업이 가능하다.
byte[] 예제 코드2
/**
* test2 개선
* -read():int
* -read(byte[]):int
* -try..with..resource(jdk 1.7) - Stream 객체의 선언과 반납 처리 (finally 불필요)
*/
private void test3()
{
try (//대입x 선언
FileInputStream fis = new FileInputStream("C:\\Users\\user1\\Desktop\\image\\nongdam.jpeg");
FileOutputStream fos = new FileOutputStream("nongdam_copy.jpeg");
){
byte[] buf = new byte[8192]; //기본:8kb 다른 수치로도 가능.
int len = 0; //read(byte[])의 반환값은 읽어낸 byte 갯수
while((len=fis.read(buf))!=-1) //읽어올 값이 없으면 -1리턴
{
System.out.println(len);
fos.write(buf,0,len); //buf의 idnex 0번부터 읽어낸 길이만큼 써라.
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
보조 스트림
1.기본 스트림의 성능향상 또는 부가기능을 위해 n개를 추가해서 쓸 수있다.
2.반드시 기본 스트림이 필요함(표준 입출력과 직접 연결됨)
보조 스트림 단독 사용 불가
3.기본스트림 객체를 감싼 구조. 보조스트림 객체만 제어하면 된다.
4.보조스트림 객체만 반환하면 기본 스트림까지 반환된다.
주의 사항: 입력-입력끼리, 문자기반-문자기반 끼리 사용 가능. 교차사용 불가
보조스트림 예제 코드
private void test4()
{
String srcName = "C:\\Users\\user1\\Desktop\\image\\nongdam.jpeg";
String destName = "nongdam_copy.jpeg";
try
(
//기본스트림이 인자로 반드시 있어야함
//기본스트림=대상과 직접 연결
//보조스트림=기본에 성능만 추가 srcName과 직접 연결 불가
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcName));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(srcName));
){
byte[] buf = new byte[8192]; //기본:8kb
int len = 0; //read(byte[])의 반환값은 읽어낸 byte 갯수
//보조스트림 bis ,bos 만 사용
while((len=bis.read(buf))!=-1)
{
System.out.println(len);
bos.write(buf,0,len);
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
문자 기반 스트림
한 글자 씩 처리한다.(한글 깨짐 없음) 텍스트 파일을 처리할 때 사용.
FileReader 문자기반 입력 스트림
FileWriter 문자기반 출력 스트림
예제 코드1
//문자 단위로 작업
private void test1()
{
//파일 객체를 만들어서 reader에 인자로 사용해도 됨
//실제,가상의 파일을 가리키는 자바 객체 (파일 자체는 아님)
//file 객체를 통해서 실제 파일을 제어하는 것이 가능함.
//스트림도 파일 사용가능
File src = new File("abc/abc.txt");
File dest = new File("abc/abc_copy3.txt");
try(//finally x
FileReader fr = new FileReader(src);
FileWriter fw = new FileWriter(dest);
){
int data = 0;
while((data = fr.read())!=-1)
{
System.out.print((char)data);
fw.write(data);
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
예제 코드1의 보조스트림 버전 코드
/**
* test1의 보조스트림 버전
* 입력스트림:읽어올 파일이 없으면 오류.(반드시 존재!!)
* 출력스트림:출력할 파일이 없어도 오류안남. 없으면 스트림클래스에서 새로 생성
*/
private void test2()
{
File src = new File("abc/abc.txt");
File dest = new File("abc/abc_copy4.txt"); //디렉토리는 반드시 존재 해야함...
//경로없을 시:FileNotFoundException:abcz\abc_copy4.txt (지정된 경로를 찾을 수 없습니다)
try(
BufferedReader br = new BufferedReader(new FileReader(src));
BufferedWriter bw = new BufferedWriter(new FileWriter(dest));
)
{
String data =null;
/**
* readLine():개행문자까지 읽어서 개행문자를 버리고 반환
*/
while((data=br.readLine())!=null) //readLine:한줄씩
{
System.out.print(data);
bw.write(data+"\n"); //개행추가해야 원본과 동일
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
브릿지 예제 코드
/**
* jdk1.5에 추가된 Scanner 이 전의 사용자 입력값..
* -System.in - BufferedReader
* (byte - String 사이 브릿지 역할:inputStreamReader)
*/
private void test3()
{
try
{
//스캐너와 다르게 예외처리 필수..
//System.in:표준입력이 있어서 close()하지 않음. //브릿지 //표준입출력
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("아무말:");
String str = br.readLine(); //sc.next() 처럼..
System.out.printf("\"%s\"라고 하셨습니다.",str);
}
catch(Exception e)
{
e.printStackTrace();
}
}
-파일
java.io.File
1.실제 / 가상의 파일 , 디렉토리를 가리키는 자바 객체
2.파일 생성,삭제,메타정보 조회..
3.디렉토리 생성, 삭제, 자식 파일 조회 등등 쓰임새가 다양하다.
파일 객체 생성 | File f = new File(경로); |
파일명 | f.getName() |
경로(파일명 제외) | f.getParent() |
절대 경로 | f.getAbsolutePath() |
존재 유무 (boolean) |
f.exists() |
파일인가? (boolean) |
f.isFile() |
디렉토리인가? (boolean) |
f.isDirectory() |
파일명 변경(이동) | File f2 = new File(새 경로); f.renameTo(f2); |
파일 삭제 | f.delete() |
새 디렉토리 만들기 단일 |
dir.mkdir(); |
새 디렉토리 만들기 복수 |
dir.mkdirs(); |
파일 예제 코드1
private void test1()
{
File f = new File("abc/abc.txt"); //상대경로
System.out.println(f);//toString override
File f2 = new File("C:\\Workspaces\\java_workspace\\14_IO\\abc\\abc.txt"); //절대경로
System.out.println(f2);
System.out.println(f2.getName()); //파일명만 보여줌
System.out.println(f2.getParent()); //경로(파일명뺀)
System.out.println(f.getAbsolutePath());//절대경로
System.out.println(f.exists());//존재:true
System.out.println(f.isFile());
System.out.println(f.isDirectory());
File f3 = new File("abc/def.txt");
if(!f3.exists())
{
System.out.println(f3+"을 생성합니다.");
try {
f3.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//파일명 변경 = 이동
File dest = new File("def.txt");
f3.renameTo(dest);
//f3.delete();//파일삭제도 있다.
}
파일 예제 코드2
사용자 입력값을 받아 폴더와 txt파일 만들기
/**
* 사용자 이름을 입력받아 members 하위에 사용자명/사용자명.txt 파일을 생성.
* -members/홍길동/홍길동.txt
* -members,사용자명 디렉토리를 동적으로 생성.
*/
private void test3()
{
Scanner sc = new Scanner(System.in);
System.out.print("사용자 명을 입력하세요. : ");
String userStr = sc.next();
File dir = new File("C:\\Workspaces\\java_workspace\\14_IO");//절대경로
File newDir = new File("members");
if(!newDir.exists()) //존재하지 않는다면
{
newDir.mkdir();
System.out.println(newDir+"생성완료");
}
File newDir2 = new File(newDir+"/"+userStr); //or (newDir,파일명);
if(!newDir2.exists()) //존재하지 않는다면
{
newDir2.mkdir();
System.out.println(newDir2+"생성완료");
}
File myFile = new File("members/"+userStr+"/"+userStr+".txt");
if(!myFile.exists())
{
try {
myFile.createNewFile();
System.out.println(myFile+"을 생성합니다.");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
-오브젝트 입출력
객체 단위로 읽고/쓰기를 지원하는 스트림(byte 기반)
1)ObjectInputStream
2)ObjectOutputStream
읽기/쓰기를 작업할 클래스는 반드시 serializable 인터페이스를 구현해야 한다.
serializable란?
추상 메소드가 없는 마커 인터페이스..(추후 따로 공부하기)
객체를 하나만 읽고 쓸 수도, 여러개를(리스트) 읽고 쓸 수 있다.
오브젝트 입출력 예제 코드 1
객체 하나만
/**
* -객체 하나 쓰기 읽기
*/
private void test1() {
// TODO Auto-generated method stub
try(
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
)
{
oos.writeObject(new User("honggd","1234",LocalDateTime.now()));
System.out.println("저장 완료!");
//NotSerializableException
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
//
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser")))
{
User user = (User)ois.readObject();
System.out.println(user);
}
catch (IOException | ClassNotFoundException e)
{ //멀티 캐치 : 부모자식간 사용 불가
// TODO: handle exception
e.printStackTrace();
}
}
오브젝트 입출력 예제 코드 2
리스트를 사용하여 여러개를 다룬다.
/**
* -객체 List에 담아 입출력.
*/
private void test2()
{
List<User> users = new ArrayList<>();
users.add(new User("honggd","1234",LocalDateTime.of(2023,10,10, 10, 10)));
users.add(new User("sinsa","1234",LocalDateTime.of(2023,10,9, 9, 9)));
users.add(new User("leess","1234",LocalDateTime.of(2023,10,8, 8, 8)));
//ObjectOutputStream - BufferedOutputStream - FileOutputStream
try
(
ObjectOutputStream oos =
new ObjectOutputStream(
new BufferedOutputStream(
new FileOutputStream("users.ser")));
)
{
oos.writeObject(users);
System.out.println("저장 완료...");
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
출력.
/**
* ObjectInputStream - BufferedInputStream - FileInputStream
*/
private void test3() {
try(
ObjectInputStream ois =
new ObjectInputStream(
new BufferedInputStream(
new FileInputStream("users.ser")));
)
{
List<User>users = (List<User>)ois.readObject();
System.out.println(users);
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
객체 입출력 시 사용한 User 클래스.
상단을 보면 Serializable을 구현하였다.
안하면 에러나고, 구현했다면
시리얼유니크아이디를 부여하라고 알려주는데 이건 사용자가 원하는 값을 지정할수있음.
또한 비밀번호같이 보안을 요하는/민감한 필드인 경우 타입명앞에 transient 라는 키워드를 붙일 수있음
직렬화 작업에 포함되지 않아 입출력 하고나면 null 값 인걸 알 수 있다..
package sh.java.io.user;
import java.io.Serializable;
import java.time.LocalDateTime;
public class User implements Serializable{ //Serializable:마커 인터페이스
/**
* serialVersionUID 객체 입출력 시에 정확한 타입인지를 체크하는 식별값
* -부여하지 않는다면 랜덤처리
*/
private static final long serialVersionUID = 1L;
private String name;
private transient String pw;
// 객체 입출력 시 값을 제외(필드는 존재하는데 값을 날림) 보안등의 문제로 제외하고싶을때..
//파일에 저장할때부터 없음 null
private LocalDateTime createAt;
//만약 커스텀 변수면 이것도 Serializable를 참조해야함..
//무조건 Serializable로 이루어져야함.
public User(String name, String pw, LocalDateTime createAt)
{
this.name = name;
this.pw = pw;
this.createAt = createAt;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
public LocalDateTime getCreateAt() {
return createAt;
}
public void setCreateAt(LocalDateTime createAt) {
this.createAt = createAt;
}
@Override
public String toString() {
return "User [name=" + name + ", pw=" + pw + ", createAt=" + createAt + "]";
}
}
그 동안은 scanner와 system.out.print만 다뤄봤는데
타입,목적에 따라 다양한 입출력 클래스를 사용할 수 있었다.
byte인지 string인지 혹은 객체를 사용할 것인지에 따라 표를 보고 사용해봐야겠다.
그리고 예외처리는 필수. 값을 입출력할때 비정상 종료 되는 일 없도록.