코딩 기록들

[Java Programming] 12.2 NIO (Java1.8 이후 File I/O ) , 재귀호출 본문

Java

[Java Programming] 12.2 NIO (Java1.8 이후 File I/O ) , 재귀호출

코딩펭귄 2024. 2. 2. 17:38

 

Java 1.8 이전까지는 I/O가 매우 어렵고 복잡했으나, Java 1.8 부터(NIO) 비교적 간편해졌으며, 코드를 분석하기도 쉬워짐

 

IO / NIO 차이점
  IO NIO
입출력 방식 스트림 방식 채널 방식
버퍼 방식 non-buffer buffer (버퍼)
비동기 방식 지원x 지원
블로킹/넌블로킹 방식 블로킹 방식만 지원 블로킹/넌블로킹 모두 지원

 

스트림 vs 채널

- 스트림 기반(IO) : 데이터를 읽기 위해서 입력스트림 생성 & 출력위해서는 출력스트림 생성

- 채널기반(NIO) : 양방향 입력과 출력이 가능. (입출력을 위한 별도의 채널 만들필요 x)

버퍼 vs 넌버퍼

- 버퍼 사용 (IO) : 보조스트림인 BufferedInput(output)Stream을 연결해서 복수개의 바이트를 한번에 입력받고 출력함

- NIO : 기본적으로 버퍼를 사용해서 입출력 함 - 채널 : 버퍼에 저장된 데이터출력 & 입력된 데이터 버퍼에 저장

 

NIO의 파일 경로 정의 

경로정의  (Path)

-> path 구현객체 얻기 위해서는 get() 메소드 호출

- get메소드의 매개값= 파일의 경로 -> 문자열, URI, 상위 & 하위 디렉토리로 지정할 수 있음

Path path = Paths.get(String first, String...more);

Path path = Paths.get(Uri uri)

 

 

아래 코드들은, 굉장히 많이 사용하므로 외워둘것 ~!

Java 1.8버전 이상에서 사용할 수 있는 파일 '읽기' 
/**
 * Java 1.8버전 이상에서 사용할 수 있는 파일 읽기 예제
 * new IO (nio) 사용
 * (이 코드 내용은 완전히 외울것)
 */
public class NewIOReadAndPrintExam {

	public static void main(String[] args) {
		File file  = new File("C:\\Java Exam", "Java Exam.txt");
		if (file.exists() && file.isFile()) {
			List<String> fileLine = new ArrayList<>();
			try {
				// 파일을 처음부터 끝까지 모두 읽어와 List에 할당
//				Path filePath = Paths.get("C:\\JavaExam", "Java Exam.txt");
//				Charset utf8 = Charset.forName("UTF-8");
//				fileLine.addAll(Files.readAllLines(filePath, utf8));
				//위의 3줄을 toPath를 이용해서 아래 한줄로 적어준다
				fileLine.addAll(Files.readAllLines(file.toPath(), Charset.forName("UTF-8")));
			}
			catch( IOException ioe ) {
				//파일을 읽다가 예외가 발생했을때 예외 내용만 출력
				System.out.println(ioe.getMessage());
			}
			//읽어온 파일을 모두 출력
			for (String line : fileLine) {
				System.out.println(line);
			}
		}
	}
	
}
Java 1.8버전 이상에서 사용할 수 있는 파일 '쓰기'
public class NIOWriteExam {

	public static void main(String[] args) {
	
		//이어쓰기 위한 변수 추가
		boolean append = true;
		
		//파일이 있는지 물어보는것
		File file = new File("C:\\outputs2", "java_output2.txt");
		if( ! file.getParentFile().exists()) { //file.getParentFile() = C:\\java\\outputs 
			file.getParentFile().mkdirs(); // mkdirs : make directories
			// mkdir => java만 만들어주고, mkdirs => java, outputs 둘다 만들어줌(이걸사용)
		}	
		
		//이어서쓰는게 아니라면 파일명 새로 작성하기 위해 아래코드 작성
		if( ! append ) {
			int index = 2; //이어서 쓰지 않을것이라면.. 영역
			while( file.exists()) {
				file = new File(file.getParent(), // C:\\java\\outputs = file.getParent()
						"java_outputs2 (" + (index++) + ").txt"); 
						// index++ = 2, 해당 반복문 실행 후 그 다음 3이 됨
			}
		}
		
		//파일에 쓸 내용
		List<String> fileDesc = new ArrayList<>();
		fileDesc.add("파일을 씁니다1");
		fileDesc.add("파일을 씁니다2");
		fileDesc.add("파일을 씁니다3");

		//파일을 쓴다
		try {
			if( ! append) {
				Files.write(file.toPath(), fileDesc, Charset.forName("UTF-8"));
			}
			else { //이어쓰기
				Files.write(file.toPath(), fileDesc,
						Charset.forName("UTF-8"), StandardOpenOption.APPEND);
			}
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		System.out.println(file.getAbsolutePath());
	}
}

 

- 파일에 내용 덧붙이기 : StandardOpenOption.APPEND

- 파일 및 폴더 삭제 : File 객체의 delete 인스턴스 메소드 호출-  file.delete()

--> 하지만, 파일이 들어있는 폴더는 지워지지 않음 -> ' 재귀호출' 이용

 

메소드 재귀호출 예제
public class RecursiveCallExam {

	public static File findFile(String fileName, File from) {
		if(from == null) {
			from = new File("C:\\"); //C부터 찾아라
		}
		
		if(from.exists()&&from.isDirectory()) {
			File[] itemDir = from.listFiles();
			
			//폴더 확인용 코드
			System.out.println(from.getAbsolutePath());
			
			if (itemDir != null) {
				for(File item : itemDir) { 
                    if(item.isDirectory()) { //순회하고있는게 폴더라면, findFile 해라 
                        File result = findFile(fileName, item);
                        if ( result != null) { //****
                            return result;
                        }
						//return findFile(fileName, item);     // item 주면 그걸로 찾아라
                    }
                    else if (item.getName().equals(fileName)){ //내가 찾고자하는 파일이름과 같으면
                        System.out.println(item.getAbsolutePath());
                        return item;//같으면 반환시켜라
                    }
				}
			}
		}
		// 전달받은게 폴더가 아니고 파일이라면, 
		else if (from.getName().equals(fileName)) {
			System.out.println(from.getAbsolutePath());
			return from;
		}
		
		//다 뒤져봤는데 없다면, null 반환
		return null;
	}
	
	
	/**
	 * dir아래에 있는 모든 항목들(하위폴더포함)을 출력한다.
	 * @param dir 탐색을 시작할 (폴더)경로
	 */
	public static void printAllItems(File dir) {
		if(dir.exists()&&dir.isDirectory()) {
			File[] itemDir = dir.listFiles();
			
			//폴더 확인용 코드
			System.out.println(dir.getAbsolutePath());
			
			if (itemDir != null) {
				for(File item : itemDir) {
                    if(item.isDirectory()) {
                        printAllItems(item);
                    }
                    else {
                        System.out.println(item.getAbsolutePath());
                    }
				}
			}
		}
		else if (dir.isFile()) {
			System.out.println(dir.getAbsolutePath());
		}
	}
	
	
	
	public static String join(String startStr, String joinStr) {
		if(startStr.length() >= 100) {
			return startStr; //join이 아닌 그냥 startStr을 반환시켜라
			// length >100 일때 마지막 starStr값 = 128, 
			// -> main코드의 String resultString = join("A", "");로 가서 A 128개 출력됨
		}
		startStr += joinStr;
		System.out.println(startStr);
		
		//리턴할때 join을 호출하면 이거또한 '재귀호출'이다 
		// (자기(join)가 자기(join)를 호출했으므로)
		return join(startStr, startStr); //자기가 자기를 호출해서 반환시킨다
		
	}
	
	
	public static void infinityCall(int value) {
		if(value == 3) {
			return;
		}
		System.out.println("infinityCall 호출됨." + value);
		//재귀호출 (메소드가 자기자신을 다시 실행함)
		//infinityCall 안에서 infinityCall 또 호출
		infinityCall(++value);
	}	
	
	public static void main(String[] args) {
		
//		while 무한 반복
//		while(true) {
//			infinityCall(); //무한히 반복되며 호출되는 infinityCall
//		}
//		Exception in thread "main" java.lang.StackOverflowError

		//재귀호출
		//infinityCall(0);
	
//		String resultString = join("A", "");
//		System.out.println(resultString);
		
		printAllItems( new File("C:\\Program Files (x86)"));
		
		File file = findFile("goods.csv", null); //findFile(String fileName, File from)
		System.out.println(file.getAbsolutePath());
	}
}

 

IO / NIO 선택 방법

1. IO

- 대용량 데이터를 처리할 경우 (IO는 받은즉시 처리하므로)

- 연결 클라이언트 수가 적고, 전송되는 데이터가 대용량 + 순차적으로 처리될 필요가 있을 경우

2. NIO

- 연결 클라이언트 수가 많고, 하나의 입출력 처리작업이 오래걸리지 않을 경우

--> 불특정다수의 클라이언트, 멀티파일들을 넌블로킹이나 비동기로 처리할수 있기때문에 과도한 스레드생성을 피하고, 스레드를 효과적으로 재사용하는 장점 & 운영체제 버퍼를 이용한 입출력이 가능한 장점(다이렉트 버퍼)