같은 형식을 가진 데이터들을 파일에 저장하고자할 때 어떤 방식을 이용할 수 있을까?
예를들어 사람들의 이름, 나이, 거주지, 직업의 필드를 가진 레코드들을 파일에 저장한다고 해보자.
그렇다면 이 네 가지의 정보가 들어있는 구조체를 선언하여 한 사람마다 저장한 다음 구조체단위로 파일에 써야할 것이다. 그렇다면 아래와 같은 구조체를 만들 수 있을 것이다.
struct Person {
char name[10];
char age[4];
char address[10];
char job[10];
};
이렇게 만든 구조체에 한 사람의 정보를 담고 파일에 쓴다고 생각해보자. 그렇다면 각 필드의 크기를 모두 합친 10+4+10+10 = 34byte를 하나의 레코드가 차지하는 고정길이필드 방식을 사용할 수 있다. 각 필드를 일렬로 줄세워 파일에 쓰는 것이다. 이러한 방식은 굉장히 쉽고 단순하다는 장점이 있지만 만약 이름이 4byte, 나이가 1byte, 주소가 5byte, 직업이 5byte라고 한다면 낭비되는 공간이 훨씬 많아진다는 치명적인 단점이 존재한다. 그렇기 때문에 우리는 가변길이 필드 방식을 사용할 수 있다. 가변길이 방식에는 3가지 방식이 있다.
첫 번째로 길이 지시자(length indicator)가 있다. 이 방식은 각 필드 앞에 필드의 길이를 쓰는 것이다. 예를들어 다음과 같이 쓸 수 있다.
5James2255Seoul10Programmer
이렇게 James는 5바이트이므로 앞에 5를, 25살을 뜻하는 25는 2바이트이므로 앞에 2를 쓰는 방법을 이용한다. 하지만 실제 구현하였을 때 앞의 길이지시자를 읽고 그 다음 해당 길이만큼 각 필드를 읽어야하기 때문에 번거롭다는 단점이 있다.
두 번째로 구분자(Delimiter)가 있다. 이 방식은 필드와 필드 사이에 구분을 지을 수 있는 문자를 넣는 방법이다. 다음과 같이 사용할 수 있다.
James|25|Seoul|Programmer|
이렇게 필드와 필드 사이에 '|' 문자를 넣어 필드를 구분할 수 있게 하는 방법이다. 이 방법은 가변길이기 때문에 그 delimiter를 만나기 전까지 읽어야하므로 길이를 알 수 없다는 단점이 있다.
세 번째로 Keyword = Value 방식이 있다. 이 방식은 어떤 필드인지를 앞에 써주는 방법이다.
NAME=James|AGE=25|ADDRESS=Seoul|JOB=Programmer|
이 방식을 사용하였을 때 사라진 필드가 어떤 필드인지를 알 수 있는 장점이 있다. 예를 들어 ADDRESS필드가 사라졌다고 생각해보자. 그렇다면 Keyword를 보고 빠진 필드가 무엇인지 알 수 있고 Seoul이 없을 대 직업인 Programmer가 거주지라고 착각할 일이 생기지 않는다. 하지만 이 방법은 Keyword가 너무 많은 공간을 차지하기 때문에 실질적으로 사용이 어렵다.
지금까지 필드를 구분하는 방법을 알아보았으니 필드로 이루어진 레코드를 구분하는 방법을 알아보자. 레코드 구분 방식에도 고정길이와 가변길이 방식이 있다. 고정길이의 경우 레코드의 길이를 일정하게 지정해놓는 방법이다. 다음은 고정길이 레코드 방식에 고정길이방식 필드와 가변길이방식 필드를 나타낸 것이다. 남은 공간은 '_' 로 나타내었다.
고정길이필드 : James_____25__Seoul_____Programmer
가변길이필드 : James|25|Seoul|Programmer|__________
고정길이 레코드 방식일 경우 위와 같이 나타날 수 있다. 두 방법 모두 낭비되는 공간이 생기게 된다. 이 남은 공간이 생기지 않기 위해서는 가변길이 레코드 방식을 사용하여야한다. 가변길이 레코드 방식은 4가지가 있다.
첫 번째로 헤더 영역에 메타데이터를 저장하는 것이다. 파일의 맨 첫 부분에 레코드를 구성하는 필드의 수, 수정 날짜, 삭제된 레코드 정보 등을 저장해놓아 레코드를 구분할 수 있는 방식이다. 예를 들어 우리의 구조체는 4개의 필드를 가지고 있으므로 헤더영역에 4를 저장해놓아 하나의 레코드는 4개의 필드로 이루어져 있음을 인지하고 레코드를 읽는 방식이다.
두 번째로 길이 지시자(length indicator)가 있다. 가변길이 필드 방식에서와 마찬가지로 각 레코드의 맨 앞에 레코드의 길이를 쓰는 방식이다. 아래와 같이 표현할 수 있다.
26James|25|Seoul|Programmer|
다음 예시는 필드 구분을 구분자를 사용하였다. 이 경우 구분자의 byte 수까지 포함하여 앞에 써준다. 이 방식에서는 앞에 26의 숫자를 ASCII 문자인 '2'와'6'으로 쓸지 26을 binary integer방식으로 십진수 26의 ASCII문자를 저장할 지 결정해야한다. 이때 길이지시자로 2바이트를 사용한다고 할때 ASCII문자 방식은 0~99까지의 100개의 숫자만 나타낼 수 있지만 binary integer방식은 2^16의 굉장히 많은 길이를 표현할 수있는 차이점이 있다.
세 번째로 Index file을 사용하는 방식이 있다. 레코드들이 저장된 데이터파일에 저장하는 것이 아닌 각 레코드의 시작 주소(offset)을 저장하는 index file을 따로 만드는 방식을 사용할 수 있다. 만약 하나의 레코드당 길이를 2바이트를 사용하였다면 1000번째 레코드를 읽고자할 때 offset만 2*1000으로 이동하여 2바이트를 읽으면 되는 방식이다.
마지막으로 구분자(Delimiter)방식이 있다. 필드 구분 방식과 같이 레코드를 구분할 수 있는 문자를 레코드 사이에 넣어 구분할 수 있는 방식이다. 이 경우 가변길이 필드 방식의 구분자와 같아서는 안되며 1000번째 레코드를 가져오고자 할 때 굉장히 많은 시간이 걸리고 레코드를 읽기에 힘들다는 단점이 있다.
지금까지 고정길이와 가변길이 필드, 레코드 방식에 대하여 알아보았다. 실제 파일을 처리할 때 이전 글에서 말했듯이 디스크 접근 비용을 줄이는 것이 가장 중요하므로 위 방식을 적절히 변형하거나 조합하여 사용하는 것이 중요하다.