LinkedHashMap은 기본적으로 HashMap을 상속받아 만들어져 있기 때문에 HashMap의 기능을 그대로 사용가능하다.
LinkedHashMap의 가장 큰 특징은
자료가 입력된 순서를 기억한다는 것이다.(자바 1.4버전 부터 제공)
HashMap의 경우 put을 통해 데이터나 객체를 넣을 때 key의 순서가 지켜지지 않는다는
것이다. 개발을 할때 코드상으로 순차적으로 key/value를 넣어도, 실제
HashMap에서는 해당 순서가 지켜지지 않는다.
LinkedHashMap은 보통 다른 설정이 없다면 기본 설정으로 Map에 삽입되는 Key값의 순서를 기반으로 정렬된다.
하지만 기존에 같은 키값이 LinkedHashMap에 있을 수 있기 때문에 HashMap과 동일하게 put() 직전에 contain()을 호출한다.
만약 내부에 같은 key가 존재한다면 LinkedHashMap에 새로 들어가는게 아니라 기존에 있던 key에 value 값이 업데이트 된다.
LinkedHashMap은 페이지 교체 알고리즘 중 하나인 LRU(Least Recently Used) 캐시를 구현하기에 적합하다. LinkedHashMap의 생성자 중
하나인 다음 생성자를 사용하면 위에 언급한 key값의 초기 순서 기반 정렬이 아니라 이미 같은
key값이 있더라도 key:value 쌍은 LinkedHashMap 후미에 들어오게 된다. 또한 get(), getOrDefault() 같은 key를 기반으로
value값을 꺼내는 메서드를 사용해도 사용된 key값은 LinkedHashMap의 후미로 들어온다.
LinkedHashMap은 HashMap의 장점을 그대로 가지고 있기 때문에 get,put,remove,containsKey 메소드를 호출할 때 O(1)의 시간 복잡도를 갖는다.
모든 Map의 동작들을 제공하며 null 역시 허용한다.
double-linked List로 모든 Entry를 유지한다.
LinkedHashMap 역시 HashMap과 마찬가지로 동기화 처리가 되어있지 않기 때문에 multi-thread환경에서 사용은 적절하지 않다.
만약 사용해야 하는 경우 접근하는 thread들이 외부에서 동기화처리되거나 아래와 같이 동기화된 LinkedHashMap을 얻어 올수 있다.
Map m = Collections.synchronizedMap(new `LinkedHashMap`(...));
Eldest는 가장 나이가 많은 이라는 의미가 있는데 말 그대로 LinkedHashMap에서 가장 오래된 entry값을 삭제해 준다.
아래는 MAX를 상수로 두고, LinkedHashMap의 사이즈가 MAX보다 크면 오래된 데이터를
자동으로 삭제하는 예제이다.
final int MAX = 10;
Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>() {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX;
}
};
출처는 링크 의 예제이며, LinkedHashMap의 기본생성자, capacity와 load factor, accessOrder 파라미터를 받는 생성자를 비교해보고 accessOrder 속성을 사용해서 LRU를 구성해 보자.
public class MyLinkedHashMap {
public static void main(String[] args) throws InterruptedException {
LinkedHashMap<String, String> lhm = new LinkedHashMap<>();
for (int i = 0; i < 10; i++) {
lhm.put("foo" + i, "bar" + i);
}
lhm.put("foo5", "re-insert bar5");
lhm.put("foo11", "bar11");
for (Entry<String, String> string : lhm.entrySet()) {
System.out.println(string);
}
}
}
다음과 같이 foo를 key값으로 둔 10개가량의 String 인스턴스를 LinkedHashMap에 넣어주고 이후에 기존 foo5라는 key에 새로운 값을 넣어주는 예제이다.
foo0=bar0
foo1=bar1
foo2=bar2
foo3=bar3
foo4=bar4
foo5=re-insert bar5
foo6=bar6
foo7=bar7
foo8=bar8
foo9=bar9
foo11=bar11
예상했던것과 마찬가지로 foo5라는 key값의 위치는 변경되지 않고 value값만 update된 걸 확인할 수 있다.
다음으로 capacity와 load factor, accessOrder를 받는 생성자를 이용해서 위 예제와 조금 다른 동작을 하도록 구성해봤다.
public class MyLinkedHashMap2 {
public static void main(String[] args) throws InterruptedException {
LinkedHashMap<String, String> lhm = new LinkedHashMap<>(1000, 0.75f, true);
for (int i = 0; i < 10; i++) {
lhm.put("foo" + i, "bar" + i);
}
lhm.put("foo5", "re-insert bar5");
lhm.put("foo11", "bar11");
lhm.get("foo3");
for (Entry<String, String> string : lhm.entrySet()) {
System.out.println(string);
}
}
}
첫번째 예제와 다른점이 있다면 LinkedHashMap을 생성할 때 order 속성을 true로 주었다.
foo0=bar0
foo1=bar1
foo2=bar2
foo4=bar4
foo6=bar6
foo7=bar7
foo8=bar8
foo9=bar9
foo5=re-insert bar5
foo11=bar11
foo3=bar3
보이는 것처럼 put() 또는 get()을 호출한 key값인 foo5, foo11, foo3 key들이
LinkedHashMap의 후미로 들어온 것을 확인 할수 있다.
마지막으로 removeEldestEntry를 사용해서 고정 사이즈가 10인 LRU 캐시의 흉내를 한번 내보도록 하겠다.
public class MyLinkedHashMap3 {
public static void main(String[] args) throws InterruptedException {
LinkedHashMap<String, String> lhm = new LinkedHashMap<String, String>(1000,0.75f,true) {
private final int MAX = 10;
protected boolean removeEldestEntry(java.util.Map.Entry<String,String> eldest) {
return size() >= MAX;
};
};
for (int i = 0; i < 10; i++) {
lhm.put("foo" + i, "bar" + i);
}
lhm.put("foo5", "re-insert bar5");
lhm.put("foo4", "re-insert bar4");
lhm.put("foo12", "bar12");
lhm.put("foo13", "bar13");
lhm.put("foo14", "bar14");
lhm.put("foo15", "bar15");
lhm.put("foo5", "re-insert bar5");
for (Entry<String, String> string : lhm.entrySet()) {
System.out.println(string);
}
}
}
2번 예제에서 removeEldestEntry()을 지정하고 MAX값을 10으로 지정했을 때 나오는 결과값이다.
foo7=bar7
foo8=bar8
foo9=bar9
foo4=re-insert bar4
foo12=bar12
foo13=bar13
foo14=bar14
foo15=bar15
foo5=re-insert bar5
Reference
https://medium.com/@igniter.yoo/java-linkedhashmap-%EC%88%9C%EC%84%9C%EB%A5%BC-%EC%9C%A0%EC%A7%80%ED%95%98%EB%8A%94-%ED%95%B4%EC%8B%9C%EB%A7%B5-11a7846d8893
https://sup2is.github.io/2019/10/31/linked-hash-map.html