[java] 자바 파일 입출력에 대하여

개요

  • 자바는 byte 타입과 char 타입의 입출력 방식을 제공한다.
  • byte 타입은 inputstream 과 outputstream, char 타입으로는 reader 와 writer 가 있다. 하나씩 알아보자

byte 타입

  • inputstream 과 outputstream 은 byte 단위로 읽어들이고 쓰기를 한다.
  • 다음은 파일을 읽고 쓰는 예제이다. 한 바이트씩 읽고, 한바이트씩 쓰기를 한다.
  • byte로 읽으면 마지막 부분을 알 수 없기 때문에, int로 변환하여, 읽어드릴 데이터가 없으면 -1이 된다.
  • 1byte 씩 읽어, int의 4byte 중에 마지막 byte 에 값을 할당한다. 소스코드상에는 1byte 씩 읽어오는 것이지만, 실제로는 512 바이트(?) 씩 읽어오고, 한 바이트씩 가져온다음에 나머지는 버린다고 한다. byte[] 로 버퍼를 정의하여 벌크로 가져오는 개선된 방식을 알아보자.
    try(FileInputStream fis = new FileInputStream("src/com/sk/core/file/FileHandle.java");
			FileOutputStream fos = new FileOutputStream("output.txt");){
			int readData = -1; // 1byte 씩 읽어, int 4byte 의 마지막 1byte 에 할당한다. 
			while((readData = fis.read())!= -1){
				fos.write(readData);
			}
		}
  • byte[] 에 선언된 크기만큼 가져온다. 위의 예제와는 달리 반환값은 전체 읽어들여온 바이트수이다. 읽여들어온 바이트수만큼 쓰기를 한다.

//read를 버퍼만큼 읽어온다. 읽어온 byte 는 buffer 에 담기고, write 를 통해 쓰기된다. 

    try(FileInputStream fis = new FileInputStream("src/com/sk/core/file/FileHandle.java");
			  FileOutputStream fos = new FileOutputStream("output.txt");){
			int readCount = 0; // 1byte 씩 읽어, int 4byte 의 마지막 1byte 에 할당한다. 
			byte[] buffer = new byte[1024];
			while((readCount = fis.read(buffer))!= -1){
				fos.write(buffer, 0, readCount);
			}
		}

  • BufferedInputStream 은 내부적으로 버퍼를 갖고 있어, 조금 더 성능 향상을 가져온다고 한다.
    try(InputStream fis = new BufferedInputStream(new FileInputStream("D:\\temp\\test\\a.exe"));
				  OutputStream fos = new BufferedOutputStream(new FileOutputStream("D:\\temp\\test\\b.exe"));){
				int readCount = 0; // 1byte 씩 읽어, int 4byte 의 마지막 1byte 에 할당한다. 
				byte[] buffer = new byte[1024];
				while((readCount = fis.read(buffer))!= -1){
					fos.write(buffer, 0, readCount);
				}
			}
  • 다음과 같이 Files 를 이용하여 파일 복사를 할 수 있다.
  • NIO.2 API(jdk7~) low level 의 시스템 진입점을 사용하기 때문에 파일 복사 기능을 크게 높일 수 있다.
  • 실제 테스트를 해보니 가장 성능이 좋았다. File.copy > Buffered.. > FileInputStream
    Path path = Paths.get("src/com/sk/core/file/FileHandle.java");
		Path target = Paths.get("output.txt");
		//StandardCopyOption.REPLACE_EXISTING : 이미 존재하는 파일일 경우 덮어쓰기
		Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);

char 타입

  • Reader 와 Writer 는 char 타입으로 읽기 쓰기를 한다.
  • 아래는 한 글자씩 읽어들이고, 한글자씩 쓰기를 한다.
    try(FileReader fr = new FileReader("src/com/sk/core/file/FileHandle.java");
			FileWriter fw = new FileWriter("output.txt");){
			int readChar = 0;
			
			while((readChar = fr.read()) != -1) {
				System.out.println((char)readChar);
				fw.write(readChar);
			}
		}
  • 보통은 한 줄 씩 읽어들인다.
  try(FileReader fr = new FileReader("src/com/sk/core/file/FileHandle.java");
				BufferedReader br = new BufferedReader(fr);
			FileWriter fw = new FileWriter("output.txt");
				BufferedWriter bw = new BufferedWriter(fw)
				){
			String readLine = "";
			
			while((readLine = br.readLine()) != null) {
				System.out.println(readLine);
				bw.write(readLine + "\n");
			}
		}
  • write 씩 개행문자를 넣어주었는데, PrintWriter 를 써보자.
    try(FileReader fr = new FileReader("src/com/sk/core/file/FileHandle.java");
				BufferedReader br = new BufferedReader(fr);
				PrintWriter pw = new PrintWriter("output.txt");
				){
			String readLine = "";
			
			while((readLine = br.readLine()) != null) {
				System.out.println(readLine);
				pw.println(readLine);
			}
		}

Files

  • Files 를 이용해 line 읽기
  • 한번에 읽어오는 것과 stream 으로 지연처리(권고) 하는 방식이 있다.
    Path path = Paths.get("src/com/sk/core/file/FileHandle.java");
		
		List<String> readAllLines = Files.readAllLines(path, StandardCharsets.UTF_8);
		for(String str : readAllLines) System.out.println(str);
		
		Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);
		lines.forEach(System.out::println);
  • InputStream 을 Reader 로 OutputStream 을 Writer 로 생성가능하다.
    URL url = new URL("https://www.idblife.com/");

		try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
				OutputStream out = new FileOutputStream("output.txt");
				Writer writer = new OutputStreamWriter(out);) {
			Stream<String> lines = reader.lines();
			lines.forEach(line -> {
				try {
					writer.write(line + "\n");
				} catch (IOException e) {
					e.printStackTrace();
				}
			});
		}
  • Files.newBufferedWriter를 사용할 수 있다. Files.newBufferedWriter 는 BufferedWriter 를 반환하고, PrintWriter 는 Writer를 가지고 생성한다.
    URL url = new URL("https://www.idblife.com/");
		Path path = Paths.get("out.txt");

		try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
				PrintWriter pw = new PrintWriter(Files.newBufferedWriter(path, StandardOpenOption.CREATE_NEW));
				) {
			Stream<String> lines = reader.lines();
			lines.forEach(line -> {
				pw.println(line);
			});
		}
  • String 을 File로 쓰고 싶다면, Files.write를 사용해도 된다.
    Path path = Paths.get("out.txt");
		
		String str = "tesxt";
    //StandardOpenOption.APPEND : 쓰기용으로 열었을때 파일의 끝에 추가한다. 
		Files.write(path, str.getBytes(), StandardOpenOption.APPEND);
		
		String str2 = "tesxt2";
		Files.write(path, str2.getBytes(), StandardOpenOption.APPEND);
  • List 를 파일로 쓸수도 있다.
Files.write(path, Arrays.asList("1", "2", "a"), StandardOpenOption.APPEND);
  • Files.readAllBytes 를 이용해, 파일을 byte 로 모두 가져올 수 있다.
  • DataOutputStream, ObjectOutputStream를 활용하는 방법.

DataOutputStream, ObjectOutputStream 간단 예제

    byte[] body = Files.readAllBytes(Paths.get("out.txt"));
		
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("result.txt"));
		dos.writeBytes(new String(body));
		
		dos.close();
		
		Map<String, String> map = new HashMap<>();
		map.put("a", "1");
		map.put("b", "2");
		
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("map.obj"));
		oos.writeObject(map);
		oos.close();
		
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("map.obj"));
		Object readObject = ois.readObject();
		Map<String, String> readMap = (Map)readObject;
		System.out.println(readMap.get("a"));

파일 잠금

  • FileLock 을 사용하면 되는데, 지금까지 개발하면서 파일잠금 처리하면서 파일쓰기 작업을 했던 적은 없던것 같다.

Reference

Categories:

Updated:

Leave a comment