해시(Hash)는 임의의 크기를 갖는 데이터를 고정된 크기의 값으로 변환하는 알고리즘입니다. 이렇게 변환된 값은 해시 값 또는 해시 코드라고도 합니다.
해시 함수는 입력 데이터의 작은 변화에도 결과 값이 크게 달라지도록 설계되어 있습니다. 따라서 입력 데이터가 달라지면 해시 값도 크게 달라집니다.
해시 함수의 특징은 다음과 같습니다
일방향성(One-way property) : 해시 함수는 입력 값을 해시 값으로 변환하는 것은 가능하지만, 해시 값을 역으로 입력 값으로 변환하는 것은 거의 불가능합니다. 즉, 해시 값을 통해 원래 데이터를 복원하는 것은 매우 어렵습니다.
결정론적(결정적, Deterministic) : 동일한 입력에 대해서는 항상 동일한 해시 값을 생성합니다. 예를 들어, "Hello, world!"라는 문자열을 해시 함수에 입력하면 항상 같은 해시 값을 얻을 수 있습니다.
충돌 저항성(Collision resistance) : 두 개의 다른 입력에 대해 동일한 해시 값을 얻는 충돌이 발생하는 것을 매우 어렵게 만들어야 합니다. 좋은 해시 함수는 충돌이 거의 발생하지 않도록 설계되어야 합니다.
해시 함수는 다양한 분야에서 유용하게 활용됩니다. 몇 가지 예를 들어보면
데이터 무결성 검사 : 해시 함수는 데이터의 무결성을 확인하기 위해 사용될 수 있습니다. 데이터를 전송 또는 저장하기 전에 해시 값을 계산하고, 데이터를 받는 측에서 다시 해시 값을 계산하여 이를 비교함으로써 데이터가 변경되지 않았는지 확인할 수 있습니다.
암호화 : 해시 함수는 암호화 알고리즘에서도 사용됩니다. 비밀번호 저장 시에는 실제 비밀번호를 저장하는 대신에 해시 값을 저장하여 비밀번호를 보호합니다. 사용자가 로그인할 때 입력한 비밀번호의 해시 값을 계산하여 저장된 해시 값과 비교하여 인증을 수행합니다.
데이터 검색 : 해시 함수를 사용하여 데이터를 저장하고 검색할 수 있습니다. 해시 함수를 통해 데이터의 특정 속성을 기반으로 고유한 해시 값을 생성하고, 이를 인덱스로 사용하여 데이터를 빠르게 검색할 수 있습니다.
좋은 해시 함수는 다음과 같은 속성을 갖추어야 합니다
빠른 계산 속도 : 해시 함수는 빠르게 계산될 수 있어야 합니다.
충돌 저항성 : 충돌이 발생할 확률이 매우 낮아야 합니다.
분포성 : 입력 데이터의 작은 변화에도 해시 값이 균일하게 분포되어야 합니다.
주의할 점은 해시 함수가 충돌이 발생할 수 있다는 것입니다. 입력 공간이 무한하지만 해시 값의 공간은 제한적이기 때문에 두 개 이상의 다른 입력이 동일한 해시 값을 가질 수 있습니다.
이를 충돌(Collision)이라고 합니다. 좋은 해시 함수는 충돌이 발생할 확률을 매우 낮추는 것이 목표입니다. 충돌이 발생하면 해시 값이 중복되는 데이터를 구별하는 것이 어려워질 수 있습니다.
많은 알고리즘이 해시 함수로 사용될 수 있으며, 가장 널리 알려진 해시 함수 중 일부에는 MD5, SHA-1, SHA-256, SHA-3 등이 있습니다. 그러나 보안 요구 사항이나 응용 프로그램의 목적에 따라 적절한 해시 함수를 선택해야 합니다.
데이터 검색에서 사용되는 해시 알고리즘은 주로 해시 테이블(Hash Table)이라는 자료구조를 구현하는 데에 활용됩니다. 해시 테이블은 효율적인 데이터 검색을 위해 키(Key)와 값(Value)의 쌍으로 이루어진 데이터를 저장하는 자료구조입니다.
해시 테이블(Hash table)은 다음과 같은 구성 요소로 구성됩니다:
해시 함수 (Hash function): 해시 함수는 입력 데이터를 해시 코드로 변환하는 알고리즘입니다. 입력 데이터의 특성에 따라 해시 함수는 고유한 해시 코드를 생성해야 합니다. 이 해시 코드는 버킷의 인덱스로 사용되어 데이터를 저장하고 검색할 수 있게 합니다.
버킷 (Bucket): 버킷은 데이터를 저장하는 해시 테이블의 각 요소입니다. 일반적으로 배열 형태로 구성되며, 각 버킷은 데이터를 저장하기 위한 공간을 가지고 있습니다. 해시 코드에 따라 버킷의 인덱스로 매핑되어 데이터가 저장됩니다.
데이터 (Data): 해시 테이블에 저장되는 실제 데이터입니다. 데이터는 키-값 쌍의 형태로 저장될 수 있으며, 키를 통해 데이터에 접근하고 검색할 수 있습니다. 각 데이터는 해시 함수를 통해 생성된 해시 코드에 따라 적절한 버킷에 저장됩니다.
충돌 해결 메커니즘 (Collision resolution mechanism): 해시 함수를 통해 생성된 해시 코드는 고유해야 하지만, 서로 다른 데이터가 동일한 해시 코드를 가질 수 있습니다. 이를 "충돌(collision)"이라고 합니다. 충돌이 발생할 경우에는 충돌 해결 메커니즘이 필요합니다. 일반적으로 충돌을 해결하기 위해 체이닝(Chaining) 또는 개방 주소법(Open addressing)과 같은 방법을 사용합니다.
로드 팩터 (Load factor): 로드 팩터는 해시 테이블에 저장된 데이터의 양과 버킷의 수 사이의 관계를 나타냅니다. 로드 팩터는 해시 테이블의 성능에 영향을 미치는 중요한 요소로서, 적절한 로드 팩터를 유지하기 위해 필요에 따라 버킷의 크기를 동적으로 조절할 수 있습니다.
로드 팩터(Load factor)는 해시 테이블(Hash table)에서 현재 저장된 데이터의 양과 버킷의 수 사이의 비율을 나타내는 값입니다.
일반적으로 로드 팩터는 저장된 데이터의 수를 버킷의 수로 나눈 값으로 표현됩니다. 로드 팩터는 해시 테이블의 성능과 공간 활용에 영향을 미치는 중요한 요소입니다. 로드 팩터가 높을수록 해시 테이블에 저장된 데이터의 양이 버킷 수에 비해 많다는 의미이고, 낮을수록 데이터의 양이 적다는 의미입니다.
로드 팩터의 값에 따라 해시 테이블의 성능이 영향을 받습니다. 로드 팩터가 낮으면 충돌이 적어 성능이 향상될 수 있지만, 메모리 공간의 낭비가 발생할 수 있습니다. 반대로 로드 팩터가 높으면 충돌이 더 자주 발생하여 성능이 저하될 수 있지만, 메모리 공간을 보다 효율적으로 사용할 수 있습니다.
로드 팩터를 적절하게 조정하는 것은 해시 테이블의 성능 최적화에 중요합니다. 일반적으로 로드 팩터가 특정 임계값을 초과하면 해시 테이블의 크기를 조정하거나 재조정하는 작업이 필요할 수 있습니다. 이를 통해 해시 테이블의 성능을 유지하면서 공간을 효율적으로 활용할 수 있습니다.
로드 팩터의 선택은 해시 테이블을 사용하는 애플리케이션의 요구사항과 예상되는 데이터의 양에 따라 달라집니다. 일반적으로 0.7 또는 0.8을 넘지 않는 로드 팩터를 유지하는 것이 성능과 공간 활용을 균형있게 유지하는 방법일 수 있습니다.
[HASH : 나무위키]https://namu.wiki/w/%ED%95%B4%EC%8B%9C
해시 테이블(Hash Table)은 데이터를 저장하는 자료구조로, 효율적인 검색을 위해 사용됩니다. 해시 테이블은 해시 함수를 사용하여 데이터를 해시 값으로 변환하고, 이 해시 값을 배열의 인덱스로 사용하여 데이터를 저장합니다. 각 배열 요소를 버킷(Bucket)이라고 부릅니다.
버킷은 일반적으로 배열의 요소를 가리키는 포인터로 구성됩니다. 해시 테이블의 크기는 일반적으로 해시 값을 인덱스로 사용할 수 있는 배열의 크기로 결정됩니다. 예를 들어, 크기가 10인 해시 테이블은 10개의 버킷으로 구성된 배열을 의미합니다.
각 버킷은 연결 리스트(Chaining) 또는 개방 주소법(Open Addressing)의 일부로 구성될 수 있습니다. 연결 리스트 방식에서는 각 버킷이 데이터를 저장하는데 사용되며, 동일한 해시 값이 발생하는 경우 해당 버킷에 있는 연결 리스트에 데이터가 추가됩니다. 개방 주소법에서는 버킷 자체가 데이터를 저장하는데 사용되며, 충돌이 발생할 경우 다른 빈 버킷을 찾아 데이터를 저장합니다.
버킷의 역할은 데이터를 해시 값에 따라 저장하고, 데이터의 검색을 위해 해당 버킷을 참조하는 것입니다. 해시 테이블은 버킷을 사용하여 데이터를 저장하고 검색하기 때문에 빠른 검색 속도를 제공할 수 있습니다. 데이터의 해시 값에 따라 적절한 버킷으로 이동하여 데이터를 저장하고 검색함으로써 효율적인 데이터 관리가 가능해집니다.
해시 테이블은 다음과 같은 단계로 동작합니다:
해시 함수 선택: 데이터를 해시 테이블에 저장하기 전에 키를 해시 값으로 변환하는 해시 함수를 선택합니다. 해시 함수는 키의 일부분이나 전체를 입력으로 받아 해시 값으로 변환합니다.
해시 값에 따른 인덱스 계산: 해시 함수를 사용하여 각 키에 대한 해시 값을 계산합니다. 이 해시 값은 일반적으로 정수로 표현되며, 해시 테이블의 인덱스로 사용됩니다. 해시 값의 범위는 해시 테이블의 크기에 따라 결정됩니다.
충돌 해결: 서로 다른 키가 동일한 해시 값을 가질 수 있으므로, 이를 충돌(Collision)이라고 합니다. 충돌이 발생할 경우에는 충돌 해결 방법을 사용하여 충돌을 처리합니다. 대표적인 충돌 해결 방법으로는 개방 주소법(Open Addressing)과 연결 리스트(Chaining) 방식이 있습니다.
개방 주소법(Open Addressing): 충돌이 발생하면 다른 빈 슬롯을 찾아서 해당 슬롯에 데이터를 저장하는 방식입니다. 선형 탐사(Linear Probing), 이차 탐사(Quadratic Probing), 이중 해싱(Double Hashing) 등이 개방 주소법의 일부입니다
연결 리스트(Chaining): 충돌이 발생하면 같은 인덱스에 해당하는 슬롯에 연결 리스트를 이용하여 데이터를 연결하여 저장합니다. 충돌이 발생하면 연결 리스트에 데이터를 추가합니다.
데이터 저장 및 검색: 충돌이 발생하지 않는 경우에는 해시 값을 인덱스로 사용하여 데이터를 저장합니다. 이후에 데이터를 검색할 때에도 동일한 과정을 거쳐 해시 값을 계산하여 해당 인덱스로 접근하여 검색을 수행합니다.
해시 테이블은 일반적으로 데이터 검색에서 매우 빠른 속도를 제공합니다. 해시 함수의 성능에 따라 충돌이 적게 발생하면 더욱 효율적인 검색이 가능합니다. 따라서 좋은 해시 함수 선택과 충돌 해결 방법의 적절한 선택이 해시 테이블의 성능에 중요한 영향을 미칩니다.
자바에서 Set은 중복된 요소를 허용하지 않는 컬렉션 인터페이스입니다. Set은 수학적인 집합 개념을 모델링하며, 고유한 요소들의 모음을 나타냅니다. Set은 다양한 구현체를 가질 수 있으며, 가장 일반적인 구현체로는 HashSet, TreeSet, LinkedHashSet이 있습니다.
HashSet: HashSet은 해시 테이블을 사용하여 요소를 저장하는 Set의 구현체입니다. HashSet은 요소의 순서를 유지하지 않습니다. HashSet은 O(1) 시간 복잡도로 요소를 삽입, 삭제, 검색할 수 있습니다. 그러나 요소의 순서가 중요하지 않거나, 중복 요소를 허용하지 않는 경우에 사용하기 적합합니다.
TreeSet: TreeSet은 이진 검색 트리를 사용하여 요소를 저장하는 Set의 구현체입니다. TreeSet은 요소를 기본적으로 오름차순으로 정렬된 상태로 유지합니다. TreeSet은 O(log n) 시간 복잡도로 요소를 삽입, 삭제, 검색할 수 있습니다. 정렬된 상태로 요소를 유지하고 싶거나, 범위 기반의 검색이 필요한 경우에 사용하기 적합합니다.
LinkedHashSet: LinkedHashSet은 해시 테이블과 연결 리스트를 사용하여 요소를 저장하는 Set의 구현체입니다. LinkedHashSet은 요소를 삽입한 순서대로 유지합니다. LinkedHashSet은 HashSet의 기능에 순서를 추가한 것으로 생각할 수 있습니다. HashSet과 마찬가지로 O(1) 시간 복잡도로 요소를 삽입, 삭제, 검색할 수 있습니다. 요소의 순서를 유지하고 싶으면서도 중복 요소를 허용하지 않는 경우에 사용하기 적합합니다.
package java.util;
public interface Set<E> extends Collection<E> {
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean add(E e);
boolean remove(Object o);
// Bulk Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
// Comparison and hashing
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
@SuppressWarnings("unchecked")
static <E> Set<E> of() {
return (Set<E>) ImmutableCollections.EMPTY_SET;
}
static <E> Set<E> of(E e1) {
return new ImmutableCollections.Set12<>(e1);
}
static <E> Set<E> of(E e1, E e2) {
return new ImmutableCollections.Set12<>(e1, e2);
}
static <E> Set<E> of(E e1, E e2, E e3) {
return new ImmutableCollections.SetN<>(e1, e2, e3);
}
static <E> Set<E> of(E e1, E e2, E e3, E e4) {
return new ImmutableCollections.SetN<>(e1, e2, e3, e4);
}
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5) {
return new ImmutableCollections.SetN<>(e1, e2, e3, e4, e5);
}
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6) {
return new ImmutableCollections.SetN<>(e1, e2, e3, e4, e5,
e6);
}
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7) {
return new ImmutableCollections.SetN<>(e1, e2, e3, e4, e5,
e6, e7);
}
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) {
return new ImmutableCollections.SetN<>(e1, e2, e3, e4, e5,
e6, e7, e8);
}
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) {
return new ImmutableCollections.SetN<>(e1, e2, e3, e4, e5,
e6, e7, e8, e9);
}
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) {
return new ImmutableCollections.SetN<>(e1, e2, e3, e4, e5,
e6, e7, e8, e9, e10);
}
@SafeVarargs
@SuppressWarnings("varargs")
static <E> Set<E> of(E... elements) {
switch (elements.length) { // implicit null check of elements
case 0:
@SuppressWarnings("unchecked")
var set = (Set<E>) ImmutableCollections.EMPTY_SET;
return set;
case 1:
return new ImmutableCollections.Set12<>(elements[0]);
case 2:
return new ImmutableCollections.Set12<>(elements[0], elements[1]);
default:
return new ImmutableCollections.SetN<>(elements);
}
}
@SuppressWarnings("unchecked")
static <E> Set<E> copyOf(Collection<? extends E> coll) {
if (coll instanceof ImmutableCollections.AbstractImmutableSet) {
return (Set<E>)coll;
} else {
return (Set<E>)Set.of(new HashSet<>(coll).toArray());
}
}
}
Set 인터페이스는 Collection 인터페이스를 확장하고 있으며, 다양한 메서드를 제공합니다. 예를 들어, Set에 요소를 추가하려면 add() 메서드를 사용하고, 요소를 삭제하려면 remove() 메서드를 사용합니다. 또한, Set의 크기를 확인하려면 size() 메서드를 사용할 수 있습니다.
Set은 중복된 요소를 허용하지 않으므로, 요소의 동등성(equality)을 판단하기 위해 요소 클래스에 equals()와 hashCode() 메서드를 적절하게 구현해야 합니다. 동등성 판단은 HashSet 및 HashMap과 같은 내부 구조를 사용하는 Set 구현체에서 중요합니다.
Set은 중복된 요소를 제거하고 고유한 요소들로만 구성된 컬렉션을 다루고자 할 때 유용합니다. 예를 들어, 집합 연산을 수행하거나 중복을 제거한 목록을 생성하려는 경우에 Set을 사용할 수 있습니다.
다음은 HashSet을 사용하는 몇 가지 예시 코드입니다.
import java.util.HashSet;
public class StringHashSetExample {
public static void main(String[] args) {
HashSet<String> stringSet = new HashSet<>();
stringSet.add("Apple");
stringSet.add("Banana");
stringSet.add("Orange");
System.out.println("HashSet의 크기: " + stringSet.size());
// 모든 요소 출력
for (String item : stringSet) {
System.out.println(item);
}
// 요소 제거
stringSet.remove("Banana");
System.out.println("HashSet의 크기: " + stringSet.size());
// 요소 존재 여부 확인
System.out.println("Orange가 HashSet에 포함되어 있는가? " + stringSet.contains("Orange"));
}
}
import java.util.HashSet;
public class IntegerHashSetExample {
public static void main(String[] args) {
HashSet<Integer> integerSet = new HashSet<>();
integerSet.add(10);
integerSet.add(20);
integerSet.add(30);
System.out.println("HashSet의 크기: " + integerSet.size());
// 모든 요소 출력
for (int item : integerSet) {
System.out.println(item);
}
// 요소 제거
integerSet.remove(20);
System.out.println("HashSet의 크기: " + integerSet.size());
// 요소 존재 여부 확인
System.out.println("30이 HashSet에 포함되어 있는가? " + integerSet.contains(30));
}
}
import java.util.HashSet;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return 31 * name.hashCode() + age;
}
}
public class CustomClassHashSetExample {
public static void main(String[] args) {
HashSet<Person> personSet = new HashSet<>();
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Bob", 30);
Person person3 = new Person("Alice", 25);
personSet.add(person1);
personSet.add(person2);
personSet.add(person3);
System.out.println("HashSet의 크기: " + personSet.size());
// 모든 요소 출력
for (Person person : personSet) {
System.out.println(person.getName() + ", " + person.getAge());
}
// 요소 제거
personSet.remove(person2);
System.out.println("HashSet의 크기: " + personSet.size());
// 요소 존재 여부 확인
Person person4 = new Person("Alice", 25);
System.out.println("person4가 HashSet에 포함되어 있는가? " + personSet.contains(person4));
}
}
import java.util.HashSet;
import java.util.ArrayList;
public class DuplicateRemovalUsingHashSet {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(10);
numbers.add(40);
numbers.add(20);
System.out.println("중복 요소가 포함된 리스트: " + numbers);
HashSet<Integer> uniqueNumbers = new HashSet<>(numbers);
numbers.clear();
numbers.addAll(uniqueNumbers);
System.out.println("중복 제거된 리스트: " + numbers);
}
}
import java.util.HashSet;
public class SetOperationsExample {
public static void main(String[] args) {
HashSet<Integer> set1 = new HashSet<>();
set1.add(1);
set1.add(2);
set1.add(3);
HashSet<Integer> set2 = new HashSet<>();
set2.add(3);
set2.add(4);
set2.add(5);
// 교집합 계산
HashSet<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("교집합: " + intersection);
// 합집합 계산
HashSet<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("합집합: " + union);
// 차집합 계산
HashSet<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("차집합: " + difference);
}
}
import java.util.HashSet;
import java.util.ArrayList;
public class RemoveDuplicateStrings {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
strings.add("Apple");
strings.add("Banana");
strings.add("Orange");
strings.add("Apple");
strings.add("Grape");
strings.add("Banana");
System.out.println("중복 요소가 포함된 리스트: " + strings);
HashSet<String> uniqueStrings = new HashSet<>(strings);
strings.clear();
strings.addAll(uniqueStrings);
System.out.println("중복 제거된 리스트: " + strings);
}
}
import java.util.HashSet;
public class FindDuplicatesInArray {
public static void main(String[] args) {
int[] numbers = {2, 4, 6, 8, 4, 10, 6, 12, 14, 8};
HashSet<Integer> uniqueNumbers = new HashSet<>();
HashSet<Integer> duplicateNumbers = new HashSet<>();
for (int number : numbers) {
if (!uniqueNumbers.add(number)) {
duplicateNumbers.add(number);
}
}
System.out.println("중복 요소: " + duplicateNumbers);
}
}
import java.util.HashSet;
public class FindCommonElementsInArrays {
public static void main(String[] args) {
String[] array1 = {"Apple", "Banana", "Orange", "Grape"};
String[] array2 = {"Orange", "Grape", "Mango", "Pineapple"};
HashSet<String> set1 = new HashSet<>();
for (String element : array1) {
set1.add(element);
}
HashSet<String> commonElements = new HashSet<>();
for (String element : array2) {
if (set1.contains(element)) {
commonElements.add(element);
}
}
System.out.println("공통 요소: " + commonElements);
}
}
import java.util.HashSet;
public class ExtractUniqueElementsFromArray {
public static void main(String[] args) {
int[] numbers = {2, 4, 6, 8, 4, 10, 6, 12, 14, 8};
HashSet<Integer> uniqueElements = new HashSet<>();
for (int number : numbers) {
uniqueElements.add(number);
}
System.out.println("고유한 요소: " + uniqueElements);
}
}
import java.util.HashSet;
public class ExtractNonDuplicateElementsFromArray {
public static void main(String[] args) {
String[] strings = {"Apple", "Banana", "Orange", "Apple", "Grape", "Banana"};
HashSet<String> uniqueElements = new HashSet<>();
HashSet<String> nonDuplicateElements = new HashSet<>();
for (String str : strings) {
if (!uniqueElements.add(str)) {
nonDuplicateElements.remove(str);
} else {
nonDuplicateElements.add(str);
}
}
System.out.println("중복되지 않는 요소: " + nonDuplicateElements);
}
}
Map 인터페이스는 Java 컬렉션 프레임워크에서 Key-Value pair의 집합을 나타내는 인터페이스입니다. 이 인터페이스는 다양한 맵 구현체에서 사용되며, 키와 값의 관계를 저장하고 조회하는 기능을 제공합니다.
Map 인터페이스의 특징은 다음과 같습니다
키-값 쌍 저장: Map은 키와 값의 쌍을 저장합니다. 키는 중복될 수 없고, 각 키는 유일해야 합니다. 값은 중복 저장될 수 있습니다.
키를 통한 값 조회: 주어진 키를 사용하여 해당 키에 연결된 값을 검색할 수 있습니다. get(key) 메서드를 사용하여 특정 키에 해당하는 값을 가져올 수 있습니다.
키-값 쌍 삭제: remove(key) 메서드를 사용하여 특정 키-값 쌍을 삭제할 수 있습니다.
특정 키의 존재 여부 확인: containsKey(key) 메서드를 사용하여 특정 키가 맵에 존재하는지 여부를 확인할 수 있습니다.
맵의 크기 확인: size() 메서드를 사용하여 맵에 저장된 키-값 쌍의 개수를 확인할 수 있습니다.
반복 작업 지원: keySet(), values(), entrySet() 메서드를 사용하여 맵의 키, 값 또는 키-값 쌍을 가져올 수 있습니다. 이를 통해 반복 작업을 수행하거나 맵의 요소를 순회할 수 있습니다.
맵의 구체적인 구현체: Map 인터페이스의 구체적인 구현체로는 HashMap, TreeMap, LinkedHashMap 등이 있습니다. 각 구현체는 내부적으로 다른 데이터 구조를 사용하여 효율적인 검색 및 삽입 작업을 수행합니다.
Map 인터페이스는 다양한 상황에서 유용하게 사용될 수 있으며, 키-값 데이터를 저장하고 관리하는 기능을 제공하여 데이터 구조의 유연성과 편의성을 높여줍니다.
package java.util;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.io.Serializable;
public interface Map<K, V> {
// Query Operations
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
// Modification Operations
V put(K key, V value);
V remove(Object key);
// Bulk Operations
void putAll(Map<? extends K, ? extends V> m);
void clear();
// Views
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
interface Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K, V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
@SuppressWarnings("unchecked")
public static <K, V> Map.Entry<K, V> copyOf(Map.Entry<? extends K, ? extends V> e) {
Objects.requireNonNull(e);
if (e instanceof KeyValueHolder) {
return (Map.Entry<K, V>) e;
} else {
return Map.entry(e.getKey(), e.getValue());
}
}
}
// Comparison and hashing
boolean equals(Object o);
int hashCode();
// Defaultable methods
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if (newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
@SuppressWarnings("unchecked")
static <K, V> Map<K, V> of() {
return (Map<K,V>) ImmutableCollections.EMPTY_MAP;
}
static <K, V> Map<K, V> of(K k1, V v1) {
return new ImmutableCollections.Map1<>(k1, v1);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
K k6, V v6) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
k6, v6);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
K k6, V v6, K k7, V v7) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
k6, v6, k7, v7);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
K k6, V v6, K k7, V v7, K k8, V v8) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
k6, v6, k7, v7, k8, v8);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
k6, v6, k7, v7, k8, v8, k9, v9);
}
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);
}
@SafeVarargs
@SuppressWarnings("varargs")
static <K, V> Map<K, V> ofEntries(Entry<? extends K, ? extends V>... entries) {
if (entries.length == 0) { // implicit null check of entries array
@SuppressWarnings("unchecked")
var map = (Map<K,V>) ImmutableCollections.EMPTY_MAP;
return map;
} else if (entries.length == 1) {
// implicit null check of the array slot
return new ImmutableCollections.Map1<>(entries[0].getKey(),
entries[0].getValue());
} else {
Object[] kva = new Object[entries.length << 1];
int a = 0;
for (Entry<? extends K, ? extends V> entry : entries) {
// implicit null checks of each array slot
kva[a++] = entry.getKey();
kva[a++] = entry.getValue();
}
return new ImmutableCollections.MapN<>(kva);
}
}
static <K, V> Entry<K, V> entry(K k, V v) {
// KeyValueHolder checks for nulls
return new KeyValueHolder<>(k, v);
}
@SuppressWarnings({"rawtypes","unchecked"})
static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map) {
if (map instanceof ImmutableCollections.AbstractImmutableMap) {
return (Map<K,V>)map;
} else {
return (Map<K,V>)Map.ofEntries(map.entrySet().toArray(new Entry[0]));
}
}
}
Map 인터페이스에서 views란 용어는 맵의 부분 집합을 나타내는 개념입니다. 즉, 맵에서 특정한 관점으로 본 결과를 반환하는 메서드를 의미합니다. 이러한 뷰 메서드는 기존 맵에 대한 참조를 반환하며, 맵의 일부를 변경하면 뷰에도 영향을 미칩니다.
Map 인터페이스에서 제공되는 세 가지 주요 뷰는 다음과 같습니다:
Key Set View (키 집합 뷰): keySet() 메서드를 사용하여 맵의 모든 키를 포함하는 Set을 반환합니다. 이 Set은 맵의 키 집합에 대한 뷰로, 키 집합의 변경이 맵에 반영됩니다.
Value Collection View (값 컬렉션 뷰): values() 메서드를 사용하여 맵의 모든 값들을 포함하는 Collection을 반환합니다. 이 Collection은 맵의 값들에 대한 뷰로, 값 컬렉션의 변경이 맵에 반영됩니다.
Entry Set View (키-값 쌍 집합 뷰): entrySet() 메서드를 사용하여 맵의 키-값 쌍들을 포함하는 Set을 반환합니다. 이 Set은 맵의 키-값 쌍 집합에 대한 뷰로, 키-값 쌍 집합의 변경이 맵에 반영됩니다.
이러한 뷰 메서드를 사용하면 맵에 저장된 데이터에 쉽게 접근하고 조작할 수 있습니다. 예를 들어, keySet() 메서드를 사용하여 맵의 모든 키를 가져와서 반복 작업을 수행하거나, entrySet() 메서드를 사용하여 키-값 쌍을 가져와서 특정 조건에 따라 처리할 수 있습니다. 이러한 뷰 메서드를 활용하면 맵을 더 효율적으로 활용할 수 있습니다.
HashMap을 사용하는 다양한 코드
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성
HashMap<String, Integer> hashMap = new HashMap<>();
// 요소 추가
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// HashMap 출력
System.out.println(hashMap);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 요소 가져오기
int price = hashMap.get("사과");
System.out.println("사과의 가격: " + price);
}
}
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// HashMap 반복문으로 순회
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
System.out.println(key + "의 가격: " + value);
}
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 요소 삭제
hashMap.remove("바나나");
// HashMap 출력
System.out.println(hashMap);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 크기 확인
int size = hashMap.size();
System.out.println("HashMap의 크기: " + size);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 중복된 키로 요소 추가
hashMap.put("사과", 1200);
// HashMap 출력
System.out.println(hashMap);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 특정 키가 존재하는지 확인
boolean containsKey = hashMap.containsKey("사과");
System.out.println("사과가 HashMap에 존재하는가? " + containsKey);
}
}
import java.util.HashMap;
import java.util.Collection;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 모든 값 가져오기
Collection<Integer> values = hashMap.values();
System.out.println("HashMap의 값들: " + values);
}
}
import java.util.HashMap;
import java.util.Set;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 모든 키 가져오기
Set<String> keys = hashMap.keySet();
System.out.println("HashMap의 키들: " + keys);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 모든 요소 제거
hashMap.clear();
// HashMap 출력
System.out.println(hashMap);
}
}
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 특정 값이 포함된 요소 찾기
int searchValue = 1500;
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
if (entry.getValue() == searchValue) {
System.out.println("찾은 요소: " + entry.getKey());
break;
}
}
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
// 기본값 설정하여 값 가져오기
int defaultValue = 0;
int price = hashMap.getOrDefault("오렌지", defaultValue);
System.out.println("오렌지의 가격: " + price);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// 첫 번째 HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap1 = new HashMap<>();
hashMap1.put("사과", 1500);
hashMap1.put("바나나", 2000);
hashMap1.put("오렌지", 1000);
// 두 번째 HashMap 생성 및 첫 번째 HashMap의 요소 복사
HashMap<String, Integer> hashMap2 = new HashMap<>(hashMap1);
// 두 번째 HashMap 출력
System.out.println("복사된 HashMap: " + hashMap2);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// 특정 키에 대한 값 업데이트
hashMap.put("사과", 1200);
// HashMap 출력
System.out.println(hashMap);
}
}
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성 및 요소 추가
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("사과", 1500);
hashMap.put("바나나", 2000);
hashMap.put("오렌지", 1000);
// HashMap의 요소를 배열로 변환
Map.Entry<String, Integer>[] entries = hashMap.entrySet().toArray(new Map.Entry[0]);
// 배열 출력
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
이 코드들은 HashMap을 사용하여 요소 복사, 값 업데이트, 요소를 배열로 변환하는 방법을 보여줍니다.