가비지 컬렉션, Garbage Collection

개요

가비지 컬렉션Garbage Collection이란, 시스템에서 더 이상 사용하지 않는 동적 할당된 메모리 블럭 혹은 개체를 찾아 자동적으로 다시 사용 가능한 자원으로 회수하는 것을 말한다. 시스템에서 가비지 컬렉션을 수행하는 부분을 가비지 컬렉터Garbage Collector라고 하며, 최초의 가비지 컬렉터는 1958년에 존 매카시John McCarthy에 의해 리습Lisp 언어의 일부로 구현되었다. 가비지 컬렉션은 약자로 GC라고 부르기도 한다.


일반적인 가비지 컬렉터 알고리듬Algorithm은 다음과 같이 동작한다.


1. 더 이상 프로그램에서 사용하지 않을 개체Object를 찾아낸다.

2. 해당 개체가 사용하는 리소스를 회수한다.

그러나 실제로 어떤 개체가 마지막으로 사용되었고, 따라서 더 이상 사용되지 않을 것이란 사실을 알아내기는 일반적으로 불가능하다. 프로그램에 앞으로 주어질 입력을 무시하더라도 특정 개체가 유효한지 알아내는 일반적인 알고리듬은 없다. (정지 문제Halting Problem 참고) 따라서 더 이상 프로그램에서 사용하지 않을 개체를 찾아내기 위해서 가비지 컬렉터는 매우 확실하고 보수적인 방법을 사용하는데, 그것은 해당 개체를 참조할 수 있는 방법이 있는가를 알아내는 것이다. 프로그램에서 어떤 개체에 접근할 방법이 없는 이상 그 개체는 더 이상 누구도 사용할 수 없고, 따라서 그 개체는 유효하지 않다고 판단하는 것이다.


이것이 현재 구현되어 있는 가비지 컬렉터들이 사용하는 보편적인 전략이고, 이런 방법으로 참조 불가능한 개체를 회수하는 가비지 컬렉터를 트레이싱 가비지 컬렉터Tracing Garbage Collector라고 부른다. 레퍼런스 카운트Reference Count 역시 가비지 컬렉션의 일종으로 볼 수도 있지만 좁은 의미로 가비지 컬렉터라는 단어를 사용할 때는 트레이싱 가비지 컬렉터만을 의미하기 한다. 개체 간의 참조 관계를 살펴보고 참조할 수 없는 개체를 살펴보기 위해서 가비지 컬렉터는 개체 내의 포인터 레이아웃을 알 필요가 있고, 따라서 프로그래밍 언어에 통합되어 있는 경우가 많다.


가비지 컬렉션을 도입했을 때 가장 눈에 띄는 장점 중 하나는 가비지 컬렉션이 메모리 릭Memory Leak이나 이중 해제Double Free, 너무 빠른 해제Premature Free와 같이 수정하기 까다롭지만 저지르기는 쉬운 실수에 대한 강력한 방어 수단이 된다는 점이다. 이런 실수를 했을 때 프로그램이 어떤 문제를 일으킬 지, 또 언제 이런 문제가 발생할 지를 웬만해서는 알 수 없기 때문에 이 두 가지 버그들은 다른 일반적인 버그보다 대처하기가 어렵고, 그렇기 때문에 실제로 개발자들이 이런 버그들을 찾아내는 데 도움을 주는 작업만을 전문적으로 수행하는 도구(Compuware BoundsChecker, Rational Purify 등)가 많이 있다. 결과적으로 가비지 컬렉션은 이런 문제가 일어났을 때 추적하는 노력을 제거할 뿐 아니라 프로그램의 복잡도Complexity를 낮추기 때문에 전체적인 생산성에도 긍정적인 영향을 준다.


알고리듬

트레이싱 가비지 컬렉터는 가비지 컬렉션 사이클GC Cycle을 수행한다. 가비지 컬렉션 사이클은 보통 메모리가 부족해질 때, 즉 동적 할당이 실패할 때 수행되고, 삼색 표시Tri-Color Marking이라고 불리는 다음과 같은 과정으로 진행된다.


1. 백색, 회색, 검은 색 집합을 만든다. 이 집합들은 사이클 내내 유지된다. 

2. 검은 색 집합은 초기에 비어있고, 회색 집합에는 루트Root라고 불리는 특별한 개체를 넣는다. 흰색 집합에는 그 외 모든 개체를 넣는다.

3. 회색 집합에서 개체 하나를 꺼내서, 이 개체에서 포인터 연산 한 번으로 닿을 수 있는 (보통은 멤버) 모든 흰색 개체를 회색 집합에 넣는다.

4. 회색 집합에서 꺼낸 개체를 검은 색 집합으로 옮긴다.

5. 회색 집합에 개체가 남아 있으면 3으로, 남아 있지 않으면 사이클을 종료한다.

여기서 루트는 프로그램이 즉각적으로 접근할 수 있는 특별한 개체들이고, 일반적으로 전역 개체, 스택Stack 내의 개체, 레지스터Register 내의 개체를 의미한다. 루트 내에 있는 개체로부터 참조 가능한 개체는 다시 접근 가능한 개체로 간주된다. 말하자면, 프로그램이 즉시 접근 가능한 개체로부터 포인터 연산을 반복하여 참조 가능한 개체는 모두 접근 가능한 개체인 것이고, 가비지 컬렉션 사이클은 루트로부터 참조 가능한 개체를 모두 찾아내는 과정으로 간주할 수 있다. 이 과정에서 흰색 집합은 자원 회수 대상이 되는 개체가 들어있는 집합이고, 검은 색 집합은 사용 가능성이 있으므로 회수하지 않는 개체의 집합이 된다. 사이클이 모두 종료되면 3번 과정에 의해서 흰색 집합에 있는 어떤 개체도 검은 색 집합에 있는 개체로부터 참조될 수 없다는 점에 주목하자. 이제 흰색 집합에 남은 개체는 프로그램에서 접근할 수 없는 개체이고 따라서 참조될 수 없으므로 가비지 컬렉터는 이 개체들을 해제하게 된다.


가비지 컬렉션 사이클을 수행하는 방법은 몇 가지로 구분할 수 있다.


표시하고 치우기Mark-and-Sweep는 각 개체에 현재 이 개체가 흰색인지, 회색인지, 아니면 검은 색인지를 표시할 수 있는 플래그를 만들어두고 가비지 컬렉션 사이클 동안 이 플래그를 마킹해서 결과적으로 남은 흰색 개체를 지워주는 방법인데, 메모리 상에서 개체의 배치가 달라지지 않기 때문에 추가적인 메모리를 필요로 하지 않지만 단편화Fragmentation가 생길 수 있다는 문제점이 있다.


멈추고 복사Stop-and-Copy는 메모리 상에 활성 영역과 비 활성 영역을 구분한 뒤, 활성 영역에 대한 가비지 컬렉션 사이클이 끝나면 검은 색 집합 내의 모든 개체를 비활성 영역으로 옮기고, 이전 활성 영역을 비활성 영역으로 표기하고, 비활성 영역을 활성 영역으로 사용한다. 표시하고 치우기에서 볼 수 있던 단편화가 해결되는 반면 가비지 컬렉션 사이클 동안 메모리 사용량이 두 배로 증가한다는 문제점이 있다. 또한 개체를 복사하는 데 상당한 양의 퍼포먼스 부담을 갖게 된다.


표시하고 압축Mark-and-Compact은 가비지 컬렉션 대상인 개체를 모두 수집한 뒤, 남아 있는 개체를 하위 주소로 이동하면서 빈 공간을 남기지 않는 방법이다. 단편화를 제거하면서도 멈추고 복사와 같이 메모리 사용량 부담을 지지 않을 수 있는 방법이다.

표시하고 치우기를 사용하는 가비지 컬렉터는 개체를 메모리 공간 상에서 실제로 이동하지 않으므로 고정 가비지 컬렉터Non-Moving Garbage Collector라고 부르고, 멈추고 복사나 표시하고 압축과 같은 방법을 사용하는 가비지 컬렉터는 이동 가비지 컬렉터Moving Garbage Collector라고 부른다. 이동 가비지 컬렉터를 사용할 경우 가비지 컬렉션 사이클마다 모든 개체의 참조를 조정할 수 없으므로, 개체 참조는 핸들을 가리키고 있고, 핸들을 다시 한 번 더 참조하면 실제 포인터를 얻어올 수 있도록 구현되어 있어야 한다. 이렇게 구현할 경우 메모리 공간 상에서 개체를 재배치한 뒤 핸들이 참조하고 있는 포인터만 바꾸면 실제 개체가 갖고 있는 참조를 수정하지 않고도 개체의 재배치를 완료할 수 있기 때문이다.


성능상의 문제점

트레이싱 가비지 컬렉션 알고리듬의 가장 문제점은 접근 가능한 개체를 알아내기 위해서 전체 개체를 모두 순회하는 데 상당히 오랜 시간이 걸린다는 점이다. 가비지 컬렉션이 필요한 상황이 언제일지 미리 알 수 없기 때문에, 이런 특성은 즉각적인 반응성을 요구하는 애플리케이션에서는 큰 문제점으로 간주될 수 있다. 성능상의 이유로 한 번에 모든 개체를 추적하지 않고 중단했다가 다시 시작할 수 있는 가비지 컬렉션 방법을 점진적 가비지 컬렉션Incremental GC이라고 한다.


가비지 컬렉션 사이클은 중간에 중단할 수 없으면서 완전히 수행되는 시간이 길다는 문제를 해결하기 위해서 여러가지 방법이 도입되는데, 보편적으로 사용되는 것 중 하나가 세대별 가비지 컬렉션Generational GC이다. 세대별 가비지 컬렉션은 다음과 같은 가정을 기반으로 동작한다.


1. 새로 생긴 개체는 오래 사용하지 않을 가능성이 클 것이다..

2. 개체가 오래 사용될수록 그 뒤로도 계속 사용될 가능성이 클 것이다.

3. 새로 생긴 개체는 특정 기간 동안 서로를 빈번하게 참조하는 경향이 있을 것이다.

4. 메모리의 일부만 정리하는 것은 전체를 정리하는 것보다 비용이 낮을 것이다.

세대별 가비지 컬렉션에서는 가비지 컬렉션 사이클 한 번을 거칠 때마다 수거되지 않은 개체들이 한 세대씩 나이를 먹는다. 다음 차례에는 가장 어린 세대에 대해서만 가비지 컬렉션을 수행할 수 있게 되는데, 이 때에는 흰색 집합에 가장 어린 세대의 개체만 넣어두고, 어린 세대로부터 상위 세대에 대한 참조가 발견될 경우, 가정 3에 의해서 상위 세대의 개체는 하위 세대의 개체를 참조할 가능성이 낮으므로 해당 상위 세대의 개체가 참조하는 개체는 추적하지 않는 방식으로 좀 더 적은 연산을 수행하게 된다. 이 때, 상위 세대의 개체가 하위 세대를 참조할 가능성이 전혀 없는 것이 아니기 때문에, 사용중인 어린 세대 개체를 해제하지 않기 위해서 상위 세대의 참조가 변경될 때마다 하위 세대를 참조하는지 확인하여 따로 추적해야 할 대상으로 간주하는 추가적인 연산이 필요하다.


가비지 컬렉터의 문제 중 또 다른 것은 이것이 참조 근접성Locality of Reference을 훼손한다는 점이다. 사용하지 않는 개체가 어떤 것인지 찾아내기 위해서, 오랫동안 사용하지 않은 개체들을 모두 순회하게 되는데, 가비지 컬렉터가 사용 여부를 판단하기 위해서 개체를 순회하는 것은 최근의 컴퓨터 시스템이 도입하고 있는 여러 단계의 캐시Cache 효율성을 심각하게 위협할 수도 있다. (순회하는 개체 중에는 심지어 가상 메모리 부족으로 페이지 파일에 저장된 것도 있을 수 있다) 따라서 가비지 컬렉션을 지원하는 언어를 사용할 때는 해당 언어가 사용하는 컴퓨터 아키텍처와 OS에 최적화되어 있는지 확인할 필요가 있다.


가비지 컬렉션을 지원하는 언어

가비지 컬렉션은 현재 많은 언어에 의해서 지원되고 있다. 대표적인 것으로 자바Java, Smalltalk, C#, VB.net, Managed C++ 등의 개체 지향Object-Oriented 언어들이 있고, 루아Lua, 파이선Python과 같은 스크립트 언어도 언어의 특성상 가비지 컬렉션을 지원한다. C/C++에서도 사용할 수 있는 공개 소스 가비지 컬렉터가 있다.



출처 - http://beforu.egloos.com/1134737







'Development > Java' 카테고리의 다른 글

java - 메서드 체인닝(Method chaining)  (0) 2012.10.08
java - Timer 및 TimerTask Class  (0) 2012.10.06
jdom - xmlns, xsi 설정  (0) 2012.10.04
java - 파일 읽기 및 쓰기  (0) 2012.10.04
java - 디렉토리 생성, 삭제  (0) 2012.10.04
Posted by linuxism
,


Ask

I am trying to produce a XML document using the newest JDOM package. I'm having trouble with the root element and the namespaces. I need to produce this root element:

<ManageBuildingsRequest 
   
xmlns="http://www.energystar.gov/manageBldgs/req"
   
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   
xsi:schemaLocation="http://www.energystar.gov/manageBldgs/req
                        http://estar8.energystar.gov/ESES/ABS20/Schemas/ManageBuildingsRequest.xsd"
>

I use this code:

Element root = new Element("ManageBuildingsRequest");
root
.setNamespace(Namespace.getNamespace("http://www.energystar.gov/manageBldgs/req"));
Namespace XSI = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
root
.addNamespaceDeclaration(XSI);
root
.setAttribute("schemaLocation", "http://www.energystar.gov/manageBldgs/req http://estar8.energystar.gov/ESES/ABS20/Schemas/ManageBuildingsRequest.xsd", XSI);

Element customer = new Element("customer");
root
.addContent(customer);
doc
.addContent(root); // doc jdom Document

However, the next element after ManageBuildingsRequest has the default namespace as well, which breaks the validation:

<customer xmlns="">

Any help? Thank you for your time.


Answer

The constructor you're using for the customer element creates it with no namespace. You should use the constructor with the Namespace as parameter. You can also reuse the same Namespace object for both root and customer elements.

Namespace namespace = Namespace.getNamespace("http://www.energystar.gov/manageBldgs/req");
Element root = new Element("ManageBuildingsRequest", namespace);
Namespace XSI = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
root
.addNamespaceDeclaration(XSI);
root
.setAttribute("schemaLocation", "http://www.energystar.gov/manageBldgs/req http://estar8.energystar.gov/ESES/ABS20/Schemas/ManageBuildingsRequest.xsd", XSI);

Element customer = new Element("customer", namespace);
root
.addContent(customer);
doc
.addContent(root); // doc jdom Document


Here's an alternate approach that implements a custom XMLOutputProcessor that skips emitting empty namespace declarations:

public class CustomXMLOutputProcessor extends AbstractXMLOutputProcessor {
   
protected void printNamespace(Writer out, FormatStack fstack, Namespace ns)
           
throws java.io.IOException {
       
System.out.println("namespace is " + ns);
       
if (ns == Namespace.NO_NAMESPACE) {
           
System.out.println("refusing to print empty namespace");
           
return;
       
} else {
           
super.printNamespace(out, fstack, ns);
       
}
   
}
}


출처 - http://stackoverflow.com/questions/8359150/namespaces-default-in-jdom










'Development > Java' 카테고리의 다른 글

java - Timer 및 TimerTask Class  (0) 2012.10.06
java - 가비지 컬렉션(Garbage Collection)  (0) 2012.10.05
java - 파일 읽기 및 쓰기  (0) 2012.10.04
java - 디렉토리 생성, 삭제  (0) 2012.10.04
java - JDOM  (0) 2012.10.02
Posted by linuxism
,


자바에서 파일 쓰기 할 때
BufferedWriter file = new BufferedWriter(new FileWriter("filename")); 


대개의 경우 이런식으로 코딩을 했었는데, 이 코드는 파일을 덮어쓴다. 
파일을 덮어쓰지 않고 이어쓰기하는 방법이 없을까 하고 고민하고 찾아봤다.
RandomAccessFile 클래스를 사용하는 방법도 있었고
그리고 파일을 쭉 읽어서 변수에 저장을 한 뒤 새로 추가할 내용을 덧붙여서 파일에 쓰는 방법도 있었다. 
하지만 뭔가 더 간편한게 있을 것 같아서 찾아봤더니
이 한문장이면 파일 이어쓰기가 가능하다.

BufferedWriter file = new BufferedWriter(new FileWriter("filename", true));

이 코드는 파일이 없으면 새로 만들고 있다면 덮어쓰지 않고 이어서 쓰게한다.

그리고 파일에서 개행문자를 쓰려고할 때
C나 C++에서처럼 당연히 ""이 먹힐거라 생각을 하고 
str = str + "";
file.write(str, 0, str.length());

이렇게 썼는데 파일에는 줄바꿈이 되어있지 않았다. 
알아본 결과 저 위에서 사용한 FileWriter가 가독성때문에 ""을 지원하지 않기 때문이었다. 
BufferedWriter를 통해서 개행문자를 쓸 수 있다. 
바로 이렇게..

file.write(str, 0, str.length());
file.newLine();

간단하다. 하하하;;;
이 사실을 난... 어제 알았다. 쿨럭 -_-;;;
까먹을까봐...



===================================================================================


자바로, 텍스트 파일을 만들어서, 그 안에 문자열을 쓰고, 저장하는 예제입니다.

텍스트 파일 생성/만들기 예제 소스


소스 파일명: Foo.java
import java.io.*;

public class Foo {
  public static void main(String args[]) {

    try {
      ////////////////////////////////////////////////////////////////
      BufferedWriter out = new BufferedWriter(new FileWriter("out.txt"));
      String s = "출력 파일에 저장될 이런 저런 문자열입니다.";

      out.write(s); out.newLine();
      out.write(s); out.newLine();

      out.close();
      ////////////////////////////////////////////////////////////////
    } catch (IOException e) {
        System.err.println(e); // 에러가 있다면 메시지 출력
        System.exit(1);
    }

  }
}


위의 자바 소스를 컴파일한 후 실행하면, 하드의 현재 디렉토리에 out.txt 라는 텍스트 파일이 생깁니다.

생성된 out.txt 파일의 내용:
출력 파일에 저장될 이런 저런 문자열입니다.
출력 파일에 저장될 이런 저런 문자열입니다.



참고로, newLine() 이라는 메소드는, 현재 운영체제의 종류를 자동으로 판단한 후, 적절한 개행문자를 출력하여 줄바꿈하는 것입니다.


출처 - http://mwultong.blogspot.com/2006/10/java-text-file-write.html



===================================================================================


하드에 있는 텍스트 파일을, 명령행 옵션으로 지정해 주면, 그 파일을 한 줄씩 읽어서 화면에 출력하는 자바 프로그램입니다.

if (args.length == 0) 라는 부분은 읽을 파일명을 지정해 주지 않았을 때 에러 메시지를 출력하는 코드입니다.


자바로 텍스트 파일 읽기 예제 소스


소스 파일명: Foo.java
import java.io.*;

public class Foo {
  public static void main(String args[]) {

    if (args.length == 0) {                   // args.length 는 옵션 개수
      System.err.println("Input Filename...");
      System.exit(1);                         // 읽을 파일명을 주지 않았을 때는 종료
    }

    try {
      ////////////////////////////////////////////////////////////////
      BufferedReader in = new BufferedReader(new FileReader(args[0]));
      String s;

      while ((s = in.readLine()) != null) {
        System.out.println(s);
      }
      in.close();
      ////////////////////////////////////////////////////////////////
    } catch (IOException e) {
        System.err.println(e); // 에러가 있다면 메시지 출력
        System.exit(1);
    }

  }
}


위의 소스에서 빗금 쳐진 구역이, 파일을 읽는 핵심부입니다.

FileReader 는 현재 시스템의 기본 인코딩으로 파일을 읽는데, BufferedReader 로 감싸 주어야 합니다.

readLine() 은 파일을 한 줄씩 읽지만 줄바꿈 문자는 읽지 않습니다. 그래서 println()이 아닌 print(s)로 출력하면 모든 줄이 하나로 붙어서 나옵니다.



0.txt 라는 테스트용 텍스트 파일에, 이육사 시인의 '청포도'라는 시가 적혀 있다고 가정합니다.

javac Foo.java
이렇게 컴파일 한 후

java Foo 0.txt
이렇게 실행시킵니다.


실행 결과:
D:\Z>javac Foo.java

D:\Z>java Foo 0.txt

          청포도


                          이육사


내 고장 칠월은
청포도가 익어 가는 시절

이 마을 전설이 주저리주저리 열리고
먼 데 하늘이 꿈꾸며 알알이 들여와 박혀

하늘 밑 푸른 바다가 가슴을 열고
흰 돛단배가 곱게 밀려서 오면,

내가 바라는 손님은 고달픈 몸으로
靑袍를 입고 찾아온다고 했으니

내 그를 맞아, 이 포도를 따먹으면
두 손은 함뿍 적셔도 좋으련

아이야, 우리 식탁엔 은쟁반에
하이얀 모시 수건을 마련해 두렴


D:\Z>


출처 - http://mwultong.blogspot.com/2006/10/java.html


===================================================================================


<텍스트 파일을 읽기>

1.파일을 연다.
텍스트 파일을 읽기 위해서는 FileReader 스트림 클래스를 사용한다.

FileReader in = new FileReader("test.txt");
또는
FileReader in = new FileReader("c:/test.txt");

2.데이터를 읽는다.
데이터를 읽기 위해서는 read() 메소드를 사용한다.
read() 메서드는 읽어 온 문자를 int 형 정수로 변환한다.
읽어 올 데이터가 없으면 -1 을 반환한다.

int c;
c = in.read();

3.파일을 닫는다.
파일을 닫기 위해서는close() 메서드를 사용한다.

in.close();

텍스트 파일의 전체 내용을 읽기 위해서는 read() 메서드가 -1 을 반환할때까지 읽기를 반복하면 된다.

inc c;
String s = new String();
while((c=in.read()) != -1){
  s = s + (char) c;
}

즉, in.read() 를 이용해 읽어들인 값 c 는 int 형 정수이기 때문에, 이를 캐스트 연산자를 이용해 char 형으로 변환한다.
(한개의 문자를 읽기 때문에)
그리고, 이렇게 변환한 문자를 String 형 변수인 s 에 계속 덧붙여서 문장을 완성한다.

test.txt 라는 파일이 존재하고, 그 안에 '가나다라마바사' 라는 내용이 들어있다고 가정하자.
아래와 같은 로직을 통해 파일을 읽어들인다.

<a.java 내용>

import java.io.*;   //스트림을 이용하기 위해서 java.io 패키지를 이용한다.
public class a
{
  public static void main(String [] args)
  {
    try{
      //FileReader in = new FileReader(args[0]);  //읽어들일 파일을 최초 클래스 호출시 인자값으로 받아 처리한다.
      //FileReader in = new FileReader("c:/test.txt");  //인자가 아니라, 지정된 파일명으로 호출한다.
      //만약, 파일의 절대경로를 표시하려면, C:\ 로 표시하지 말고, C:/ 로 표시해야 한다.
      //만약, 현재 클래스 파일과 같은 위치의 파일이라면, 경로를 생략하고 파일명만 기재하면 된다.
      FileReader in = new FileReader("test.txt");
      int c;
      String s = new String();
      while((c=in.read()) != -1){
       s = s + (char)c;
       //System.out.println(s);  //엔터키값이 있는 경우에도 읽어들이는데, 엔터키값은 2개의 코드로 이루어져 있어 두번더 반복된다.(화면에는 출력되지 않음)
      }
      System.out.println(s);
      in.close();
    }catch(IOException ie){
      System.out.println("파일이 존재하지 않습니다.");
      //IOException 의 경우에는, 별도로 java.io 패키지를 사용하는 경우 덧붙여 사용할 수 있다.(생략가능)
      //만약, 생략하면, 그냥 catch(Exception e) 에 걸린다.
    }catch(Exception e){
      System.out.println(e);
    }
  }
}

위의 로직에서 몇가지를 테스트 해보았다.
절대경로를 탐색하는 경우에는 \ 기호가 아니라 / 기호를 이용해야 한다.
즉, c 루트에 있는 test.txt 파일이라면,
c:\test.txt 로 하게되면 파일을 찾지 못한다.
그래서,
c:/test.txt 로 경로를 작성하면 파일이 검색된다.
또한, FileReader 객체를 소거하는 in.close() 경우에도,
while 구문 안에 넣어서는 안된다. while 구문이 끝난 마지막에 처리할것.
catch(IOException ie) 는, java.io 패키지를 사용하는 경우, 추가하여 기술할 수 있다.
이는, 파일스트림관련 오류가 발생할 경우, 그 에러를 감지한다.

엔터키값이 들어있는 경우에도, 이 코드들을 한개의 문자로 인식하기 때문에,
while 반복구문에서 두번의 반복 횟수가 늘어난다.
즉, \n 또는 키값이 있기 때문인데,
화면에는 별도로 표시되지 않지만, 반복횟수는 두번이 늘어난다는 것을 명심할것.

최초, 파일을 찾는 부분에서, 호출 인자인 arg[] 를 이용해 파일을 찾는다면,
FileReader in = new FileReader(args[0]);
라고 기술할 수 있으며, 이는, 실행명령 다음에, 클래스명 다음에 온 문자열을 파일의 이름으로 인식하는 것이다.
즉,

c:\java>java a test.txt

라고 기술한다면,
test.txt 라는 인자를 인지하여, 그것으로 파일을 찾는 것이다.


<텍스트 파일에 쓰기>
1.파일을 연다.
텍스트 파일에 문자를 써 넣기 위해서는 FileWriter 스트림 클래스의 오브젝트를 사용한다.

FileWriter out = new FileWriter("test.txt");
또는,
FileWriter out = new FileWriter("c:/test.txt");

2.데이터를 써 넣는다.
데이터를 써 넣을때는 write() 메서드를 이용한다.
write() 메서드는 인수로 주어진 데이터를 파일에 써 넣는다.
(또는, 임의로 지정한 스트링을 입력)

out.write("Hello\n");

3.파일을 닫는다.

out.close();

이를 이용해 아래와 같은 로직을 작성한다.

import java.io.*;

public class a
{
  public static void main(String [] args)
  {
    try{
      //FileWriter out = new FileWriter("c:/test.txt");  //절대경로를 사용할 경우,
      //FileWriter out = new FileWriter(args[0]);  //인자를 이용해서 사용할 경우.
      
      String fn = "test.txt";
      
      FileWriter out = new FileWriter(fn);
      int a = 10, b = 5;
      out.write ("덧셈:");
      out.write (a + " + " + b + " = " + (a+b));
      //out.write ("\n");  //유닉스 계열에서는 \r 을 사용하지 않는다.
      out.write ("\r\n");  //윈도우에서는 줄바꿈을 위해 \r\n 을 해줘야 한다.
      out.write ("뺄셈:");
      out.write (a + " - " + b + " = " + (a-b));
      out.write ("\r\n");
      out.close();
      
      System.out.println("파일 " + fn + " 에 내용을 입력하였습니다.");
    }catch(IOException ie){
      System.out.println("IO 에러가 발생했습니다.");
    }catch(Exception e){
      System.out.println(e);
    }
  }
}


주의할점은, write() 메소드는, 기존에 파일이 없으면 새로 생성하고,
있다면, 그 내용이 모두 지워지고 새로 쓰여 진다는 점이다.

또한, 줄바꿈에 있어서, 윈도우 계열에서는 줄바꿈을 \r\n 을 이용해야만 줄바꿈이 되며,
\n 만을 사용하면, ■ 과 같은 기호만 출력된다.
그러나, 유닉스 계열에서는 줄바꿈에 \r 을 사용하지 않는다.
따라서, 유닉스 계열에서는 \n 만을 사용해야 한다.

책에서는 별도로, 파일에 쓰기 관련해서는 IOException 을 언급하고 있지 않지만,
이 부분을 기술해도 에러가 나지 않는것으로 보아, 사용할 수 있을것으로 보이며,
실상, 쓰기에서 IOException 에러가 나는 상황을 접해보지 않아서, 그런 경우에 어떻게 처리하면 좋을 것인지는
좀더 테스트가 필요하다.


\n 을 이용한경우,

\r\n 을 이용한경우,





Java 7 Files

NIO. 2 에 파일 관련 각종 API가 추가되었다. 이전보다 더 쉽게 파일 관련 작업을 수행할 수 있다.

File I/O (Featuring NIO.2) (The Java™ Tutorials > Essential Classes > Basic I/O) 에서 자세히 볼 수 있으며 아래는 간단한 정리이다..

Path

파일의 경로는 java.nio.file.Path 인터페이스로 나타낸다. 기존의 java.io.File을 Path로 대체한다.

Path 는 인터페이스이며 Paths 혹은 FileSystem 객체로부터 Path 객체를 생성할 수 있다.

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
 
Path path = Paths.get("C:\\Temp\\test.txt");
//혹은 FileSystem 객체로 부터...
Path pathFromFS = FileSystems.getDefault().getPath("C:\\Temp\\test.txt");
 
System.out.println("Path exists == " + Files.exists(path));

Files

Files 클래스는 파일의 복사/이동/삭제/스트림생성/각종 파일 속성 설정 등에 관련된 매우 쉬운 static 메소드를 수십여개 제공해주고 있다.

Manipulating Files in Java 7와 File I/O (Featuring NIO.2) (The Java™ Tutorials > Essential Classes > Basic I/O)를 참조한다.

Java 7: Copy and Move Files and Directories - Java Code Geeks


출처 - http://wiki.kwonnam.pe.kr/java/7/files













'Development > Java' 카테고리의 다른 글

java - 가비지 컬렉션(Garbage Collection)  (0) 2012.10.05
jdom - xmlns, xsi 설정  (0) 2012.10.04
java - 디렉토리 생성, 삭제  (0) 2012.10.04
java - JDOM  (0) 2012.10.02
java - Date 시간 및 날짜 차이 계산  (0) 2012.09.08
Posted by linuxism
,