카테고리 없음

[APR] table

고요한하늘... 2015. 2. 2. 14:26

테이블은 key-value 쌍으로 돼 있다.

key는 문자열로 가정한다.

value는 어떤 타입도 가능한다. 그러나 일반적으로는 문자열로 생각을 한다.

아래쪽 설명은 key, value 모두 문자열로 가정한다.


append : 효율적

insert/preend : 비효율적

delete : 비효율적

search(lookup ) : 거의 효율적

iteration : 효율적


알다시피, 거의 배열과 동일하게 동작한다. 이것은 자연스러운 현상인데 내부적으로 배열로 구성돼 있기 때문이다.

첫째로 테이블은 보다 좋은 API를 제공하고 두번째로 효율적인 검색은 제공과 같은 더 좋은 기능들을 제공합니다.

테이블은 내부적으로 효율적인 검색은 위해 체크섬을 사용한다. 뒤에 설명할 해시테이블만큼 효과적이진 않지만, 그럼에도 불구하고 충분히 좋다.


apr_table_t *tab;

tab = apr_table_make(mp, TABLE_INIT_SZ);


첫번째 인자는 사용할 메모리풀이다. 

테이블과 엘리먼트는 메모리풀에서 할당된다.

두번째 인자는 초기화할 테이블 크기이다.

동적 배열처럼 테이블 크기도 동적이다. 그래서 초기화할 크기는 힌트로 사용된다.


4가지 아이템 추가용 API

/* excerpted from apr_tables.h */


APR_DECLARE(void) apr_table_set(apr_table_t *t, const char *key, const char *val);

APR_DECLARE(void) apr_table_setn(apr_table_t *t, const char *key, const char *val);

APR_DECLARE(void) apr_table_add(apr_table_t *t, const char *key, const char *val);

APR_DECLARE(void) apr_table_addn(apr_table_t *t, const char *key, const char *val);


set과 add의 차이는 같은 key에 대해 중복을 허용하느냐 하지 않느냐이다. set 은 같은 키에 대해 중복을 허용하지 않는다. 그래서 만약 같은 키에 다른 value가 이미 존재한다면 apr_table_set()과 apr_table_setn()은 오래된 value를 덮어쓴다. 반대로 apr_table_add()와 apr_table_addn()은 같은 키에 중복을 허용하기 때문에 이전에 추가된 value가 여전히 유효하다.


n이 있는 것과 없는 것의 차이는 key와 value의 메모리가 복제되는지 그렇지 않은지에 있다.

n이 없으면 key와 value 문자열의 메모리를 복제한다. 반대로 n이 있으면 복제하지 않기 때문에 좀더 효율적이다. 그래서 문자열 복제가 필요하지 않다면 aprt_table_setn() 또는 apr_table_addn()을 사용해라.

예를 살펴보면


/* examples we can call apr_table_setn() or apr_table_addn() */

apr_table_setn(tab, "foo", "bar"); /* string literals don't require duplication */

apr_table_setn(tab, apr_pstrdup(mp, "foo"), apr_pstrdup(mp, "bar")); 

/* 문자열이 이미 메모리풀에 할당돼 있으면 , 복제를 할 필요가 없다.*/



key로 value를 찾을때 apr_table_get()을 사용한다.

간단히 protype을 보면

/* excerpted from apr_tables.h */

 APR_DECLARE(const char *) apr_table_get(const apr_table_t *t, const char *key);


key가 테이블에 있으면, value를 리턴할 것이다. 없으면 NULL을 리턴한다.

초기에 언급한것 처럼 add API는 중복키를 허용한다. 그러나 apr_table_get()은 여러개의 value를 리턴하지는 않는다. 다만 첫번째 value만을 리턴한다. 여러개의 value를 얻을려면 table을 순회해야 한다. 그 방법을 뒤에 다루겠다.

REMAKR : 테이블 키는 대소문자를 구분하지 않는다. 역사적으로 테이블을 HTTP headers를 위해 개발되었다.


apr_table_get()은 단지 value의 포인터를 리턴한다. value 문자열의 복제를 의미하는 것은 아니다. 그래서 예제 코드는 버그를 내포하고 있다.


/* BUGGY sample */

apr_table_setn(tab, "mykey", strdup("myval"));/* apr_table_setn() doesn't duplicate the strings */


const char *v = apr_table_get(tab, "mykey");

assert(strcmp(v, "myval") == 0);  /* v's value should be "myval" */


/* v is a string allocated by strdup(3), so calling free(v) is a proper operation.

 * However, since the element still exists in the table, free(v) leaves an invalid memory in the table */

free(v);


반면에 테이블에서 엘리먼트를 삭제하면 메모리 릭을 없애기 위해 문자열이 차지하고 있는 메모리를 해제해야 한다. 


/* The similar situation above */

const char *v = apr_table_get(tab, "mykey");

apr_table_unset(tab, "mykey");

free(v);/* XXX If we forget this, it causes memory leak... */



메모리 풀을 사용하면 간단히 해결된다.

만약 모든 key, value가 메모리 풀에 있다면 apr_pool_destory()를 호출하면 된다.


테이블을 순회하는 두가지 방법이 존재하는데

한가지는 callback 함수를 사용하는 것이고 다른 하나는 apr_array_header_t를 직접 사용하는 것이다.

콜백함수를 사용한 이터레이션을 apr_table_tabld_do()를 호출하면 되고 prototype을 보면



/* excerpted from apr_tables.h */


 APR_DECLARE_NONSTD(int) apr_table_do(apr_table_do_callback_fn_t *comp,

                                     void *rec, const apr_table_t *t, ...);

/* excerpted from table-sample.c */


 apr_table_do(tab_cb, "label1", tab, NULL);

apr_table_do(tab_cb, "label2", tab, "key1", "key2", NULL);/* iteration with filters */


/* table iteration callback */

static int tab_cb(void *data, const char *key, const char *value)

{

    const char *label = data;

    printf("callback[%s]: %s %s\n", label, key, value);

    return TRUE;/* TRUE:continue iteration. FALSE:stop iteration */

}



apr_table_do()의 첫번째 인자는 callback 함수이다. 두번째 인자는 callback함수를 위한 context object를 전달한다.

세번째 인자는 table 객체이다.

남아있는 인자는 key를 필터링하는 용도이다.

다른 순회 방법은 apr_array_header_t를 직접 사용하는 것이다.

이것은 좋은 방법은 아닌데 내부 구현에 의존적인 방법이기 때문이다. 좋진 않지만 실용적이긴 하다.

테이블을 내부적으로 apr_table_entry_t 오브젝트를 가지고 있다. libapr의 동적 배열에 대해 알고 있다면 아래 예제를 이해하기가 쉬울것이다.

/* excerpted from table-sample.c */


 const apr_array_header_t *tarr = apr_table_elts(tab);

const apr_table_entry_t *telts = (const apr_table_entry_t*)tarr->elts;

int i;


for (i = 0; i < tarr->nelts; i++) {

    printf("key = %s, val = %s\n", telts[i].key, telts[i].val);

}