본문 바로가기
국비지원/JAVA

[JAVA] 22. API - I/O (InputStream/OutputStream)

by cosmog 2022. 10. 27.
반응형

File의 I/O와 경로에 대하여

경로에 대하여

🔹 dir구분 기호

절대 경로 : root로 부터의 위치
  • window : \
  • window이외 : /
상대 경로 : 현재 위치에서의 경로
  • ./  : 현재 위치
  • ../ : 상위 위치
  • /   : root 

 

🔹 java.io.File - method

exists() - 업데이트
lastModified - 날짜를 확인. (ex) 다르면 업데이트 진행)
length - 크기 비교하여 다르면 업데이트 진행.
renameTo - 파일이 이미 있는 경우 덮어쓰기 안하고 이름을 test(1)이런식으로 바꾸기

 

Stream
  • 입출력 할 때의 스트림이 있고
    • 입출력에서의 스트림 = 데이터를 운반하는데 사용하는 연결 통로이다.
  • 여러개의 데이터를 순차적으로 처리하기 위한 스트림이 있다.(람다와 스트림에서 처럼)

🔹 분류

  1. 입력 Stream / 출력 Stream
  2. 바이트 Stream / 문자 Stream
  • 일반적인 파일 처리는 바이트 Stream을 사용한다. (문자 이외의 것들도 전부 처리가 가능하기 때문)
  • 문자를 읽고 쓰는 경우에만 문자 Stream을 사용한다. 문자 Stream을 사용할 때는 인코딩에 주의해야 한다. ❗인코딩 주의

인코딩에 대하여

File에 대해서

package com.eb.fileinformation;

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

public class Main {
	
	public static void main(String[] args) {
		//파일에 대한 정보 확인
		try {
			//file 인스턴스 생성 - windows에서는 dir구분 기호가 \이다.
			// 프로그래밍 언어에서는 \가 오고 하나의 문자가 오면 제어문자로 인식하기 때문에 \\로 사용한다.
			File f = new File("C:\\Users\\user\\Desktop\\Test.txt");
			
			//파일의 존재 여부 확인
			System.out.println(f.exists());
			//마지막 수정 날짜
			System.out.println(f.lastModified());
			System.out.println(new Date(f.lastModified()));
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
		
	}

}

 

ByteStream

InputStream 과 OutputStream

  • 바이트 스트림의 최상위 클래스로 추상 클래스(인스턴스 생성 못하)
  • 다른 바이트 스트림들이 가져야 하는 메서드 원형을 소유

🔹 InputStream - method

int available() : 읽을 수 있는 바이트 수 리턴
void clise() : 닫기
int read() : 한 바이트를 읽어서 정수로 리턴하고 읽지 못하면 음수(-1)을 리턴
int read(byte[] b) : b배열 만큼 읽어서 b에 저장하고 읽은 개수를 리턴 -0이나 음스가 리턴되면 읽기 실패
int read(byte[] b, int offset, int length) : offset 부터 length만큼 읽어서 b에 저장하고 읽은 갯수를 리턴 
long skip(long n) : n만큼 넘기기


🔹 OutputStream - method

void close() : 닫기
void write(int n) : n을 기록
void write(byte[] b) : b배열을 기록
void write(byte[] b, int offset, int length) : b배열에서 offset에서 length만큼 기록
void flush() : 버퍼의 내용을 출력

 Stream은 열면 반드시 닫아야 한다.

 

package com.eb.bytefilterprocessing;

import java.io.FileOutputStream;
import java.io.PrintStream;

public class ByteBufferStream {

	public static void main(String[] args) {
		try {
			
			//버퍼를 이용해서 출력하는 Stream을 만들기 
			PrintStream ps = new PrintStream(new FileOutputStream("./buffer.txt"));
			
			ps.print("문자열을 버퍼를 이용해서 출력");
			ps.flush();
			ps.close();
			  
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}

	}

}

🔹 바이트 단위 파일 입출력 - FileInputStream 과 FileOutputStream 사용

  • 바이트 단위로 파일에 기록을 하는 class : FileOutputStream
  • FileOutputStream(File file) - 매번 새로 만들어진다.
  • FileOutputStream(File file, true) - 기존의 파일에 밑에 내용을 추가 하게된다.
package com.eb.bytefilterprocessing;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class Main {

	public static void main(String[] args) {
		//바이트 단위로 파일에 기록하기 - log 기록
		try {
			//오늘 날짜를 문자열로 만들기
			Date date = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			//기록할 파일 만들기
			FileOutputStream fos = new FileOutputStream("./"+sdf.format(date)+".txt", true);
			
			//기록할 메세지 생성 후 기록
			String msg = "hello\n";
			fos.write(msg.getBytes());
			
			//파일 닫기
			fos.close();
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
		
		//앞에서 작성한 파일 읽기
		try {
			FileInputStream fis = new FileInputStream("./2022-10-27.txt");
			
			//읽어서 저장할 바이트 배열을 생성
			byte[] b = new byte[fis.available()];
			fis.read(b);
			
			//출력
			System.out.println(new String(b));
			System.out.println(Arrays.toString(b ));
			//닫기
			fis.close();
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
	}

}

 Println 바로바로 출력하는것 print 버퍼 모아서 출력 하는 차이이다. (단순히 줄바꿈의 차이만 있는 것이 아니다) (print는 버퍼에 출력하는 method이다)

 

🔹 버퍼를 이용한 입출력

  • java 코드를 입출력하는 작업을 작성하면 실제로는 JVM이 운영체제의 Native Method를 호출해서 처리한다. 자주 입출력을 하는 경우 작업을 할 때마다 Native Method를 호출하면 시스템의 성능이 저하될 수 있다. 이런 경우 버퍼를 이용해서 입출력 횟수를 줄이는 것이 좋다.
  • 버퍼를 이용하는 바이트 단위 입출력은 BufferedInputStream(읽기)와 PrintStream(쓰기)를 이용한다. 버퍼에 기록을 할 때에는 write메서드 대신에 print method를 사용할 수 있으면 flush method를 이용해서 버퍼의 내용을 전부 출력할 수 있다.
  • 표준 출력 스트림이 System.out인데 이 스트림의 자료형이 PrintStream

 

Character Stream (문자 스트림)
  • ByteStream 을 이용하면 모든 종류의 IO장치와 데이터를 읽고 쓰기 작업이 가능 - 컴퓨터는 기본적으로 바이트 단위로 작업을 수행하기 때문입니다.
  • 문자 단위로 데이터를 입출력하는 경우 ByteStream을 사용하게 되면 변환 작업이 필요.

Reader & Writer

  • 문자 단위로 읽고 쓰기 위한 메서드를 제공하는 추상 클래스 - 문자 스트림의 최상위 클래스

🔹 Reader의 method

void close()
int read() 하나의 문자를 읽어오는 메서드로 문자의 코드를 리턴하는데 읽는데 실패하면 음수를 리턴
int read(char[] buf) 문자를 buf의 크기만큼 읽어서 buf에 저장하고 읽은 개수를 리턴 - 리턴값이 0보다 작거나 같으면 읽기 실패
int read(char[] buf, int offset, int length) 문자를 offset 부터 length만큼 읽어서 buf에 저장하고 읽은 개수를 리턴 
int read(CahrBuffer target)
boolean ready()
long skip(long n)


🔹 Writer Method

void close()
void flush()
void write(String str)
void write(String str, int offset, int length)
package com.eb.charaterstream;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;

//파일에 문자 단위로 기록하고 읽어오기
public class CharaterFileMain {

	public static void main(String[] args) {
		//파일에 문자열을 문자 단위로 기록하기
		try {
			//버퍼를 이용해서 파일에 문자열을 기록하는 클래스의 인스턴스를 생성.
			//한번 기록하고 두번째 기록할 때 이어서 기록하는 인스턴스
			//true를 생략하고 false를 설정하면 파일의 내용을 항상 새로 작성한다.
			PrintWriter pw = new PrintWriter(new FileWriter("ch.txt", false));
			
			//문자열을 기록
			//문자열을 기록할 때 , 또는 공백, 탭 등으로 구분이 가능한데 이와 같이 만들어진 텍스트를 csv라고 한다.
			//이 방식은 변하지 않는 고정적인 데이터를 기록할 떄 주로 이용
			pw.print("민지, 뉴진스\n");
			pw.print("도영, NCT\n");
			pw.print("사쿠라, 르세라핌\n");
			
			//버퍼의 내용 전부 출력
			pw.flush();
			pw.close(); //닫기
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
		
		//파일에서 문자열을 문자 단위로 읽어오기 : FileReader 와 BufferedReader 이용
		try {
			//FileReader fr = new FileReader("ch.txt");
			//BufferedReader br = new BufferedReader(fr);
			BufferedReader br = new BufferedReader(new FileReader("ch.txt"));
			
			while(true) {
				String line = br.readLine();
				if(line == null) {
					break;
				}
				//읽은 데이터 출력
				//System.out.println(line);

				String[] ar = line.split(",");
				System.out.println(ar[0]);
				System.out.println(ar[1]);
			}
			
			br.close();
			//fr.close();
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}

	}

}
package com.eb.charaterstream;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class WebTextDownload {

	public static void main(String[] args) {
		try {
			//읽어올 URL 생성
			URL url = new URL("https://www.naver.com");
			
			//URL에 연결
			HttpURLConnection con = (HttpURLConnection)url.openConnection();
			
			//문자열을 읽기 위한 스트림 생성
			BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
			
			while(true) {
				String line = br.readLine();
				if(line == null) {
					break;
				}
				System.out.println(line);
			}
			br.close();
			con.disconnect();
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
			// TODO: handle exception
		}
	}
}

 

InputStreamReader & OuputStreamReader (ByteStream)
  • ByteStream을 CharacterStream으로 변환시켜주는 클래스
  • 네트워크 프로그래밍에서 소켓은 ByteStream을 리턴하는데 주고 받는 데이터가 문자열이라면 ByteStream을 InputStreamReader 인스턴스로 변환해서 사용해야 한다. 
new InputStreamReader(ByteStream instance)

 

FileReader / FileWriter (CharacterStream)

파일에 문자 단위로 읽고 쓰기 위한 클래스

 

BufferedReader / PrintWrite
  • BufferedReader는 버퍼를 이용해서 문자를 읽어주는 클래스 (입출력 성능향상 스트림)
  • 이 클래스에는 String readLine()이라는 method가 제공되어 줄 단위로 읽어낼 수 있다. but, 읽어 낸 내용이 없다면 null을 return.
  • PrintWriter는 버퍼를 이용해서 문자 단위로 기록하는 클래스

 

etc

🔹파일에 문자 단위로 기록하고 읽어오기

  • 문자열을 기록할 때 , 또는 공백, 탭 등으로 구분이 가능한데 이와 같이 만들어진 텍스트를 csv라고 한다.
<문자열을 분할하는 방법>
특수한 문자를 기준으로 분할 
Stringp[ split(String 기분문자열) : 기준 문자열을 기준으로 문자열을 분할해서 문자열 배열로 리턴

String msg = "Hello, Java, World";
String[] splits = msg.split(",");
System.out.println(splits[0]); //Hello 
System.out.println(splits[1]); //Java 
System.out.println(splits[2]); // World 

//마지막 데이터 출력 - 확장자 제거하기와 같은 작업을 할 때 필요
System.out.println(splits[splits.length-1]);

//위치를 기준으로 분할 - 주민번호로 나이 계산, 성별 확인, 생일 확인과 같은 일들 가능.
subString사용. Document 확인을 해야 한다. (숫자를 몇개줄지)
String msg = "Hello, Java, World";
String result = msg.substring(6); //Java, World
String result1 = msg.substring(6, 10); //Java


🔹 Serializable (직렬화)

  • 인스턴스를 다른 곳에 전송할 수 있도록 해주는것
  • 전송한다는 의미는 파일 단위로 읽고 쓰는 것도 전송으로 간주하고 컴퓨터와 컴퓨터 또는 component와 component 사이를 왔다갔다 하는 것도 전송한다고 합니다.
    • 이럴 때 사용하는 값의 집합을 나타내기 위한 인스턴스를 DTO(Data Transfer Object)라고 합니다.
  • java에서는 인스턴스 단위로 데이터를 전송하고자 하면 Serializable 인터페이스를 implements하면 된다. (Override를 하지 않아도 되는 인터페이스다!! 띠용)
    • 별도의 method구현이 필요없다.
  • 응용프로그램을 만들 때 자신만의 저장 형식을 갖고자 하는 경우 사용하고 안드로이드에서 Activity 사이에 데이터 전송을 하고자 하면 사용한다.
  • DataInputStream과 DataOutputStream을 이용해서 사용한다.
  • 데이터를 전송하고 전송받을 때 데이터는 항상 하나로 만들어두는 것이 좋다.

 

🔆 직렬화를 위한 DTO 클래스 생성

  • 정수로 된 번호, 문자열로 된 이름, Data로 만들어진 생일을 저장.
package com.eb.serizlizable;

import java.io.Serializable;
import java.util.Date;

//인스턴스 단위로 읽고 쓰기가 가능한 클래스 - Serializable interface 때문
public class StudentDTO implements Serializable{

	private int num;
	private String name;
	private Date birthday;
	private String group;
	
	public StudentDTO() {
		super();
		
	}
	
	public StudentDTO(int num, String name, Date birthday, String group) {
		super();
		this.num = num;
		this.name = name;
		this.birthday = birthday;
		this.group = group;
	}


	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	public String getGroup() {
		return group;
	}

	public void setGroup(String group) {
		this.group = group;
	}

	@Override
	public String toString() {
		return "StudentDTO [num=" + num + ", name=" + name + ", birthday=" + birthday + ", group=" + group + "]";
	}
	
}

 

🔆 Main

  • 여러개의 객체를 write read하는 것은 전부 모아서 한번만 작업해주어야 한다 따라서 아래와 같이 list.add(인스턴스)를 여러줄 사용하는 것은 안된다.
package com.eb.serizlizable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Date;

public class Main {
	
	public static void main(String[] args) {
		try {
			//인스턴스 단위로 파일에 기록할 수 있는 객체를 생성
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("multiobject.txt"));
			
			//기록할 인스턴스 생성
			StudentDTO dto = new StudentDTO(1, "사쿠라", new Date(), "르세라핌");
			StudentDTO dto1 = new StudentDTO(2, "민지", new Date(), "뉴진스");
			
			//기록할 인스턴스가 여러개 이므로 하나의 List로 묶어줍니다. (write는 한번만 해주어야 한다.)
			ArrayList<StudentDTO> list = new ArrayList<>();
			list.add(dto);
			list.add(dto1);
			//기록
			oos.writeObject(list);
			oos.close();
		 
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
		
		try {
			//인스턴스 단위에 파일에 읽어낼 수 있는 객체를 생성
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("multiobject.txt"));
			
			ArrayList list = (ArrayList)ois.readObject();
			
			for(Object obj : list) {
				System.out.println(obj);
			}
			
			//하나의 데이터 읽어오기
			//StudentDTO dto = (StudentDTO) ois.readObject();
			//System.out.println(dto);
			
			ois.close();
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
	}

}
  • ArrayList로 한번에 모아서 writeObject(ArrayList)한번만 해주었다. read해 올때에도 ArrayList로 readObject하여 List를 한개씩 출력해볼 수 있다.
package com.eb.serizlizable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Date;

public class Main2 {
	
	public static void main(String[] args) {
		
		try {
			
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("try&resource.txt"));
            
			//기록할 인스턴스 생성
			StudentDTO dto = new StudentDTO(1, "사쿠라", new Date(), "르세라핌");
			StudentDTO dto1 = new StudentDTO(2, "민지", new Date(), "뉴진스");
			
			//기록할 인스턴스가 여러개 이므로 하나의 List로 묶어줍니다. (write는 한번만 해주어야 한다.)
			ArrayList<StudentDTO> list = new ArrayList<>();
			list.add(dto);
			list.add(dto1);
			//기록
			oos.writeObject(list);
            oos.close();
		 
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
		
		try {
			//인스턴스 단위에 파일에 읽어낼 수 있는 객체를 생성
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("try&resource.txt"));
			
			ArrayList list = (ArrayList)ois.readObject();
			
			for(Object obj : list) {
				System.out.println(obj);
			}
			
			//하나의 데이터 읽어오기
			//StudentDTO dto = (StudentDTO) ois.readObject();
			//System.out.println(dto);
            ois.close();
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
	}

}

 

🔹 데이터의 분류

1) scala data(하나의 데이터) : Byte, Short, Charater, Integer, Long, Float, Double, Boolean + 자바는 String도 하나의 데이터로 본다.
2) Vector data (여러개의 데이터) 
* 배열과 List(Collection) : 동일한 종류의 비교 가능한 데이터 , 인덱스를 이용해서 데이터를 구분   
* VO 클래스나 Map : 여러개의 데이터를 하나로 묶을 떄 사용, 이름을 이용해서 데이터를 구분 

🔹 try - resource 

  • jdk 1.7에서부터 지원되는 문법
  • Stream은 만들면 반드시 close를 해줘야 한다. 하지 않으면 메모리 누수가 발생한다.
  • 아래와 같은 try-resource문법으로 자동으로 Stream을 close하도록 해줄 수 있다.
try(여기서 만든 객체가 AutoCloseable 인터페이스를 implements 하고 있다면 close 하지 않아도 try-catch 구문을 벗어나면 자동적으로 close를 호출합니다.){

}catch(Exception e){
}

 

  • 위의 Serializable에서 사용한 코드를 재사용해서 예를 들면 try - chatch 안에서 만든 ObjectOutputStream을 try옆에()안으로 옮겨주고 close를 해주지 않아도 try()안에 넣어준것은 자동으로 close()된다.
package com.eb.serizlizable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Date;

public class Main2 {
	
	public static void main(String[] args) {
		
		//따로 close를 해주지 않아도 자동적으로 close하도록 하는 기능.
		try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("try&resource.txt"));){
			
			//기록할 인스턴스 생성
			StudentDTO dto = new StudentDTO(1, "사쿠라", new Date(), "르세라핌");
			StudentDTO dto1 = new StudentDTO(2, "민지", new Date(), "뉴진스");
			
			//기록할 인스턴스가 여러개 이므로 하나의 List로 묶어줍니다. (write는 한번만 해주어야 한다.)
			ArrayList<StudentDTO> list = new ArrayList<>();
			list.add(dto);
			list.add(dto1);
			//기록
			oos.writeObject(list);
		 
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
		
		try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("try&resource.txt"));){
			//인스턴스 단위에 파일에 읽어낼 수 있는 객체를 생성
			
			ArrayList list = (ArrayList)ois.readObject();
			
			for(Object obj : list) {
				System.out.println(obj);
			}
			
			//하나의 데이터 읽어오기
			//StudentDTO dto = (StudentDTO) ois.readObject();
			//System.out.println(dto);
			
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
	}

}



반응형