面试题:总结Iterator,Collection,Set,Map和他们之间的关系

发布时间:2024年01月16日
Collection Map 可谓构成 Java 容器的两大体系,你熟知的数据结构。 ArrayList LinkedList
HashSet HashMap TreeSet TreeMap PriorityQueue Stack 都从 Collection Map 实现而
来。
容器(Collection)是什么?

容器( Collection )是容纳数据用的。 Java 的容器 (Collection) 可以装一组对象。既然是一组对
象,那么他们就应该可以被遍历 (traverse)
可以被遍历的数据是可以被迭代的 (Iterable) 。可以被迭代的数据,就可以使用 for 循环进行迭代。

实现Iterable<T>接口

可以被迭代的数据需要实现 Iterable 接口,而 Iterable 内部需要实现一个迭代器。下面这段程序,
教你实现一个产生随机字符串的迭代器。
public class RandomStringGenerator<T> implements Iterable<T> {
 private final List<T> list;
 public RandomStringGenerator(List<T> list) {
   this.list = list;
 }
 @Override
 public Iterator<T> iterator() {
 return new Iterator<T>() {
 @Override
 public boolean hasNext() {
   return true;
 }
 @Override
 public T next() {
 return list.get((int) (list.size() * Math.random()));
    }
  };
 }
 public static void main(String[] argv) {
   var list = Arrays.asList("List", "Tree", "Array");
   var gen = new RandomStringGenerator<String>(list);
   for(var s: gen) {
   System.out.println(s);
   }
 
 }
}

容器(Collection)接口

容器都是可以被迭代的。 ArrayList LinkedList TreeSet HashSet PriorityQueue Stack 都是
容器, 都可以被迭代,都可以使用 for 循环,直接遍历。
判断一个容器是不是空的 isEmpty() 方法
想知道容器的大小 size() 方法
想知道容器中有没有某个元素 contains(object)
将容器转化成数组 toArray() 方法
添加元素到容器 add(E e) 方法
从容器中移除一个元素 remove(object) 方法
判断一个容器的元素是否在这个容器当中 containsAll(Collection<? extends T> c)
从容器中移除一个容器的元素 removeAll(Collection<?> c)
移除不在某个容器中的元素 retainAll(Collection<?> c)
清空这个容器 clear()
以上这些函数是实现容器必须实现的。
很多实现容器的数据结构。并不是直接实现 Collection<E> ,而是再抽象出中间的接口。比如
ArrayList 的继承如下:
public class ArrayList<E> extends AbstractList<E>
 implements List<E>, RandomAccess, Cloneable, java.io.Serializ
 
 
public interface List<E> extends Collection<E>
上面代码中, ArrayList<E> 先继承于 List<E> 再继承于 Collection<E>

集合(Set)

集合 (Set<E>) 是一种容器 (Collection<E>) 。这种容器。不仅仅可以用来存一组数据。而且他可以
保证这一组数据没有重复。
ConcurrentSkipListSet, CopyOnWriteArraySet, EnumSet, HashSet, LinkedHashSet, TreeSet
ImmutableCollections.SetN 这些类。都是集合。只不过他们用来容纳数据。并且保证数据不重复
的手段不一样。
实现集合最重要的是给定一个元素,集合能够快速的判断这个元素是否在集合当中。所以你看到
上面的所有的类并没有通过数组或者链表。来实现集合的方法。因为在数组和链表当中,查找一
个元素。需要遍历整个数组和链表。这样太慢了。
所以集合可能的 3 类实现是树、跳表、散列表和位运算。
这里我先简单的说一下这三种结构。树和跳表通常是二分查找结构(实现集合通常是平衡的二叉
搜索树,比如红黑树);哈希表是一个映射结构。
ConcurrentSkipListSet 跳表来实现集合。 HashSet LinkedHashSet ImmutableCollections.SetN
是用散列实现集合。 TreeSet 用树实现集合。 EnumSet 是一个抽象类和工厂,实际是
RegularEnumSet ,里面在用位运算实现集合。
集合 (Set) 复用容器( Collection )的接口即可,只不过所有的函数都要控制一下元素的唯一性。
映射(Map

映射 (Map) 是两个集合间的对应关系。在 Java 中的 Map 将键 (Key) 映射到值 (Value) Map Java
是一个接口,具体的实现有很多,比如 HashMap TreeMap ,Hashtable SortedMap 等。
为了实现映射,我们需要容器存储 Key ,需要容器存储 value ,也需要容器存储 key-value 。一组
key-value Java 中我们称之为一个条目 (Entry)
Map是不是Entry的容器?

Map 最核心的功能,是根据 Key 查找值。因此一个 Map 中。不能拥有重复的 Key
当然从某种角度来看,我们可以把 Map 看做存储条目的容器。接口 Map.Entry<K,V> 表示 Java
Map 的条目。每一个 Map 内部需要实现自己的 Entry
但是这样看是片面的。因为 Map 的核心能力,不是提供容器而是提供快速的数据查找能力。那本
质是映射,根据不同的 Map 实现,可能会把 Entry 存储到某个容器当中。也可能将 Key Value
储到两个不同的容器当中。
每个 Map 要求实现 Set<Map.Entry<K, V>> entrySet() 接口,可见 Entry Map 中是以集合的形式
存在(不能重复)。但是我们同样不能说 Map 是存储 Entry 的集合 (Set) ,这是因为, Map 并没有要
求一定要将 Entry 存储下来,可以在 entrySet 中动态计算 Entry 的集合。
所以 Map 不是集合,也不是容器,它是映射。 Map 中需要容器,需要数据结构,但是具体如何去
存储、如何去查询 Map 并没有约束。
Map.Entry<K,V>接口

每一种 Map 都必须实现自己的 Map.Entry<K, V> 类型。
下面是, Hashtable 中的一段程序, Hashtable 通过内部类实现了自己的 Map.Entry<K, V>
private static class Entry<K,V> implements Map.Entry<K,V> {
 final int hash;
 final K key;
 V value;
 Entry<K,V> next;
 protected Entry(int hash, K key, V value, Entry<K,V> next) {
 this.hash = hash;
 this.key = key;
 this.value = value;
 this.next = next;
 }
 @SuppressWarnings("unchecked")
 protected Object clone() {
 return new Entry<>(hash, key, value,
 (next==null ? null : (Entry<K,V>) next.clone()));
 }
 
 public K getKey() {
 return key;
 }
 public V getValue() {
 return value;
 }
 public V setValue(V value) {
 if (value == null)
 throw new NullPointerException();
 V oldValue = this.value;
 this.value = value;
 return oldValue;
 }
 public boolean equals(Object o) {
 if (!(o instanceof Map.Entry))
 return false;
 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
 return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
 (value==null ? e.getValue()==null : value.equals(e.getValue()));
 }
Map<K,V>的接口

Map 的接口中 K 代表 Key 的类型, V 代表值的类型。
Map 和容器非常的像,需要实现一些集合中也存在的接口函数。比如说:
int size()
boolean isEmpty()
void clear()
另外, Map 中提供了三种提取容器的方法:
Collection<V> values() 将值提取为容器。
Set<K> keySet() Key 提取为集合。
Set<Map.Entry<K, V>> entrySet() Entry 提取为集合。
上面三种方法体现的是映射的三要素。 KeySet 是原集合, values 是目标集合 ,Entry 是关联关系。
最后我们来看一下 Map 几个最重要的用途。
boolean containsKey(Object key) 查找 map 中有没有某个键。
boolean containsValue(Object value) 查找 map 中有没有某个 value
V get(Object key) 根据某一个键拿到 value
V put(K key, V value) 写入一组 Key value 的映射关系。
V remove(Object key) 相当于 get 和删除的结合体,拿到值删除关系。
还有批量操作 :
void putAll(Map<? extends K, ? extends V> m) 这里是批量添加。
Map的实现

Map 的诸多实现中有: ConcurrentHashMap, ConcurrentSkipListMap, EnumMap, HashMap,
Hashtable, LinkedHashMap, Properties, TreeMap,
和集合类似 Map 最常的种实现如下:
ConcurrentHashMap HashMap Hashtable LinkedHashMap 是基于哈希表的实现
  • TreeMap是基于树的实现
  • ConcurrentSkipListMap是基于跳表的实现
  • EnumMap是基于位运算的实现
和集合类似,哈希表的实现是一种稀疏的结构。树的实现是一种紧密的结构。树的实现中间继承
路径中会实现 NavigableMap<K,V> ,从而实现浏览的能力。
HashMap vs Hashtable

上面诸多实现当中 HashMap Hashtable 实现非常相近。这里有 2 个显著的区别:
Hashtable 中的所有对用户的方法都用 synchronized 关键字上了锁(因此线程安全)。如果
你学到了本课程后续的并发编程环节。你会知道这并不是一种好的实现方案。 HashMap
有做任何控制。
  • HashMap允许null key/null value
  • Hashtable不允许null key/null value
总结

Java 的数据结构。但是归根结底,其实只有两类。一类就是存储数据的容器 (Collection<T>)
一类就是将数据映射到另一种数据的 Map<K, V> Map 中依然要用到容器,用 到Iterable 。但 Map 的本质不是容器,而是映射。
这些数据结构。我觉得大家可以从两方面去掌握。一方面就是什么东西是什么。比如说 HashMap
Hash 实现的 Map TreeSet Tree 实现的 Set。
另一个需要掌握的维度就是数据结构和算法本身。 TreeSet 是二叉搜索树中的红黑树, HashMap
是哈希表, ConcurrentSkipListMap 是跳表, EnumSet是位运算。
文章来源:https://blog.csdn.net/lichongxyz/article/details/135620850
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。