11. 해시 클래스

package 해시;

import java.util.Iterator;
import java.util.LinkedList;

/**
 * 해시에서 필요한 메서드를 정의
 * 추가, 삭제, 탐색, 크기 조정 메서드
 */
interface HashI<K, V> {

	boolean add(K key, V value);

	boolean remove(K key, V value);

	V getValue(K key);

	void resize(int newSize);
}

public class Hash<K, V> implements HashI<K, V> {

	/**
	 * 해시 체인의 데이터를 담당하는 내부 클래스
	 * @param <K>
	 * @param <V>
	 */
	static class HashElement<K, V> implements Comparable<HashElement<K, V>> {

		K key; // 키
		V value; // 값

		public HashElement(K key, V value) {
			this.key = key;
			this.value = value;
		}

		/**
		 * 값이 동일하다고 같다고 판단하지 않고 키의 동일성을 비교
		 * @param h, the object to be compared.
		 * @return 키 값의 비교 결과
		 */
		@Override
		public int compareTo(HashElement<K, V> h) {
			return (((Comparable<K>)h.key).compareTo(this.key));
		}
	}

	int numElements, tableSize; // 요소의 개수, 배열의 크기
	double maxLoadFactor; // 최대 적재율, 이 변수값을 넘게 되면 크기 조정이 필요
	LinkedList<HashElement<K, V>>[] hashArray; // 체인 해시, 연결 리스트 배열
	IteratorHelper<K> iteratorHelper;

	public Hash(int tableSize) {
		this.tableSize = tableSize;

		// 객체로 배열을 만든 뒤 형변환 필요
		hashArray = (LinkedList<HashElement<K, V>>[])new LinkedList[tableSize];
		for (int i = 0; i < tableSize; i++) {
			hashArray[i] = new LinkedList<HashElement<K, V>>();
		}
		iteratorHelper = new IteratorHelper<>();

		maxLoadFactor = 0.75; // 0.75 이상이 되면 tableSize 를 늘린다
		numElements = 0; // 생성 시 자료구조에 아무것도 없기 때문에 0
	}

	public double getLoadFactor() {
		return numElements / tableSize;
	}

	@Override
	public boolean add(K key, V value) {
		// resize
		if (getLoadFactor() > maxLoadFactor) {
			resize(tableSize * 2);
		}

		// 키와 값들을 저장할 요소
		HashElement<K, V> hashElement = new HashElement(key, value);
		int hashValue = key.hashCode(); // 저장할 위치
		hashValue = hashValue & 0x7FFFFFFF;
		hashValue = hashValue % tableSize;

		hashArray[hashValue].add(hashElement); // 해당 위치에 요소 추가
		numElements++; // 요소 개수 값을 증가

		return true;
	}

	@Override
	public boolean remove(K key, V value) {
		int hashValue = key.hashCode(); // 제거할 위치 찾기
		hashValue = hashValue & 0x7FFFFFFF;
		hashValue = hashValue % tableSize;

		hashArray[hashValue].remove(); // 해당 위치의 요소 삭제
		numElements--; // 요소 개수 값을 감소

		return true;
	}

	/**
	 * Best Case: O(1), Worst Case: O(N)
	 * @param key, 찾고자 하는 값의 키
	 * @return Value
	 */
	@Override
	public V getValue(K key) {
		int hashValue = key.hashCode(); // Key 인덱스 찾기
		hashValue = hashValue & 0x7FFFFFFF;
		hashValue = hashValue % tableSize;

		for (HashElement<K, V> he : hashArray[hashValue]) {
			if (((Comparable<K>)key).compareTo(he.key) == 0) { // 키를 비교해 동일성 확인
				return he.value;
			}
		}

		return null;
	}

	@Override
	public void resize(int newSize) {
		// 크기 조정을 한 새로운 배열
		LinkedList<HashElement<K, V>>[] temp
			= (LinkedList<HashElement<K, V>>[])new LinkedList[newSize];
		for (int i = 0; i < newSize; i++) {
			temp[i] = new LinkedList<HashElement<K, V>>();
		}

		// 새로운 크기에 맞게 기존 값들을 가져오기
		for (K key : this.iteratorHelper.keys) {
			V value = getValue(key);
			HashElement<K, V> hashElement = new HashElement<>(key, value);

			int hashValue = (key.hashCode() & 0x7FFFFFFF) % newSize;
			temp[hashValue].add(hashElement);
		}

		hashArray = temp; // 복사
		tableSize = newSize; // 크기 갱신
	}

	class IteratorHelper<T> implements Iterator<T> {
		T[] keys;
		int position;

		public IteratorHelper() {
			keys = (T[]) Object[numElements];
			int p = 0;

			for (int i = 0; i < tableSize; i++) {
				LinkedList<HashElement<K, V>> list = hashArray[i];
				for (HashElement<K, V> he : list) {
					keys[p++] = (T)he.key;
				}
			}
			position = 0;
		}

		@Override
		public boolean hasNext() {
			return position < keys.length;
		}

		@Override
		public T next() {
			if (!hasNext()) {
				return null;
			}
			return keys[position++];
		}
	}
}
public class Hash<K, V> implements HashI<K, V> {

	LinkedList<HashElement<K, V>>[] hashArray;
	int tableSize, numElements;
	double maxLoadFactor;

	public Hash(int tableSize) {
		this.tableSize = tableSize;

		// 객체로 배열을 만든 뒤 형변환 필요
		hashArray = (LinkedList<HashElement<K,V>>[]) new LinkedList[tableSize];
		for (int i = 0; i < tableSize; i++) {
			hashArray[i] = new LinkedList<HashElement<K, V>>();
		}

		maxLoadFactor = 0.75; // 0.75 이상이 되면 tableSize 를 늘린다
		numElements = 0; // 생성 시 자료구조에 아무것도 없기 때문에 0
	}

12. add(), remove()

	@Override
	public boolean add(K key, V value) {
		// resize
		if (getLoadFactor() > maxLoadFactor) {
			resize(tableSize * 2);
		}

		// 키와 값들을 저장할 요소
		HashElement<K, V> hashElement = new HashElement(key, value);
		int hashValue = key.hashCode(); // 저장할 위치
		hashValue = hashValue & 0x7FFFFFFF;
		hashValue = hashValue % tableSize;

		hashArray[hashValue].add(hashElement); // 해당 위치에 요소 추가
		numElements++; // 요소 개수 값을 증가
		
		return true;
	}
	@Override
	public boolean remove(K key, V value) {
		int hashValue = key.hashCode(); // 제거할 위치 찾기
		hashValue = hashValue & 0x7FFFFFFF;
		hashValue = hashValue % tableSize;

		hashArray[hashValue].remove(); // 해당 위치의 요소 삭제
		numElements--; // 요소 개수 값을 감소

		return true;
	}

13. getValue()

	@Override
	public V getValue(K key) {
		int hashValue = key.hashCode(); // Key 인덱스 찾기
		hashValue = hashValue & 0x7FFFFFFF;
		hashValue = hashValue % tableSize;

		for (HashElement<K, V> he : hashArray[hashValue]) {
			if (((Comparable<K>)key).compareTo(he.key) == 0) {
				return he.value;
			}
		}
		
		return null;
	}

14. resize()

	@Override
	public void resize(int newSize) {
		// 크기 조정을 한 새로운 배열
		LinkedList<HashElement<K, V>>[] temp
			= (LinkedList<HashElement<K, V>>[])new LinkedList[newSize];
		for (int i = 0; i < newSize; i++) {
			temp[i] = new LinkedList<HashElement<K, V>>();
		}

		// 새로운 크기에 맞게 기존 값들을 가져오기
		for (K key : this) {
			V value = getValue(key);
			HashElement<K, V> hashElement = new HashElement<>(key, value);

			int hashValue = (key.hashCode() & 0x7FFFFFFF) % newSize;
			temp[hashValue].add(hashElement);
		}

		hashArray = temp; // 복사
		tableSize = newSize; // 크기 갱신
	}