{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[Java] 자바로 프로그래밍 입문하기: 1.5. 입출력 (2)

 대화형 입력(Interactive user input)

  <프로그램 1.5.2, TwentyQuestions>는 사용자와 상호작용하는 프로그램의 간단한 예입니다. 프로그램은 난수를 생성하며, 사용자에게 숫자를 추측할 수 있게끔 단서를 던져줍니다.(참고: 이진 탐색을 이용하면 여러분은 언제나 숫자를 맞출 수 있습니다) 여지껏 작성한 다른 프로그램들과의 근본적 차이는, 사용자가 프로그램 실행 도중에 제어 흐름을 바꿀 수 있다는 것입니다. 이것은 여러분들이 보기에도 굉장히 중요해보일 것입니다. 현대의 많은 프로그램들은 GUI(graphical user interface)를 통해 다양한 입력들을 받아 제어 흐름을 변경합니다.


프로그램 1.5.2: Interactive user input

public class TwentyQuestions
{
	public static void main(String[] args)
	{	// Generate a number and answer questions
		// while the user tries to gues the value.
		int N = 1 + (int) (Math.random() * 1000000);
		StdOut.print("I'm thinking of a number ");
		StdOut.println("between 1 and 1,000,000");
		int m = 0;
		while (m != N)
		{ // Solicit one guess and provide one answer
			StdOut.print("What's your guess? ");
			m = StdIn.readInt();
			if (m == N)
				StdOut.println("You win!");
			if (m < N)
				StdOut.println("Too low ");
			if (m > N)
				StdOut.println("Too high");
		}
	}
}

% java TwentyQuestions
I'm thinking of a number between 1 and 1,000,000
Whats' your guess? 500000
Too low 
What's your guess? 750000
Too low
Whats' your guess? 875000
Too high
...

  <프로그램 1.5.2>는 간단한 스무고개 게임입니다. 여러분이 입력하는 숫자가 맞는지 확인할 수 있으며, 만약 틀리다면 여러분의 숫자가 낮은지, 높은지 알려줍니다. 


프로그램 1.5.3: Averaging a stream of numbers 


public class Average
{
	public static void main(String[] args)
	{ 	// Average the numbers on the input stream.
		double sum = 0.0;
		int cnt = 0;
		while (!StdIn.isEmpty())
		{ // Read a number and cumulate the sum.
			double value = StdIn.readDouble();
			sum += value;
			cnt++;
		}
		double average = sum / cnt;
		StdOut.println("Average is " + average);
	}
}
% java Average
10.0 5.0 6.0
3.0
7.0 32.0
<ctrl-z>
Average is 10.5
% java RandomSeq 100000 > data.txt
% java Average < data.txt
Average is 0.5010473676174824

% java RandomSeq 100000 | java Average
Average is 0.5000499417963857

  <프로그램 1.5.3>은 일련의 실수들을 받아 평균을 출력합니다. 입력 스트림의 크기에는 제한이 (오버플로우가 일어나지 않는 한에서)없습니다. 오른쪽의 명령에 왼쪽의 명령을 연결(piping)하는 모습도 볼 수 있습니다. 보통 <ctrl-z> 혹은 <ctrl-d>를 통해 콘솔에게 입력의 끝을 알릴 수 있습니다. 제대로 작동하지 않는다면, 다음 포스팅을 참고하세요.


[Eclipse] 콘솔에서 Ctrl+Z를 통한 EOF 입력이 되지 않을 때 해결 방법


임의의 크기의 입력 스트림을 처리하기(Processing an arbitary-size input stream)

  보통 우리가 입력을 무한하게 하지는 않습니다: 여러분의 프로그램이 입력 스트림을 통해 스트림이 텅 빌 때까지 값을 소모합니다. 하지만 입력 스트림의 크기에 대한 제한은 없으며, 몇몇 프로그램은 이것만으로도 제공되는 입력들을 전부 처리할 수 있습니다.

  <Average>는 표준 입력을 통해 읽은 일련의 실수들의 평균을 출력합니다. 이는 입력 스트림을 사용할 때의 중요한 속성을 드러내는데, 스트림의 길이를 프로그램은 알 수 없다는 것입니다. 숫자를 읽기 전에 프로그램은 StdIn.isEmpty() 메소드를 사용해 입력 스트림에 아직 입력이 남아있는지를 확인합니다. 어떻게 더 이상 데이터가 없음을 확인할 수 있을까요?

  관습 상, 특별한 일련의 문자들을 이용합니다. 이를 EOF(end-of-file)이라고 합니다. 안타깝게도, 우리가 현대의 운영체제에서 접하는 터미널 응용 프로그램들은 서로 EOF의 관습이 살짝씩 다릅니다. 이 강의에서는 <ctrl-z>를 사용했고, 다른 많은 시스템에서는 <ctrl-d>도 사용 합니다. 

  <Average>는 간단한 프로그램이지만, 프로그래밍의 새롭고 엄청난 능력을 재현합니다: 표준 입력이 있다면, 무한한 양의 데이터들을 처리하는 프로그램을 작성할 수 있다는 것입니다. 


  표준 입력은 우리가 사용해왔던 명령행(command-line) 입력 모델에 있어 중요한 단계입니다. 두 가지를 알 수 있게 되기 때문인데, 첫째로는 <TwentyQuestions>로 알게 된, 우리가 프로그램과 상호작용 할 수 있다는 사실입니다. 둘째로는 <Average>로 알게 된, 우리가 굉장히 많은 양의 데이터를 읽어들일 수 있다는 사실입니다.


리디렉션과 파이프(Redirection and piping)

  많은 응용프로그램에서, 표준 입력에 데이터를 직접 손으로 입력하는 것은 별로 권고되지는 않습니다. 우리가 타이핑 할 수 있는 양의 데이터까지만 프로그램의 능력이 발휘될테니까요.(심지어 타이핑 속도도 영향을 받겠죠) 비슷한 이야기로, 우리가 어떤 결과를 보는데 항상 자바 프로그램을 실행시켜 터미널에 찍힌 표준 출력을 통해서만 볼 수 있다면, 이것 또한 효율적이지 않습니다. 따로 어딘가 결과를 저장해두고 싶죠.

  이런 문제들을 해결하기 위해 우리가 집중할 생각은, 표준 입력은 추상화 되어 있다는 것입니다. 추상화 되어 있다는 것은 우리의 프로그램이 단순히 입력 스트림을 통해 입력을 받을 뿐, 입력 스트림이 어떻게 생겼는지, 어떤 키보드를 통해서 입력을 받는지, 어떤 코드들로 구성되어 있는지는 별로 신경쓸 필요가 없게 설계가 되어 있다는 것입니다. 마찬가지로 표준 출력도 추상화 되어 있습니다.

  이런 추상화의 힘은 우리에게끔 표준 입출력을 위한 다양한 다른 소스들을 명시할 수 있게 합니다. 예를 들어, 파일, 네트워크, 혹은 다른 프로그램으로부터 입력을 받을 수 있게 하는 것입니다. 모든 현대의 운영체제는 이런 메커니즘들을 구현합니다.


표준 출력에서 파일로 리디렉션 하기(Redirecting standard output to a file)

  영구적으로 저장하거나 다른 프로그램의 입력으로 사용하기 위해, 간단한 커맨드를 추가하여 프로그램 실행 시 표준 출력을 파일 생성으로 리디렉션(우회)시킬 수 있습니다. 다음 명령어는 <RandomSeq>에 인자를 1000으로 넘겨주며 실행시키고, 해당 출력을 <data.txt> 파일로 리디렉션하는 명령어입니다. 이는 운영체제에서 지원해주는 것이므로, Eclipse를 활용하기 위해서는 Eclipse 내부의 Terminal을 실행시켜서 입력해야 합니다. 


% java RandomSeq 1000 > data.txt

  위 명령어를 사용 시, 표준 출력 스트림이 터미널 윈도우에 출력이 되는 것이 아닌 <data.txt>라는 이름의 파일에 작성될 것입니다. System.out.print()나 System.out.println()을 호출할 때마다, 파일의 끝부분에 해당 문자열이 계속 추가될 것입니다. 이번 경우, 1000개의 무작위 값이 적혀있겠네요.


파일에서 표준 입력으로 리디렉션 하기(Redirecting from a file to standard input)

  비슷하게, 우리는 표준 입력을 리디렉션 할 수 있습니다. 다음 명령어를 사용하면, StdIn은 자료를 터미널이 아닌 파일로부터 읽게 될 것입니다.


% java Average < data.txt

  이 커맨드는 <data.txt>에 있는 일련의 숫자들을 읽게 합니다. 그리고 그들의 평균을 계산하죠. 특히, < 기호는 운영체제에게 표준 입력 스트림을 터미널 윈도우에서 사용자의 타이핑을 기다리는 것 대신 <data.txt>로부터 읽게 만듭니다. 프로그램이 StdIn.readDouble()을 호출할 때마다, 운영체제는 파일로부터 그 값을 읽어들입니다. 


두 프로그램을 연결하기(Connecting two programs)

  더욱더 유연하게 표준 입력과 표준 출력 추상화를 구현하는 방법은, 자체 프로그램에 의해 구현되게 만드는 방법입니다. 이 메커니즘을 piping(파이프)라고 합니다. 다음 명령어는 <RandomSeq>의 출력을 <Average>의 입력으로 연결시킵니다.


% java RandomSeq 1000 | java Average

  이는 <RandomSeq>의 표준 출력 스트림이 <Average>의 표준 입력 스트림과 같다고 명시하는 것입니다. 이렇게 하면, <Average>가 동작하는 동안, <RandomSeq>가 무작위의 수를 생성해 입력시켜줍니다. 이는 다음 명령어들을 사용하는 것과 동일한 효과를 가져옵니다.


% java RandomSeq 1000 > data.txt
% java Average < data.txt

  물론 <data.txt> 파일이 생성되지 않는다는 점에서 차이는 있습니다. 이는 중요한 차이점인데, <data.txt> 파일이 생성될 디스크 공간이 필요하지 않다는 것입니다. 예를 들어 우리는 1000 대신 1000000000을 넣어서, 10억 개의 숫자를 생성해내도 10억 개의 숫자가 저장될 공간을 마련할 필요가 없습니다.

  <RandomSeq>가 System.out.println()을 호출하여 문자열을 출력할 때, <Average>는 StdIn.readInt()를 호출하여 해당 출력을 입력으로 받아들이고, 후에 문자열은 삭제됩니다. 이런 일이 언제 어떻게 일어나느냐 하는 것은 글쎄요. 아마 여러분의 PC에 있는 운영체제만이 알 것입니다. 


프로그램 1.5.4: A Simple filter

 
public class RangeFilter
{
	public static void main(String[] args)
	{ 	// Filter out numbers not between lo and hi.
		int lo = Integer.parseInt(args[0]);
		int hi = Integer.parseInt(args[1]);
		while (!StdIn.isEmpty())
		{ 	// Process one number,
			int t = StdIn.readInt();
			if (t >= lo && t <= hi)
				StdOut.print(t + " ");
		}
		StdOut.println();
	}
}
% java RangeFilter 100 400
358 1330 55 165 689 1014 3066 387 575 843 203 48 292 877 65 998
358 165 387 203 292
<ctrl-z>


필터(Filters)

  파이프는 1970년 초 UNIX 시스템의 핵심 기능이었습니다. 현대 시스템에도 여전히 건재한 이유는, 간단한 추상화로 서로 다른 프로그램들을 연결할 수 있기 때문입니다. 이런 추상화가 강력하다는 증거는, 많은 UNIX 프로그램들이 당시 프로그래머들이 예상한 것보다 훨씬 더 큰 크기의 파일들을 처리하는데도 여전히 문제가 없기 때문입니다. 

  자바 프로그램들끼리는 메소드 호출로 의사소통 할 수 있지만, 표준 입출력은 서로 다른 시간에 쓰여진 프로그램들, 아마 자바 언어가 아닌 다른 언어로 쓰여있는 프로그램들과 의사소통 해야할 것입니다. 표준 입출력은 바깥 세계와 소통하는 간단한 인터페이스의 역할을 하는 것입니다.

  쉽게 이야기 하면, 각 프로그램들은 일종의 필터인 셈입니다. 표준 입력을 받아들여, 표준 출력으로 바꾸는 필터죠. 이 개념을 잘 받아들이세요. 프로그램은 일련의 입력을 받아 그 입력에 맞는 출력을 내보내는 것 그 이상 그 이하도 아닙니다.

  UNIX에서 고안된 몇몇 표준 필터들은 여전히 현대 운영체제에서 명령어로 사용되고 있습니다. 예를 들어, <sort> 필터는 표준 입력을 받아 정렬된 상태로 출력해줍니다. 


% java RandomSeq 6 | sort
0.035813305516568916
0.14306638757584322
0.348259347192635249
0.5765252840139256
0.72357518425267216
0.97953764516572647

  두 번째 유용한 필터는 <grep>입니다. <grep>은 입력을 받아, 주어진 패턴이 있는 문장들을 출력해줍니다. 예를 들어 다음 명령어는 소스 파일 <RangeFilter.java>에서 'lo'가 포함된 문장들을 출력해주는 명령어입니다. 윈도우 10에서는 이런 명령어가 유효하지 않으며, 대신 <findstr> 명령어를 사용할 수 있습니다.


% grep lo < RangeFilter.java
	{ 	// Filter out numbers not between lo and hi.
		int lo = Integer.parseInt(args[0]);
			if (t >= lo && t <= hi)


% findstr "lo" < RangeFilter.java
	{ 	// Filter out numbers not between lo and hi.
		int lo = Integer.parseInt(args[0]);
			if (t >= lo && t <= hi)

  프로그래머들은 종종 변수의 이름이나 언어의 어떤 구문의 사용 사례를 보고 싶을 때, grep 명령어를 이용해 확인합니다. 세 번째 필터는 <more>입니다. <more>은 수많은 데이터들이 출력될 때, 너무 많이 출력 되어 초반에 출력된 데이터들을 확인하기 힘들 때 사용합니다. 출력될 데이터들이 아무리 많아도 터미널 화면에 보이는만큼만 출력되며, 스페이스 바를 눌러 남은 출력들을 계속 볼 수 있습니다.


계속.

댓글

이 블로그의 인기 게시물

[코딩의탑] 5층: 포탈(Portal), 더 나아가기

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링