Set集合
- Set集合中,没有下标的概念。
- Set集合,是⼀个去重复的集合。 在Set集合中不会添加重复的元素的!
在向⼀个Set集合中添加元素的时候, 会先判断这个元素是否已经存在了。如果存在, 则不再添加。
- Set集合中, 数据的存储是⽆序的。
⽆序: 所谓的⽆序, 其实指的是元素的添加顺序和存储顺序是不⼀致的。
⽆序, 并不意味着随机!
Set接⼝, 是继承⾃Collection接⼝的。 Set接⼝中的⽅法, 都是从Collection接⼝中
继承下来的, 并没有添加新的⽅法。
Map集合
Map是双列集合的顶级接⼝, 这个接⼝并没有继承⾃Collection接⼝。在Map中, 更多强调的是⼀层映射关系。 在Map中存储的数据, 是⼀个个的键值对(Key-Value-Pair), 键和值是⼀⼀对应的。
需要注意:
由于Map集合并没有实现Iterable接⼝, 因此这个集合是不能使⽤增强for循环遍历的。
Set集合
HashSet与TreeSet的区别
- HashSet:底层是哈希表,线程不安全的
- TreeSet:底层是⼆叉树,线程不安全的
Set集合的两个实现类HashSet与LinkedHashSet,底层实现都是哈希表。
- Hash,⼀般翻译做“散列”,也有直接⾳译为“哈希”的,它是基于快速存取的⻆度设计的,也是⼀种典型的“空间换时间”的做法。顾名思义,该数据结构可以理解为⼀个线性表,但是其中的元素不是紧密排列的,⽽是可能存在空隙。
- 散列表(Hash table,也叫哈希表),是根据键值码值(Key value)⽽直接进⾏访问的数据结构。也就是说,它通过把键值码值映射到表中⼀个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数。
- Hash表的组成是”数组+链表”这些元素是按照什么样的规则存储到数组中呢。⼀般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组⻓度取模得到。
⽐如下图哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以
12、28、108以及140都存储在数组下标为12的位置
- HashSet & LinkedHashSet
解释:是通过调⽤元素的hashCode和equals⽅法实现去重,⾸先调⽤hashCode⽅法,拿到当前对象的哈希码值,去让两个对象的哈希码值进⾏⽐较,如果不同,直接认为是两个对象,不再去调⽤equals,如相同,再继续调⽤equals⽅法,返回true认为是⼀个对象,返回false认为是两个对象
TreeSet
TreeSet是⼀个Set接⼝的实现类,底层实现是⼆叉树。这样的集合,会对添加进集合的元素进⾏去重的处理。 同时, 这个集合会对添加进⼊的元素进⾏⾃动的升序排序。
Comparable接⼝
如果某⼀个类实现这个接⼝, 表示⾃⼰实现了⼀个可以和⾃⼰的对象进⾏⼤⼩⽐较的规则。 此时, 这个类的对象就可以直接存储进TreeSet集合中了。 因为此时TreeSet集合已经知道了怎么对两个这个类的对象进⾏⼤⼩⽐较。
Comparator接⼝(⼈⼯排序)
定义:使⽤实现了Comparator接⼝的compare()⽅法的⽐较器对象进⾏⽐较
分析1:有了Comparable,为什么还要有comparator?
- 对于⾃定义的类,代码是我们⾃⼰编写的,所有在排序时不管是通过Comparator还是Comparable,排序规则我们都可以⾃⼰制定,所以最终使⽤那种⽅法没有太⼤的区别
- 对于系统类,影响⾮常⼤.系统类中的代码我们只能⽤,不能改.这也就意味着系统类内部通过Comparable实现的⽐较规则已经确定了.这时我们想使⽤其他的规则对当前的系统类对象进⾏⽐较,只能使⽤Comparator⾃⼰重新制定⽐较规则.
**分析2:**⼈⼯排序和默认排序那个优先级⾼?
⼈⼯排序的优先级⾼于默认排序.
我们可以让TreeSet同时获取到Comparator和Comparable的⽐较⽅法,此时对于系统类来说默认排序是系统⾃带的,通过Comparator实现的⼈⼯排序规则是我们想要的,所以系统必须让⼈⼯排序优先于默认排序,才能正常的使⽤后加的排序规则.
Comparable与Comparator的使⽤场景
- 如果这个对象, 在项⽬中⼤多数的情况下, 都采⽤相同的⼤⼩⽐较的⽅式。 ⽐如: ⼀个Person类, 在⼤多数情况下, 都是按照年龄进⾏⼤⼩⽐较的。 此时就可以让Person类实现Comparable接⼝。
- 如果某⼀个类的对象, 在临时进⾏⼤⼩⽐较的时候, 使⽤的与默认的⽐较不⼀样的规则。 ⽐如: ⼀个Person类, ⼤多数情况下, 都是使⽤的年龄进⾏⼤⼩⽐较的, 但是临时需要使⽤身⾼进⾏⼀次⽐较, 此时就可以使⽤ Comparator临时完成了。 ⽽且, Comparator的优先级要⾼于Comparable。
- 系统类想实现新的⽐较规则,使⽤Comparator
TreeSet的去重
⽆论使⽤Comparator还是Comparable,如果两个对象进⾏⼤⼩⽐较的结果是0,此时代表这两个对象是相同的对象。 在TreeSet中会完成排重的处理。
注意: TreeSet中元素的去重只与对象的⼤⼩⽐较结果有关。 与hashCode()、equals(), 没有任何关系。
Map集合
返回值 | ⽅法 | 描述 |
---|---|---|
V | put(K key, V value) | 将⼀个键值对插⼊到集合中。 在Map集合中,不允许出现重复的键。如果添加的键重复了,会⽤新的值覆盖掉原来的值。并返回被覆盖的原来的值。 |
V | putIfAbsent(K key, Vvalue) | 将⼀个键值对插⼊到集合中。 向集合中添加元素的时候,如果这个键已经存在了,则不进⾏添加。 返回集合中已经存在的这个键对应的值。 |
void | putAll(Map<K, V>map) | 将⼀个Map集合中所有的键值对添加到当前集合中。 |
boolean | remove(Object key,Object value) | 通过键值对进⾏删除。 只有当键和值⼀⼀匹配的时候, 才会进⾏删除。 |
void | clear() | 清空集合。 |
V | replace(K key, V value) | 修改指定的键对应的值, 并返回被覆盖的值。 |
boolean | replace(K key, V oldValue, V newValue) | 只有当key和oldValue是匹配的情况下,才会将值修改成newValue。 |
void | replaceAll(BiFunction<K,V, V> biFunction) | 对集合中的元素进⾏批量的替换 将集合中的每⼀个键值对,带⼊到BiFunction的⽅法中, 使⽤接⼝⽅法的返回值替换集合中原来的值。 |
V | get(K key) | 通过键, 获取值。 如果键不存在,返回null。 |
V | getOrDefault(K key) | 通过键, 获取值。 如果键不存在,返回默认的值。 |
int | size() | 获取集合中的元素数量(有多少个键值对) |
boolean | isEmpty() | 判断集合是否为空 |
boolean | containsKey(K key) | 判断是否包含指定的键 |
boolean | containsValue(V value) | 判断是否包含指定的值 |
Set | keySet() | 获取由所有的键组成的集合(因为键是不允许重复的, 因此这⾥返回的是Set集合) |
Collection | values() | 获取由所有的值组成的集合 |
Map集合的遍历
使⽤keySet进⾏遍历
- 可以使⽤keySet()⽅法获取到集合中所有的键。
- 遍历存储了所有的键的集合,依次通过键获取值。
// 1. 获取存储了所有的键的集合
Set<String> keys = map.keySet();
// 2. 遍历这个Set集合
for (String key : keys) {
// 2.1. 通过键获取值
String value = map.get(key);
// 2.2. 展示⼀下键和值
System.out.println("key = " + key + ", value = " +value);
}
使⽤forEach⽅法
这个forEach⽅法, 并不是Iterable接⼝中的⽅法。 是Map接⼝中定义的⼀个⽅法。
从功能上将, 与Iterable中的⽅法差不多。 只是在参数部分有区别。
default void forEach(BiConsumer<? super K, ? super V> action)
map.forEach((k, v) -> {
// k: 遍历到的每⼀个键
// v: 遍历到的每⼀个值
System.out.println("key = " + k + ", value = " + v);
});
使⽤EntrySet进⾏遍历
Entry<K, V>:
是Map中的内部静态接⼝, ⼀个Entry对象我们称为⼀个实体,⽤来描述集合中的每⼀个键值对。
// 1. 获取⼀个存储有所有的Entry的⼀个Set集合
Set<Map.Entry<String, String>> entries = map.entrySet();
// 2. 遍历Set集合
for (Map.Entry<String, String> entry : entries) {
// 2.1. 获取键
String key = entry.getKey();
// 2.2. 获取值
String value = entry.getValue();
// 2.3. 展示
System.out.println("key = " + key + ", value = " + value);
}
//通过setValue可以去修改原始map的值
//映射项(键-值对)。Map.entrySet ⽅法返回映射的 collection视图,其中的元素属于此类。
//获得映射项引⽤的唯⼀ ⽅法是通过此 collection 视图的迭代器来实现。这些 Map.Entry 对象仅
//在迭代期间有效;更正式地说,如果在迭代器返回项之后修改了底层映射,则
//某些映射项的⾏为是不确定的,除了通过 setValue 在映射项上执⾏操作之外。
//entry.setValue("hello");
HashMap
HashMap基本实现
注意:HashMap可以实现排序:因为他的底层数据结构是由数组+链表+⼆叉树共同实现的.所以可以排序.同时这样做的⽬的是提⾼数据存储的效率.
HashMap与Hashtable的区别
- HashMap是线程不安全的集合, Hashtable是线程安全的集合。
- HashMap允许出现null键值, Hashtable是不允许的。
- HashMap的⽗类是AbstractMap, Hashtable的⽗类是Dictionary。
- HashMap的Map接⼝的新的实现类, 底层算法效率优于Hashtable。
TreeMap
原理
与TreeSet⼀样,进⾏排列,只是TreeMap是按照键的⼤⼩实现,对于值是不管的.我们可以将TreeSet中的值理解成TreeMap中的键.
注意点
-
什么类型的数据类型可以作为key?
实现了Comparable接⼝的compareTo()⽅法
实现了Comparator接⼝的compare()⽅法 -
经常作为key的有:
String,包装类,⾃定义的实现了要求的类 -
不可以的代表:数组,ArrayList,LinkedList(如果给他们建⽴的⽐较器也可以⽐较,但是不建议使⽤)
-
元素可不可以作为key,跟元素内部的成员有没有关系
元素可以作为key,跟元素内部的成员没有关系。
LinkedHashMap
- 与HashMap类似的,底层多维护了⼀个链表, 记录每⼀个键的存储顺序。也就是说, 在LinkedHashMap中, 键值对的添加顺序可以得到保障。 类似于LinkedHashSet与HashSet。