Dev/Flutter

Dart 에서 Collection Map 을 알아보자

healthyryu 2025. 6. 25. 12:37

Map은 키와 값을 한쌍으로 저장하는 컬렉션 입니다.

Map<Key Type, Value Type>

Map<String, String>
Map<String, int>

 

 

특징

  • 키 값은 중복이 없어야 함
  • 키 하나에 값 하나만 연결(매핑)
  • 키(key) 타입과 값(value) 타입은 같게 혹은 다르게 설정이 가능

 

기본적인 Map 생성 방법은 다음과 같습니다.

var mapDefault = <String, int>{};
var mapDynamic = {}; // Map<dynamic, dynamic> 으로 추론됨

// 명시적으로 타입을 선언한 형태
var mapDefault = <String, int>{ "apple" : 1, "banana" : 2};

// 암묵적으로 타입을 선언하지 않은 형태
var mapDynamic = {"apple" : 1, "banana" : 2};

 

 

Map for 문 사용한 탐색 방법

기본 Map

Map<String, int> scores = {'Alice': 90, 'Bob': 85, 'Charlie': 95};

 

forEach

scores.forEach((key, value) {
  print('$key: $value');
});

 

for - key

for (var key in scores.keys) {
  print('key: $key');
}

 

for - value

for (var value in scores.values) {
  print('value: $value');
}

 

for - entry

for (var entry in scores.entries) {
    print('key : ${entry.key}');
    print('value : ${entry.value}');
}

 

 

기본적인 Map 의 종류는 크게 3가지로 나뉘며 일반적으로 사용하는 Map 의 구현체는 LinkedHashMap 입니다.

  • LinkedHashMap
    • 키의 삽입 순서를 유지하는 Map
    • 일반 Map은 키의 순서가 보장되지 않지만, LinkedHashMap은 입력된 순서대로 키를 관리
  • HashMap
    • 해시 테이블을 기반으로 구현된 Map
    • 키의 해시 값을 사용하여 데이터를 저장하므로, 키를 기반으로 한 검색 속도가 빠름
  • SplayTreeMap
    • 자체적으로 균형을 유지하는 트리 기반의 Map
    • 특정 키에 접근할 때마다 해당 키가 트리 구조의 상위 노드로 이동하므로, 최근에 사용된 키에 대한 접근 속도가 빨라짐

 

위에서 언급했듯이 일반적으로 Map 리터럴({})의 기본 구현체로 LinkedHashMap 을 사용합니다. 즉 Map<String, int> map = {’a’ : ‘a’, … }; 와 같이 작성한다면 {’a’ : ‘a’, … } 은 LinkedHashMap 입니다.

 

예를 들어서 다음과 같이 Map 을 만들 경우에

Map<String, int> person = {'Alice': 30, 'Bob': 25, 'Charlie': 50};

 

위 코드가 내부적으로는 아래와 같이 LinkedHashMap 을 사용하는 것과 동일한 효과를 갖습니다.

Map<String, int> personMap = LinkedHashMap.from({'Alice': 30, 'Bob': 25, 'Charlie': 50});

 

Dart에서는 Map 리터럴 ({})의 기본 구현체로 LinkedHashMap 을 사용합니다.

 

 

Map의 순서 에 대해서

Map 의 경우 LinkedHashMap 순서를 유지하지만 HashMap 의 경우 순서를 신뢰할 수 없습니다. 별도의 추가적인 작업을 하지 않는 Map 을 구현할 경우에는 LinkedHashMap 을 사용하기 때문에 순서를 가진 형태로 데이터를 출력할 수 있습니다.

 

조금 더 자세히 설명을 하자면 LinkedHashMap 의 경우 내부적으로는 Hash Table 과 Linked List 를 모두 사용하는 구현체입니다. HashMap 의 단점인 순서를 보장하면서 List 의 단점인 검색이 느린 부분을 보완해서 나온 형태라고 보면 됩니다.


내부적으로는 HashTable, LinkedList 을 사용해서 데이터를 빠르게 검색(O(1))하거나 순차적으로 데이터를 출력할 수 있습니다. 다만, LinkedHashMap 이 슈퍼파워를 가진 구현체는 아닙니다. 당연하게도 Trade-off 를 해야합니다. HashMap 사용할때보다는 데이터 저장에 시간이 조금 더 소요(약 5~20% 증가)되고 메모리도 조금 더 사용(약 20~40% 증가)하게 됩니다.

 

 

아래와 같이 Map 을 만들었을 경우에 내부적으로 어떻게 데이터가 저장되는지 확인해보시면 좋겠습니다.

Map

final map = {'apple' : 1, 'banana' : 2, 'cherry' : 3 };

 

Hash Table

Index | Entry
------+----------------------------
  0   | null
  1   | null
  2   | null
  3   | [Entry(key='banana', value=2)]
  4   | null
  5   | [Entry(key='apple', value=1)]
  6   | null
  7   | null
  8   | null
  9   | [Entry(key='cherry', value=3)]

 

Doubly Linked List

head
 ↓
[Entry(key='apple', value=1)]
  ↕
[Entry(key='banana', value=2)]
  ↕
[Entry(key='cherry', value=3)]
 ↑
tail

 

 

Map 을 List 만들기 

값만 리스트로 map.values.toList()
키만 리스트로 map.keys.toList()
객체 리스트로 map.entries.map((e) => MyClass(e.key, e.value)).toList()
MapEntry 리스트로 map.entries.toList()
forEach로 직접 추가 map.forEach((k, v) => list.add(MyClass(k, v)));

 

Map 자체만 가지고는 List 로 변경이 불가능 합니다. Map 에 존재하는 Iterable 구현체 요소들(keys, values, entries) 를 통해서 만들수가 있습니다.

 

class Person {
  String name;
  int age;
  Person(this.name, this.age);
}

void main() {
  Map<String, int> personMap = {'Alice': 30, 'Bob': 25, 'Charlie': 50};

  List<Person> personList = personMap.entries
      .map((e) => Person(e.key, e.value))
      .toList();
}
반응형