λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Java

[Java DeepDive] - Map(HashMap) 1. κ°œλ…, ν•„λ“œ, μƒμ„±μž

by 주발2 2022. 11. 27.
λ°˜μ‘ν˜•

πŸ”—  Map(HashMap)

Javaμ—μ„œ κ°€μž₯ 많이 μ‚¬μš©ν•˜λŠ” μžλ£Œκ΅¬μ‘°λŠ” List와 Map이 μ•„λ‹κΉŒ μƒκ°ν•©λ‹ˆλ‹€.

μ–΄λ””μ—μ„œλ“  ꡉμž₯히 많이 ν™œμš©ν•˜λŠ” 자료ꡬ쑰둜 μ €λ˜ν•œ 많이 μ‚¬μš©ν•˜λŠ”λ°μš”, μ΄λ²ˆμ—λŠ” Map μΈν„°νŽ˜μ΄μŠ€μ˜ κ°œλ…κ³Ό μ œκ³΅λ˜λŠ” APIλ“€, κ΅¬ν˜„μ²΄μΈ HashMap에 λŒ€ν•΄ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€. 

(예제 μ½”λ“œλŠ” κΉƒν—ˆλΈŒμ—μ„œ ν™•μΈν•˜μ‹€ 수 있으며, 버전은 Java 11을 μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.)

 

https://www.geeksforgeeks.org/java-util-hashmap-in-java-with-examples/

Map μΈν„°νŽ˜μ΄μŠ€λŠ” Collection ν”„λ ˆμž„μ›Œν¬μ™€λŠ” λ‹€λ₯΄κ²Œ 킀와 값을 ν•˜λ‚˜μ˜ 쌍으둜 μ €μž₯ν•˜λŠ” Key-Value ν˜•μ‹μ˜ 자료ꡬ쑰 μž…λ‹ˆλ‹€.

 

KeyλŠ” 이름 κ·ΈλŒ€λ‘œ κ°’(Value)을 μ°ΎκΈ° μœ„ν•œ 역할을 ν•˜λ©° Map μΈν„°νŽ˜μ΄μŠ€μ˜ key, valueλŠ” λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ 가지고 μžˆμŠ΅λ‹ˆλ‹€.

  • keyλŠ” 쀑볡이 λΆˆκ°€λŠ₯ν•˜μ§€λ§Œ, valueλŠ” 쀑볡이 κ°€λŠ₯ν•©λ‹ˆλ‹€.
  • 각 keyλŠ” μ΅œλŒ€ ν•˜λ‚˜μ˜ value에 맀핑될 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 일반적으둜 μš”μ†Œμ˜ μˆœμ„œλŠ” μœ μ§€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • Map μ»¬λ ‰μ…˜μ— μ†ν•˜λŠ” ν΄λž˜μŠ€λŠ” HashMap, LinkedHashMap, Hashtable, TreeMap λ“±μ˜ κ΅¬ν˜„ ν΄λž˜μŠ€κ°€ μžˆμŠ΅λ‹ˆλ‹€.

 

개인적으둜 Map μΈν„°νŽ˜μ΄μŠ€μ˜ κ΅¬ν˜„μ²΄λ‘œ 동기화가 ν•„μš”ν•œ 일뢀 μΌ€μ΄μŠ€λŠ” ConcurrentHashMap을, Key의 μˆœμ„œκ°€ ν•„μš”ν•  경우 TreeMap을 κ·Έ μ™Έμ˜ μΌ€μ΄μŠ€λŠ” HashMap을 μ‚¬μš©μ„ ν–ˆλ˜ 것 κ°™μŠ΅λ‹ˆλ‹€.

 

Map μΈν„°νŽ˜μ΄μŠ€μ— μ‘΄μž¬ν•˜λŠ” 좔상 λ©”μ„œλ“œ, default λ©”μ„œλ“œ λͺ©λ‘ μž…λ‹ˆλ‹€.

(참고둜 of λ©”μ„œλ“œμ˜ 경우 Java 9μ—μ„œ μΆ”κ°€λœ νŒ©ν† λ¦¬ μƒμ„±μž λ©”μ„œλ“œλ‘œ μˆ˜μ •μ΄ λΆˆκ°€λŠ₯ν•œ Map을 μƒμ„±ν•©λ‹ˆλ‹€.)

μœ„ ν…ŒμŠ€νŠΈ 처럼 Map.of() λ©”μ„œλ“œλ₯Ό 톡해 μƒμ„±ν•œ 객체λ₯Ό λ³€κ²½(put)ν•  λ•Œ UnsupportedOperationException μ˜ˆμ™Έκ°€ λ°œμƒν•©λ‹ˆλ‹€.

 

β€» μΈν…”λ¦¬μ œμ΄ 같은 λ˜‘λ˜‘ν•œ IDEκ°€ κ²½κ³ λ₯Ό λ‚˜νƒ€λ‚΄μ£Όκ³  μžˆκΈ΄ν•˜μ§€λ§Œ, 개인적인 μƒκ°μœΌλ‘œλŠ” μ½”ν‹€λ¦°μ˜ Listλ‚˜ Map처럼 데이터λ₯Ό μΆ”κ°€ν•˜κ±°λ‚˜ μ‚­μ œν•˜λŠ” put(), add(), remove() λ“±μ˜ λ©”μ„œλ“œλ₯Ό μ• μ΄ˆμ— μ œκ³΅ν•˜μ§€ μ•Šλ„λ‘ κ°•μ œν•˜μ—¬ 컴파일 νƒ€μž„μ—μ„œ 확인이 κ°€λŠ₯ν–ˆμœΌλ©΄ μ–΄λ• μ„κΉŒ ν•˜λŠ” 생각이 λ“œλ„€μš”.. 😯

 

ImmutableCollections ν΄λž˜μŠ€λŠ” λΆˆλ³€ μ»¬λ ‰μ…˜μ„ μœ„ν•œ μ»¨ν…Œμ΄λ„ˆ ν΄λž˜μŠ€μž…λ‹ˆλ‹€. 즉, Map뿐만 μ•„λ‹ˆλΌ List, Set 등에 λŒ€ν•΄ λΆˆλ³€ 객체λ₯Ό μƒμ„±ν•˜λŠ” μƒμ„±μžλ„ μ‘΄μž¬ν•©λ‹ˆλ‹€.

 

 

 

πŸ”—  HashMap μ„€λͺ…

Map의 κ΅¬ν˜„μ²΄ 쀑 κ°€μž₯ 많이 μ‚¬μš©ν•˜λŠ” HashMap에 λŒ€ν•΄ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

HashMap ν΄λž˜μŠ€μ— μ„€λͺ…에 λŒ€ν•œ μ£Όμ„λ¬ΈμΈλ°μš”, ꡉμž₯히 κΈΈκΈ°λ•Œλ¬Έμ— κ°„λž΅νžˆ μ •λ¦¬ν•˜λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • Map μΈν„°νŽ˜μ΄μŠ€μ˜ Hashtable 기반 κ΅¬ν˜„μ²΄
  • 이 κ΅¬ν˜„μ²΄(HashMap)λŠ” key와 valueκ°€ null을 ν—ˆμš©ν•¨
  • λ™κΈ°ν™”λ˜μ§€ μ•Šκ³  null을 ν—ˆμš©ν•œλ‹€λŠ” 점을 μ œμ™Έν•˜λ©΄ Hashtableκ³Ό 거의 동일함
  • μˆœμ„œλ₯Ό μœ μ§€ν•˜μ§€ μ•ŠμŒ
  • ν•΄μ‹œ ν•¨μˆ˜κ°€ 버킷 κ°„ μš”μ†Œλ₯Ό 적절히 λΆ„μ‚°ν•œλ‹€κ³  κ°€μ •ν•  λ•Œ κΈ°λ³Έ μž‘μ—…(get, put)에 λŒ€ν•΄ O(1) 제곡
  • 반볡 μ„±λŠ₯이 μ€‘μš”ν•œ 경우 초기 μš©λŸ‰μ„ λ„ˆλ¬΄ λ†’κ²Œ μ„€μ •ν•˜μ§€ μ•ŠλŠ” 것이 맀우 μ€‘μš”ν•¨
  • HashMap μΈμŠ€ν„΄μŠ€λŠ” μ„±λŠ₯에 영ν–₯을 λ―ΈμΉ˜λŠ” 두 가지 λ§€κ°œλ³€μˆ˜: 초기 μš©λŸ‰, λΆ€ν•˜ κ³„μˆ˜κ°€ μ‘΄μž¬ν•¨
    • μš©λŸ‰μ€ ν•΄μ‹œ ν…Œμ΄λΈ”μ˜ 버킷 수λ₯Ό λ‚˜νƒ€λ‚΄λ©°, 초기 μš©λŸ‰μ€ ν•΄μ‹œ ν…Œμ΄λΈ”μ„ 생성할 λ•Œμ˜ μš©λŸ‰
    • λΆ€ν•˜μœ¨μ€ μš©λŸ‰μ΄ μžλ™μœΌλ‘œ μ¦κ°€ν•˜κΈ° μ „ ν•΄μ‹œ ν…Œμ΄λΈ”μ΄ μ–Όλ§ˆλ‚˜ 꽉 μ°° 수 μžˆλŠ”μ§€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” 척도
    • ν•΄μ‹œ ν…Œμ΄λΈ”μ˜ ν•­λͺ© 수 > ν˜„μž¬ μš©λŸ‰ * λ‘œλ“œ νŒ©ν„° 일 경우, ν•΄μ‹œ ν…Œμ΄λΈ”μ€ μž¬ν•΄μ‹œλ˜μ–΄ ν•΄μ‹œ ν…Œμ΄λΈ”μ˜ 버킷 μˆ˜κ°€ μ•½ 두 λ°° 증가
  • 일반적으둜 κΈ°λ³Έ λΆ€ν•˜ κ³„μˆ˜(0.75)λŠ” μ‹œκ°„κ³Ό 곡간 λΉ„μš© κ°„ 쒋은 κ· ν˜•μ„ 제곡
  • HashMap μΈμŠ€ν„΄μŠ€μ— λ§Žμ€ 맀핑이 μ €μž₯된 경우, μΆ©λΆ„νžˆ 큰 μš©λŸ‰μœΌλ‘œ μƒμ„±ν•˜λ©΄ ν…Œμ΄λΈ” ν™•μž₯ μ‹œ ν•„μš”ν•œ μžλ™ μž¬ν•΄μ‹±μ„ μˆ˜ν–‰ν•˜λŠ” 것보닀 더 효율적으둜 맀핑을 μ €μž₯ν•  수 있음
  • 동기화가 λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— ν•„μš”ν•  경우 μ™ΈλΆ€μ μœΌλ‘œ 동기화λ₯Ό ν•΄μ•Ό 함
  • 동기화가 ν•„μš”ν•  경우 μ»¬λ ‰μ…˜μ„ μ‚¬μš©ν•˜μ—¬ Collections.synchronizedMap λ©”μ„œλ“œλ₯Ό 톡해 Map을 Wrapped ν•΄μ•Ό 함
    • Collections.synchronizedMap의 경우, synchronizedList() 처럼 λ‚΄λΆ€ λ‘œμ§μ€ λŒ€λΆ€λΆ„ synchronized λΈ”λŸ­μœΌλ‘œ κ΅¬ν˜„μ΄ λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
    • μ£Όμ„μ—λŠ” μ—†μ§€λ§Œ ConcurrentHashMap 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” 방법도 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.

μ•Œκ³ μžˆλ˜ λ‚΄μš©λ„ 있고 처음 λ³΄λŠ” λ‚΄μš©λ„ 많이 μ‘΄μž¬ν•˜λŠ”λ°μš” μ•„λž˜μ—μ„œ 쑰금 더 μžμ„Ένžˆ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

 

πŸ”—  HashMap ν•„λ“œ 1. μƒμˆ˜

HashMap ν΄λž˜μŠ€μ—λŠ” μœ„ μ‚¬μ§„μ²˜λŸΌ μ—¬λŸ¬ ν•„λ“œλ“€μ΄ μ‘΄μž¬ν•˜λŠ”λ°μš”, 각 ν•„λ“œμ— λŒ€ν•œ μ„€λͺ…은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • DEFAULT_INITIAL_CAPACITY: Map의 κΈ°λ³Έ 초기 μš©λŸ‰(=버킷 수)으둜 λ””ν΄νŠΈ 값은 16
  • MAXIMUM_CAPACITY: μ΅œλŒ€ μš©λŸ‰μœΌλ‘œ ν•΄λ‹Ή 값을 μ΄ˆκ³Όν•  수 μ—†μŠ΅λ‹ˆλ‹€. 값은 2^30으둜 1,073,741,824
  • DEFAULT_LOAD_FACTOR: Map의 μš©λŸ‰μ„ λŠ˜λ €μ•Ό ν•˜λŠ” μ‹œκΈ°λ₯Ό κ²°μ •ν•˜λŠ” μ§€ν‘œλ‘œ κΈ°λ³Έ 값은 0.75(75%)
    • 예λ₯Ό λ“€μ–΄ HashMap의 μ‚¬μ΄μ¦ˆκ°€ 16이고 Load Factor은 0.75일 경우 전체 μ‚¬μ΄μ¦ˆ 16μ—μ„œ 75%에 ν•΄λ‹Ήν•˜λŠ” 12 크기의 μ €μž₯μ†Œκ°€ 이미 μ‚¬μš©μ€‘μ΄λΌλ©΄, μ €μž₯μ†Œμ˜ 크기λ₯Ό resizingν•  μ‹œκΈ°λΌκ³  νŒλ‹¨ν•©λ‹ˆλ‹€.
    • μΆ”ν›„ μ„€λͺ…λ“œλ¦¬κ² μ§€λ§Œ, resizing μ‹œ μ €μž₯μ†Œ ν¬κΈ°λŠ” 2배둜 μ¦κ°€ν•©λ‹ˆλ‹€.
  • ~_THRESHOLDTHRESHOLDλŠ” μž„κ³—κ°’μ„ λ‚˜νƒ€λ‚΄λŠ” μš©μ–΄λ‘œ 트리λ₯Ό μ‚¬μš©ν•˜κ±°λ‚˜/μ‚¬μš©ν•˜μ§€ μ•Šκ±°λ‚˜ ν•˜κΈ° μœ„ν•œ μž„κ³„κ°’μ„ λ‚˜νƒ€λ‚Έλ‹€κ³  주석에 μ„€λͺ…이 λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. λ²„ν‚·μ˜ κ°―μˆ˜κ°€ 일정 μˆ˜μ€€μ„ λ„˜μ–΄κ°€λ©΄ 버킷 관리λ₯Ό Tree ν˜•μ‹μœΌλ‘œ, 그렇지 μ•Šλ‹€λ©΄ LinkedList(Node) ν˜•νƒœλ‘œ κ΄€λ¦¬ν•˜λŠ”κ²ƒμ— 따라 값을 μ˜λ―Έν•˜λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€. (μ •ν™•ν•˜κ²ŒλŠ” μ΄ν•΄ν•˜μ§€ λͺ»ν•΄ 틀릴 수 μžˆμŠ΅λ‹ˆλ‹€..😦)

 

μ§€λ‚œ List의 ArrayList처럼 HashMap도 초기 μš©λŸ‰μ„ 적절히 μ„€μ •ν•˜μ—¬ 객체λ₯Ό μƒμ„±ν•˜λŠ” 것이 νš¨μœ¨μ μž…λ‹ˆλ‹€.

(List의 경우 Arrays.copyOf() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ μƒˆλ‘œμš΄ 배열을 μƒμ„±ν•˜λŠ” μž‘μ—…μ„ μ§„ν–‰ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.)

  • Map의 경우 각 μš”μ†Œλ₯Ό bucket이라고 ν•˜λŠ” 곡간에 μ €μž₯을 ν•˜λŠ”λ° 초기 μš©λŸ‰μ„ μ„€μ •ν•˜μ§€ μ•Šμ•„ 16인 경우 λΆ€ν•˜μœ¨(Load Factor) λŒ€λΉ„ 곡간이 가득 μ°¨λ©΄ resizingν•˜λŠ” μž‘μ—…μ΄ μˆ˜ν–‰λ˜λ©° 이 λ•Œ λͺ¨λ“  μš”μ†Œλ“€μ— λŒ€ν•΄ ν•΄μ‹œκ°’μ„ 톡해 μ–΄λ– ν•œ bucket에 μ €μž₯할지 계산을 ν•˜λŠ” κ³Όμ •(rehasing)이 μžˆκΈ°λ•Œλ¬Έμ— 객체λ₯Ό 생성할 λ•Œ μ μ ˆν•œ μš©λŸ‰μ„ μ„€μ •ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.
  • λ°˜λŒ€λ‘œ 초기 μš©λŸ‰μ„ λ„ˆλ¬΄ 크게 μ„€μ •ν•œλ‹€λ©΄ μ‚¬μš©λ˜μ§€ μ•ŠλŠ” λΆˆν•„μš”ν•œ 곡간을 λ‚­λΉ„ν•˜κΈ°μ— μ μ ˆν•œ κ°’ 섀정이 μ€‘μš”ν•  것 κ°™μŠ΅λ‹ˆλ‹€.

 

 

 

πŸ”—  HashMap ν•„λ“œ 2. λ³€μˆ˜

HashMap의 key-value μš”μ†ŒλŠ” μœ„ μ‚¬μ§„μ²˜λŸΌ Node<K,V>[] table λ³€μˆ˜μ—μ„œ 관리가 λ©λ‹ˆλ‹€.

threshold의 경우 Map의 크기 쑰정이 ν•„μš”ν•  λ•Œ λ‹€μŒ 크기 κ°’(μš©λŸ‰ * λΆ€ν•˜μœ¨)을 μ˜λ―Έν•©λ‹ˆλ‹€.

 

 

 

πŸ”—  HashMap μƒμ„±μž

HashMap의 μƒμ„±μžλŠ” μœ„ μ‚¬μ§„μ²˜λŸΌ 총 4κ°œκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€.

 

int νƒ€μž…μ˜ μΈμžλŠ” HashMap의 초기 μš©λŸ‰μ„ μ˜λ―Έν•˜λ©°, float νƒ€μž… μΈμžλŠ” μœ„μ—μ„œ μ‚΄νŽ΄λ³΄μ•˜λ˜ Load Factor의 λΆ€ν•˜μœ¨μ„ μ˜λ―Έν•©λ‹ˆλ‹€.

λ§ˆμ§€λ§‰ Map νƒ€μž… μΈμžλŠ” λ™μΌν•œ 맀핑을 ν•˜κ³  μžˆλŠ” Map을 λ°›μ•„μ„œ μƒμ„±ν•©λ‹ˆλ‹€.

 

μƒμ„±μžμ—λŠ” μœ νš¨μ„± 검증을 톡해 초기 μš©λŸ‰, loadFactor 값을 μ„€μ •ν•˜λŠ”λ°μš” μœ„μ—μ„œ tableSizeFor() λ©”μ„œλ“œμ— λŒ€ν•΄μ„œλ§Œ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

초기 μš©λŸ‰(initialCapacity)λ₯Ό 10으둜 μ„€μ •ν–ˆμ„ λ•Œ tableSizeFor λ©”μ„œλ“œκ°€ λ¦¬ν„΄ν•˜λŠ” 값은 16μž…λ‹ˆλ‹€.

λ”°λΌμ„œ, threshold 값은 16이 λ©λ‹ˆλ‹€.

(μ•„λž˜ μ£Όμ„μ²˜λŸΌ λ¦¬ν„΄ν•˜λŠ” 값은 항상 2의 제곱수 μž…λ‹ˆλ‹€... 2-4-8-16-32-64 ...)

 

numberOfLeadingZeros() λ©”μ„œλ“œμ˜ λ‘œμ§μ€ μ•„λž˜μ™€ κ°™μœΌλ©° ν•΄λ‹Ή μ½”λ“œμ— λŒ€ν•œ μ„€λͺ…은 stackoverflowλ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”:

 

μ΄μƒμœΌλ‘œ HashMap의 κ°œλ… 및 각 ν•„λ“œ μƒμ„±μžμ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

λ‚΄μš©μ΄ λ„ˆλ¬΄ 길어지기에 2편으둜 λ‚˜λˆ„μ–΄ λ‹€μŒ μ‹œκ°„μ—” HashMap의 λ‹€μ–‘ν•œ λ©”μ„œλ“œμ™€ 리사이징, ν•΄μ‹œ 등에 λŒ€ν•΄ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

 

πŸ“„  참고자료

 

λ°˜μ‘ν˜•

λŒ“κΈ€