Closed anthologia closed 2 years ago
이 질문에 대한 답을 stackoverflow에서 찾았다.
HashMap은 serialize할 때 사용되는 wrtieObject(), readObject() 메서드를 자체적으로 구현하고 있다.
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
throws IOException {
int buckets = capacity();
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
s.writeInt(buckets);
s.writeInt(size);
internalWriteEntries(s);
}
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
위의 코드에서 writeObject()를 살펴보면, bucket 수, size, 각각의 entries만이 전송되는 걸 확인할 수 있다. 그러나 이는 table을 보내지 않는 것에 대한 답이 되지 않는다.
그 답은 HashMap의 hashCode() 메서드에서 얻을 수 있었다.
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
Objects의 hashCode() 메서드를 사용하고 있기 때문이다.
Objects의 hashCode() 메서드는 hash code 값을 객체 주소를 기반으로 생성한다. 이는 중복되지 않는 hash code 값을 생성할 수 있다는 장점이 있지만, Application의 실행마다 hash code의 값이 바뀐다. 매 실행마다 같은 주소값에 저장되기란 불가능에 가깝기 때문이다.
그래서 hash table은 매 실행마다 재생성되어야 하기 때문에 굳이 메모리를 차지하여 보낼 필요가 없다. 또, 다시 잘 생각해보면, table은 저장된 entries 의 검색을 빠르게 돕기 위한 accelation structure이므로 serialize할 필요가 없다.
HashMap의 내부 코드를 부면 데이터를 아래와 같이 저장하고 있다.
transient
키워드는 serialize 하는 과정에서 데이터를 제외하고 싶은 경우 선언된다. 예를 들어, 로그인 데이터와 같은 보안적인 내용이나 불필요한 데이터일 경우 사용할 수 있다. (deserialize 해보면, 해당 transient field에는 null 값이 들어있다.)그런데 HashMap에서는 왜 table에 transient 키워드를 적용시킬까? table을 알아야 빠르게 검색할 수 있는 게 아닐까?