개발/Java

[Java] 데이터 입출력

yun000 2024. 12. 6. 14:44

<입출력 스트림>

프로그램에서 데이터가 나가면 출력 스트림

프로그램에 데이터가 들어오면 입력 스트림

 

프로그램끼리 데이터를 교환하려면 양쪽 모두 입력 스트림과 출력 스트림이 필요하다.

 

스트림=단방향으로 데이터가 흐르는 것

 

스트림의 종류

바이트 스트림 = 그림, 멀티미디어, 문자 등 모든 종류의 데이터 입출력할때 사용 (InputStream-입력,OutputStream-출력)

문자 스트림 = 문자만 입출력할 때 사용 (Reader-입력,Writer-출력)

<바이트 출력 스트림>

바이트 단위로 출력하는 스트림

OutputStream클래스 주요 메서드

void write(int b) 4byte인 int에서 제일 끝 1 byte만 출력
void write(byte[ ] b) 매개값으로 주어진 배열b의 모든 바이트 출력
void wrtie(byte[ ] b,int off,int len) 배열 b의 len개의 바이트 출력. 배열 일부분만 출력
flush() ⭐ 출력 버퍼에 잔류하는 모든 바이트 출력

모든 출력스트림은 버퍼가 있다.
write하면 버퍼에 메모리를 쌓는다
그러다가 버퍼가 꽉찼을 때 데이터를 목적지로 보내게 된다.
하지만 보통 버퍼가 커서 데이터를 다 채우는 것이 오래 걸린다.
그래서 조금이라도 찬거 보내기 위해 flush사용

목적지로 보내는 행위를 줄이면 속도가 빨라지기 때문에 이렇게 한다.
각각 하나씩 보내는 것 보다 묶어서 보내는게 전송 비용 더 저렴

close() 출력 스트림을 닫고 사용 메모리 해제.
어떤 프로그램이 파일을 사용중이면 다른 프로그램은 쓸 수 없다
그렇기에 사용 후에는 다른 프로그램들이 사용할 수 있게 하기 위해서 close한다.

write하고 flush안한 다음 close하면 
자동으로 flush하고 close된다.

int에서 byte로 변경

 

FileOutputStream은 OutputStream의 write메서드를 재정의 해서 만든다

그렇기에 write를 쓰는 방법은 같으나 실행 결과나 속도는 다 다르다.

 

write 메소드 사용

package ch18.sec02.exam02;

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

public class WriteExample 
{
	public static void main(String[]args)
	{
		try 
		{	
        		//BYTE ARRAY 전체 출력
			OutputStream os=new FileOutputStream("C:/Temp/test0.db");	
			byte[] arr= {10,20,30};
			os.write(arr);
			os.flush();//os.close();에서 자동으로 flush되므로 생략해도 문제 없음
			os.close();
            
            		//BYTE ARRAY 부분 출력
            		OutputStream os2=new FileOutputStream("C:/Temp/test3.db");	
			byte[] arr2= {0,10,20,30,40,50,60,70,80,90,100};
			os2.write(arr2,2,3);//2index에 시작하여 3개만
			os2.flush();
			os2.close();
		} 
		catch (FileNotFoundException e) 
		{	e.printStackTrace();	}
		catch (IOException e)
		{ e.printStackTrace(); }
	}
}

 

스트림 기반 입출력 처리

write()메소드 - OutputStream은 내부에 작은 버퍼를 가지고 있다. 그 버퍼에 바이트를 저장한다.

flush()메소드 - 출력 버퍼에 잔류하는 모든 바이트 출력

close()메소드 - 출력 스트림 사용 메모리 해제

<바이트 입력 스트림>

InputStream 은 '바이트 입력 스트림'의 최상위 클래스이다.

InputStream 클래스 주요 메서드

method 설명
int read() 입력 스트림으로 부터 1 byte를 읽은 후 int타입으로 return 
더 이상 입력 스트림으로부터 바이트 읽을 수 없다면 -1 return
int read(byte[]b)⭐ 한번에 모든 byte를 읽고 b 배열에 저장. 그 후 실제로 읽은 byte수 return
void close() 입력 스트림을 닫고 사용 메모리 해제

InputStream에서 실제 읽은 양과 요청한 양(버퍼 크기 등)은 차이가 있을 수 있습니다.

이 차이는 스트림의 데이터가 아직 완전히 준비되지 않았거나, 남은 데이터의 양이 요청한 양보다 적을 때 발생

 

1 Byte 읽기

read()메소드는 입력스트림으로부터 int타입으로 리턴

리턴된 4byte중 끝 1byte에만 데이터가 들어 있다.

ex) 입력스트림에서 5개의 바이트 들어온다면 read()로 1byte씩 5번 읽을 수 있다.

package ch18.sec02.exam02;
import java.io.FileInputStream;
import java.io.InputStream;

public class ReadExample {
	public static void main(String[] args)
	{
		try
		{
			InputStream is=new FileInputStream("C:/Temp/test0.db");
			byte[] data=new byte[100];
			while(true)
			{
				int num=is.read(data);
				if(num==-1) break;
				System.out.println(data);
			}
			is.close();
		}
		catch(Exception e)
		{ e.printStackTrace();}
	}
}

 

바이트 배열로 읽기

read(byte[] b) - 입력 스트림으로부터 주어진 배열 길이만큼 바이트를 읽고 배열에 저장 후 읽은 바이트 수 리턴

 

ex)입력 스트림에 5개의 바이트가 들어온다 한번에 최대3byte를 읽도록 하게 한다면

처음에는 5byte중 3byte읽고 그 다음에는 2byte를 읽는다

그런데 나머지 하나는 원래 첫번째에 있던 값이다..

 

- 파일 내용 출력

package ch18.sec03;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class ReadExample 
{
	public static void main(String[] args) 
	{
		InputStream is;
		try 
		{
			is = new FileInputStream("C:/Temp/test2.db");
			byte[] data=new byte[100];
			while(true)
			{
				int num=is.read(data);
				if(num==-1) break;
				
				for(int i=0;i<num;i++) {System.out.println(data[i]);}
			}
			is.close();
		} 
		catch (Exception e) 
		{ e.printStackTrace(); }
	}
}

 

- 파일 복사 예제

package ch18.sec02.exam03;
import java.io.*;
public class CopyExample 
{
	public static void main(String[] args)
	{
		String oFileName="C:/Temp/image1.png";
		String tFileName="C:/Temp/image2.png";
		
		try 
		{
			InputStream is=new FileInputStream(oFileName);
			OutputStream os=new FileOutputStream(tFileName);

			byte[] data=new byte[1000];//byte너무 크면 메모리 너무 많이 쓰는 거라 안좋음
			while(true)
			{
				int num=is.read(data);
				if(num==-1)break;
				os.write(data,0,num);
			}
			//is.transferTo(os);//위의 코드와 같은 역할을 한다.
			os.flush();
			os.close();
			is.close();
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
}

<문자 입출력 스트림>

[Writer]

writer은 문자 출력 스트림의 최상위 클래스로 추상클래스이다.

Writer클래스의 주요 메소드

void write(int c) 매개값으로 주어진 한 문자를 출력
void write(char[] cbuf) 매개값으로 주어진 배열의 모든 문자 출력
void write(char[] cbuf,int off,int len) 매개값으로 주어진 배열에서 cbuf[off]부터 eln개까지의 문자 출력
void write(String str) 매개값으로 주어진 문자열 출력
void write(String str,int off,int len) 매개값으로 주어진 문자열에서 off순번부터 eln개의 문자 출력
void flush() 버퍼에 잔류하는 모든 문자 출력
void close() 출력 스트림을 닫고 사용 메모리 해제

write는 OutputStream과 사용 방법은 동일하지만 출력 단위가 char이다.

 

메소드 사용 예제

package ch18.sec04;
import java.io.*;

public class WriterExample 
{
	public static void main(String[] args)throws Exception //JVM이 예외 처리한다
	{
		Writer writer=new FileWriter("C:/Temp/test1.txt");
		String data="오늘은 금요일. 주말에 준비하느라 바빠";
		writer.write(data);
		writer.flush();
		writer.close();
	}
}

[Read]

Reader는 문자 입력 스트림의 최상위 클래스로 추상 클래스이다.

Reader클래스의 주요 메서드

int read() 1개 문자 읽고 return
int read(char[] cbuf) 읽은 문자들을 매개값으로 주어진 문자배열에 저장하고 읽은 문자 수 return
void close() 입력 스트림 닫고 사용 메모리 해제

문자열 사용

import java.io.FileReader;
import java.io.Reader;

public class ReadExample 
{
	public static void main(String[] args) 
	{
		try 
		{
			Reader reader=new FileReader("C:/Temp/test.txt");
			
			//1char씩 읽기
			while(true)
			{
				int data=reader.read();
				if(data==-1)break;
				System.out.print((char)data);
			}
			reader.close();
			System.out.println();
			
			
			//문자 배열 읽기
			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.println(data[i]);}
			}
			reader.close();
		}
		catch(Exception e) {e.printStackTrace();}
	}
}

 

<보조 스트림>

다른 스트림과 연결되어 편한 기능 제공하는 스트림.

보조스트림 단독으로는 사용 못함. 주 입출력스트림이 있어야 달아서 사용 가능

 

사용 이유

1. 성능. 프로그램 입장에서 더 빠르게 입출력 가능

2. 편리함

 

- 입출력 스트림과 이어주는 법

보조스트림 변수=new 보조스트림(입출력스트림);

보조 스트림 생성시 생성자 매개값으로 입출력 스트림 제공

ex) FileInputStream(바이트 입력 스트림)에 INputStreamReader(보조 스트림)연결

InputStream is=new FileInputStream("...");
InputStreamReader reader=new InputStreamReader(is);

 

- 보조스트림에 보조스트림 또 달아서 스트림 체인 구성 가능

보조스트림2 변수=new 보조스트림2(보조스트림1);

보조스트림끼리 이어주는 법

InputStream is=new FileInputStream("...");
InputStreamReader reader=new InputStreamReader(is);
BufferedReader br=new BufferedReader(reader);

 

- 자주 사용하는 보조 스트림
+) 입력 스트림이나 출력스트림은 단방향이라서 그 용도로만 쓸 수 있다.

보조 스트림 기능
InputStreamReader 바이트 스트림을 문자 스트림으로 변환
BufferedInputStream, BufferedOutputStream
BufferedReader, BufferedWriter
입출력 성능 향상
DataInputStream, DataOutputStream 기본 타입 데이터 입출력
PrintStream,PrintWriter 줄바꿈 처리, 형식화된 문자열 출력

System.out.println()이 PrintStream 사용한다.
class System{ static final PrintStream out=...}
여기서 PrintStream이 제공하는 println사용.

ObjectInputStream, ObjectOutputStream 객체 입출력
보내는쪽 받는 쪽 둘다 java로 만들어져있는 경우
객체를 주고 받을 수 있음. 잘 안쓰임

 

<문자 변환 스트림>

기본이 되는 스트림은  Byte Stream이다.

입출력 데이터가 문자면 문자 스트림 (Reader Writer)로 바꿔서 사용하는 것이 좋다.

 

InputStream을 Reader로 변환

InputStreamReader 보조스트림을 연결하면 된다.

InputStream is =new FileInputStream("C:/Temp/test.txt");
Reader reader=new InputSTreamReader(is);

 

OutputStream을 Writer로 변환

OutputStreamWriter 보조 스트림을 연결하면됨

OutputStream os=new FileOutputSTream("C:/Temp/test.txt");
Writer writer=new OutputStreamWriter(os);

 

+) FileWriter 원리

FileOutputStream에 OutputStreamWriter 연결하지 않고 FileWriter직접 생성 가능.

FileWriter는 OutputStreamWriter의 자식 클래스.

이것은 FileWriter가 내부적으로 FileOutputStream에 OutputStreamWriter보조 스트림을 연결한 것임을 의미.

 

<성능 향상 스트림>

1. 프로그램에서 데이터 전송할 때

CPU, 메모리가 좋아도 하드 디스크 입출력이 늦어지면 프로그램 실행성능은 하드 디스트 처리 속도에 맞춰진다.

프로그램이 1초에 10000자를 전송할때 하드디스트가 1초에 1000자를 저장할 수 있으면

프로그램은 1000자씩 보내야한다. 이런식으로 10000자 다 보내려면 10초가 필요하다.

이걸 개선하기 위해 버퍼를 사용한다.

프로그램은 메모리 임의의 저장공간(버퍼)에 10000자 데이터 보내놓고 다른일을 한다.

그리고 메모리 버퍼에서는 하드 디스크가 감당할 수 있는 1000자씩 보내준다.

 

이런 방식으로 메모리 버퍼를 사용해서 프로그램의 출력 성능을 높일 수 있다.

 

2. 프로그램이 데이터를 받을 때

프로그램에서 데이터 입력받을 경우를 생각해보자

입력소스에서 직접 입력 받을 것인데 입력소스는 1초에 1000자를 준다고 치자

프로그램은 1초에 10000자를 처리할 수 있다.

근데 1000자씩 밖에 주지 않으니 10000자를 받으려면 프로그램은 10초를 기다려야한다.

그래서 메모리 버퍼에 입력소스가 보내는 1000자들을 쌓은 뒤

프로그램이 데이터를 필요로 할때 메모리 버퍼에서 왕창 읽어내면 훨씬 효율적이다.

 

버퍼를 제공하는 보조스트림

BufferedInputStream, BufferedOutputStream - 바이트 스트림

BufferedReader, BufferedWriter - 문자 스트림

 

ex) Buffer쓸때의 성능 차이 확인해보기 예제

간단한 출력은 버퍼를 쓰지 않아도 되지만, 보통 버퍼를 쓴경우 안쓴 경우보다 훨씬 빠르다

package ch18.sec07;
import java.io.*;

public class BufferExample {
	public static void main(String args[]) throws Exception
	{
		String oFile1="C:/Temp/image1_1.png";
		String tFile1="C:/Temp/image1_2.png";
		String oFile2="C:/Temp/image2_1.png";
		String tFile2="C:/Temp/image2_2.png";
		
		//No Buffer
		FileInputStream fis =new FileInputStream(oFile1);
		FileOutputStream fos=new FileOutputStream(tFile1);
		
		// Use Buffer
		BufferedInputStream bis=new BufferedInputStream(new FileInputStream(oFile2));
		BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(tFile2));
		
		//test
		long nonBufferTime=copy(fis, fos);
		System.out.println(nonBufferTime);
		
		long bufferTime=copy(bis, bos);
		System.out.println(bufferTime);
	}
	public static long copy(InputStream is, OutputStream os) throws Exception 
	{
		long start=System.nanoTime();
		while(true) 
		{
			//1byte씩 읽기
			int data=is.read();
			if(data==-1)break;
			os.write(data);
		}
		long end=System.nanoTime();
		return end-start;
	}
}

 

ex) 편리한 기능 관련 예제

printCode1, printCode2를 보면 printCode2가 더 편리한 것을 알 수 있다.

package ch18.sec07;
import java.io.*;
public class ReadLineExample 
{
	public static void main(String[] args) throws Exception 
	{
		//Test
		printCode1();
		System.out.println("------------------");
		printCode2();
	}
	
	public static void printCode1() throws Exception
	{
		//현재 파일이 저장된 위치 지정
		Reader reader=new FileReader("C:\\metanet\\projects\\proj\\src\\ch18\\sec07\\ReadLineExample.java");
		char[]data=new char[100];
		while(true) 
		{
			int num=reader.read(data);
			if(num==-1)break;
			String str=new String(data,0,num);
			System.out.print(str);
		}
		reader.close();
	}
	
	public static void printCode2() throws Exception 
	{
		Reader reader=new FileReader("C:\\metanet\\projects\\proj\\src\\ch18\\sec07\\ReadLineExample.java");
		BufferedReader br=new BufferedReader(reader);
		while(true) 
		{
			String line=br.readLine();//더 편리하다. 
			if(line==null) break;
			System.out.println(line);
		}
		br.close();
	}
}

 

 

<기본 타입 스트림>

바이트 스트림에 보조스트림(DataInputStream, DataOutpuStream) 연결하면 기본타입값 입출력 가능.

DataInputStream dis=new DataInputStream(바이트 입력 스트림);
DataOutputStream dos=new DataOutputStream(바이트 출력 스트림);

 

정수값(int value=100)을 1바이트씩 분리해서 byte[]배열에 저장하는 것은 까다롭다

DataIntputStream,DataOutputStream을 사용해서 처리하면 수월하게 가능하다

 

<프린트 스트림>

문자 데이터 출력할때 주로 사용

출력스트림(PrintStream,PrintWriter)만 있고 입력 스트림 없음

PrintStream,PrintWriter는 print(),println(),printf()메소드를 가지고 있는 보조 스트림이다.

 

print(Object obj) = obj.toString() return 값을 출력한다.

package ch18.sec09;
import java.io.*;

public class printStreamExample 
{
	public static void main(String[] args) throws Exception 
	{
	      //FileOutputStream fos = new FileOutputStream("C:/Temp/printstream.txt");
	      //PrintStream ps = new PrintStream(fos);
	      
	      Writer writer=new FileWriter("C:/Temp/printstream.txt");
	      PrintWriter ps=new PrintWriter(writer);
	      
	      ps.print("마치 ");
	      ps.println("프린터가 출력하는 것처럼 ");
	      ps.println("데이터를 출력합니다.");
	      ps.printf("| %6d | %-10s | %10s | \n", 1, "홍길동", "도적");
	      ps.printf("| %6d | %-10s | %10s | \n", 2, "감자바", "학생");
	      
	      ps.flush();
	      ps.close();
	 }
}

 

<File과 Files클래스>

java.io =단방향. 대부분 이 기능으로 만들어져 있음

java.nio =양방향. 성능 더 좋음

 

java.io, java.nio 패키지는 파일과 디렉토리 정보를 가지고 있는 File과 File 클래스를 제공한다.

Files는 File을 개선한 클래스로 더 많은 기능을 가지고 있다.

 

1. File 클래스

File클래스로부터 File객체를 생성하는 코드

해당 경로를 가지고 file객체를 만들것이라는 것.

굳이 파일경로에 파일이 없어도 됨.

File file=new File("파일 경로");

 

윈도우에서 File객체 생성하는 코드

File file=new File("C:/Temp/file.txt");
File file=new File("C:\\Temp\\file.txt");
boolean isExist=file.exists();

 

파일이 없을 경우 (file.exists()가 false) 파일, 폴더 생성

return type method explanation
boolean createNewFile() 새로운 파일 생성
File file=new File("C:/Temp/dir1/dir2/dir3");
boolean mkdir() 새 디렉토리 생성
dir1,dir2이 존재한다면 dir3을 새로만든다.
boolean mkdirs() 경로상에 없는 모든 디렉토리 생성
dir1,dir2,dir3중에 존재하지 않는 것들 다 생성

 

파일이 있을 경우 (file.exists()가 true) 사용할 수 있는  중요한 메소드들

boolean delete() 파일, 디렉토리 삭제
boolean canExecute() 실행가능한 파일인지
String getParent() 부모 디렉토리 경로 return
File getParentFile() File객체 return
boolean isDirectory() 디렉토리인지 여부
long length() 파일 크기 return
File[] listFiles(FilenameFilter filter) 디렉토리 내부의 모든 파일 목록 return.
FilenameFilter로 확장명이 png인것만 가져오기 같이 조건 설정 가능

 

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("C:/Temp/images");
      File file1 = new File("C:/Temp/file1.txt");
      File file2 = new File("C:/Temp/file2.txt");
      File file3 = new File("C:/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("C:/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();
      }
      
      dir.delete();//directory비어있을 때만 삭제 가능!
      file1.delete();
   }
}

 

2. Files 클래스

정적 메소드. 객체없이 쓸 수 있음

기능 method
복사 copy
생성 createDirectories, createDirectory, createFile, createLink
이동 move
삭제 delete
존재, 검색, 비교 exists, notExists,find,mismatch
속성 size
디렉토리 탐색 list
데이터 입출력 newInputStream등..

 

Path path=Paths.get("C:/Temp/dir/file.txt");
Path path=Paths.get("C:/Temp/dir","file.txt");
Path path=paths.get("C:","Temp","dir","file.txt");

Path path=Paths.get("dir/file.txt");
Path path=Paths.get("./dir/file.txt");

Path path=Paths.get("../dir2/file.txt");