1、Set接口
Set接口:也称Set集合,但凡是实现了Set接口的类都叫做Set集合
特点: 元素无索引,元素不可重复(唯一) (以下三种集合必须满足)
HashSet集合: 实现类--元素存取无序
LinkedHashSet集合:实现类--元素存取有序
TreeSet集合:实现类--> 对元素进行排序
注意:
1.Set集合没有特殊的方法,都是使用Collection接口的方法
2.Set集合没有索引,所以遍历元素的方式就只有: 增强for循环,或者迭代器
1.1 HashSet集合
java.util.HashSet
是Set
接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不能保证不一致)。
public class Test {
public static void main(String[] args) {
/*
HashSet集合: 元素存取无序,元素不可重复,元素无索引
*/
// 创建HashSet集合对象,限制集合元素的类型为String
HashSet<String> set = new HashSet<>();
// 往集合中添加元素
set.add("nba");
set.add("cba");
set.add("bac");
set.add("abc");
set.add("nba");
System.out.println(set);// [cba, abc, bac, nba]
}
}
HashSet集合存储数据的结构(哈希表)
哈希表底层结构
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用数组处理冲突,同一hash值的链表都存储在一个数组索引里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
HashSet保证元素唯一原理
-HashSet集合存储数据的结构---哈希表结构
哈希表结构:
jdk8以前: 数组+链表
jdk8以后: 数组+链表+红黑树
链表元素个数没有超过8: 数组+链表
链表元素个数超过8: 数组+链表+红黑树
-HashSet集合保证元素唯一的原理--依赖hashCode()和equals()方法
1.当存储元素的时候,就会调用该元素的hashCode()方法计算该元素的哈希值
2.判断该哈希值对应的位置上,是否有元素:
3.如果该哈希值对应的位置上,没有元素,就直接存储
4.如果该哈希值对应的位置上,有元素,说明产生了哈希冲突
5.产生了哈希冲突,就得调用该元素的equals方法,与该位置上的所有元素进行一一比较:
如果比较的时候,有任意一个元素与该元素相同,那么就不存储
如果比较完了,没有一个元素与该元素相同,那么就直接存储
补充:
Object类: hashCode()和equals()方法;
hashCode():Object类中的hashCode()方法主要是根据地址值计算哈希值
equals方法():Object类中的equals()方法是比较地址值
HashSet存储自定义类型元素
给HashSet中存放自定义类型元素时,**需要重写对象中的hashCode和equals方法,**建立自己的比较方式,才能保证HashSet集合中的对象唯一.
public class Person{
/**
* 姓名
*/
public String name;
/**
* 年龄
*/
public int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
测试类:
public class Demo {
public static void main(String[] args) {
// 创建多个Person对象
Person p1 = new Person("张三", 18);
Person p2 = new Person("李四", 38);
Person p3 = new Person("王五", 28);
Person p4 = new Person("张三", 18);
// 创建HashSet集合对象,限制集合中元素的类型为Person
HashSet<Person> set = new HashSet<>();
// 往集合中添加Person对象
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
// 遍历打印集合中的元素
for (Person p : set) {
System.out.println(p);
}
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
System.out.println(p3.hashCode());
System.out.println(p4.hashCode());
}
}
1.1.1 Hashtable
Hashtable概述
java.util.Hashtable<k,v>
Hashtable底层是一个哈希表,它是一个线程安全的集合,单线程集合,速度慢,Hashtable集合不能存储null值,null键。
HashMap集合底层是一个哈希表,是线程不安全集合,是一个多线程集合,不过它的速度很快,可以存储null值,null键。
不过Hashtable集合逐渐被HashMap集合取代,但是Hashtable的子类Properties依然沿用,Properties集合也是唯一一个和IO流相结合的集合。
1.2 LinkedHashSet
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。
public class Test {
public static void main(String[] args) {
/*
LinkedHashSet集合: 元素存取有序,元素无索引,元素不可重复(唯一)
采用哈希表+链表结构,由哈希表保证元素唯一,由链表保证元素存取有序
*/
// 创建LinkedHashSet集合,限制集合中元素的类型为Integer类型
LinkedHashSet<Integer> set = new LinkedHashSet<>();// 存取有序
//HashSet<Integer> set = new HashSet<>();// 存取无序
// 往集合中存储数据
set.add(300);
set.add(100);
set.add(200);
set.add(500);
set.add(400);
set.add(400);
System.out.println(set);// [300, 100, 200, 500, 400]
//打印集合变量时,依次调用元素的toString方法
}
}
1.3 TreeSet集合
TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树的实现,其特点为:
- 元素唯一
- 元素没有索引
- 使用元素的自然顺序对元素进行排序,或者根据创建 TreeSet 时提供的
Comparator
比较器
进行排序,具体取决于使用的构造方法:
public TreeSet(): 根据其元素的自然排序进行排序(升序)
public TreeSet(Comparator<E> comparator): 根据指定的比较器进行排序
public class Test {
public static void main(String[] args) {
/*
TreeSet集合: 元素无索引,元素唯一,对元素进行排序
通过构造方法实现排序:
public TreeSet(): 根据其元素的自然排序进行排序
默认规则排序:集合元素所属的类需要实现Comparable接口,重写compareTo方法,在compareTo方法中指定默认排序规则
public TreeSet(Comparator<E> comparator): 根据指定的比较器进行排序
指定规则排序: 通过传入Comparator接口的实现类对象,在实现类对象中重写compare方法,在compare方法中指定排序规则
*/
// 按照默认规则排序---->默认升序
// 创建TreeSet集合,限制集合中元素的类型为Integer类型
TreeSet<Integer> set = new TreeSet<>();
// 往集合中存储数据
set.add(300);
set.add(100);
set.add(200);
set.add(500);
set.add(400);
set.add(400);
System.out.println(set);// [100, 200, 300, 400, 500]
System.out.println("===========================================");
// 按照指定规则排序---->降序
// 创建TreeSet集合,限制集合中元素的类型为Integer类型
TreeSet<Integer> set1 = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
/*
指定排序规则:
前减后 升序
后减前 降序
前:第一个参数 后:第二个参数
*/
return o2 - o1;
}
});
// 往集合中存储数据
set1.add(300);
set1.add(100);
set1.add(200);
set1.add(500);
set1.add(400);
set1.add(400);
System.out.println(set1);// [500, 400, 300, 200, 100]
System.out.println("===========================================");
// 按照指定规则排序---->升序
// 创建TreeSet集合,限制集合中元素的类型为Integer类型
TreeSet<Integer> set2 = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
/*
指定排序规则:
前减后 升序
后减前 降序
前:第一个参数 后:第二个参数
*/
return o1 - o2;
}
});
// 往集合中存储数据
set2.add(300);
set2.add(100);
set2.add(200);
set2.add(500);
set2.add(400);
set2.add(400);
System.out.println(set2);// [100, 200, 300, 400, 500]
}
}
2、Map集合
Map<K,V>集合的特点: K用来限制键的类型,V用来限制值的类型
1.Map集合存储元素是以键值对的形式存储,每一个键值对都有键和值
2.Map集合的键是唯一,值可以重复,如果键重复了,那么值就会被覆盖
3.根据键取值
Map集合子类:
- HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。
由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
- LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。
通过链表结构可以保证键值对的存取顺序一致;
通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
- TreeMap<K,V>:TreeMap集合和Map相比没有特有的功能,底层的数据结构是红黑树;
可以对元素的键进行排序,排序方式有两种:自然排序和比较器排序
Map 综述(二):彻头彻尾理解 LinkedHashMap
本质上,HashMap和双向链表合二为一即是LinkedHashMap。所谓LinkedHashMap,其落脚点在HashMap,因此更准确地说,它是一个将所有Entry节点链入一个双向链表的HashMap。在LinkedHashMapMap中,所有put进来的Entry都保存在一个的哈希表中,但由于它又额外定义了一个以head为头结点的双向链表,因此对于每次put进来Entry,除了将其保存到哈希表中对应的位置上之外,还会将其插入到双向链表的尾部。
更直观地,下图很好地还原了LinkedHashMap的原貌:HashMap和双向链表的密切配合和分工合作造就了LinkedHashMap。特别需要注意的是,next用于维护HashMap各个桶中的Entry链,before、after用于维护LinkedHashMap的双向链表,虽然它们的作用对象都是Entry,但是各自分离,是两码事儿。
Map的常用方法
Map接口中定义了很多方法,常用的如下:
-
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。 -
public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。 -
public V get(Object key)
根据指定的键,在Map集合中获取对应的值。
-
boolean containsKey(Object key)
:判断该集合中是否有此键 -
boolean containsValue(Object value)
:如果该Map集合将一个或多个键映射到指定的值,则返回 true
-
public Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。 -
public Collection<V> values()
: 获取Map集合中所有的值,存储到Collection集合中。 -
public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
Map.Entry<K,V>:表示键值对对象—把键值对包装成一个对象,该对象的类型就是Entry类型
tips:
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
Map的遍历
方式1:键找值方式
通过元素中的键,获取键所对应的值
分析步骤:
- 获取Map中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键。方法提示:
keyset()
- 遍历键的Set集合,得到每一个键。
- 根据键,获取键所对应的值。方法提示:
get(K key)
方式2:键值对方式
Entry<K,V>接口:简称Entry项,表示键值对对象,用来封装Map集合中的键值对
Entry<K,V>接口:是Map接口中的内部接口,在外部使用的时候是这样表示: Map.Entry<K,V>
Map集合中提供了一个方法来获取所有键值对对象:
public Set<Map.Entry<K,V>> entrySet()
根据键值对对对象获取键和值:
- public K getKey():获取Entry对象中的键。
- public V getValue():获取Entry对象中的值。
Map遍历方式二:根据键值对对象的方式
1.获取集合中所有键值对对象,以Set集合形式返回。 Set<Map.Entry<K,V>> entrySet()
2.遍历所有键值对对象的集合,得到每一个键值对(Entry)对象。
3.在循环中,可以使用键值对对对象获取键和值 getKey()和getValue()
HashMap存储自定义类型
- 当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法。
- 如果要保证map中存放的key和取出的顺序一致,可以使用
java.util.LinkedHashMap
集合来存放。
LinkedHashMap介绍
HashMap保证成对元素唯一,并且查询速度很快,可是成对元素存放进去是没有顺序的,那么我们要保证有序,还要速度快怎么办呢?
使用LinkedHashMap
- 通过链表结构可以保证元素的存取顺序一致;
- 通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
TreeMap介绍
TreeMap集合和Map相比没有特有的功能,底层的数据结构是红黑树;可以对元素的**键进行排序,排序方式有两种:自然排序和比较器排序;到时使用的是哪种排序,取决于我们在创建对象的时候所使用的构造方法;
构造方法
public TreeMap() 使用自然排序
public TreeMap(Comparator<? super K> comparator) 通过比较器指定规则排序
public class Test {
public static void main(String[] args) {
/*
TreeMap集合: 键唯一,值可以重复,如果键重复了,值就覆盖,可以根据键对键值对进行排序
public TreeMap() 根据键按照默认规则进行排序
public TreeMap(Comparator<? super K> comparator) 通过比较器指定规则排序
*/
// 按照键的默认规则排序: ---->升序
// 创建TreeMap集合,限制键的类型为Integer,值的类型为String
TreeMap<Integer, String> map = new TreeMap<>();
// 往map集合中添加键值对
map.put(300, "深圳");
map.put(100, "北京");
map.put(200, "广州");
map.put(500, "上海");
map.put(400, "武汉");
map.put(400, "深圳");
System.out.println(map);
System.out.println("+=================================");
// 按照指定规则排序: ---->降序
// 创建TreeMap集合,限制键的类型为Integer,值的类型为String
TreeMap<Integer, String> map1 = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
/*
前减后: 升序
后减前: 降序
前:第一个参数 后:第二个参数
*/
return o2 - o1;
}
});
// 往map集合中添加键值对
map1.put(300, "深圳");
map1.put(100, "北京");
map1.put(200, "广州");
map1.put(500, "上海");
map1.put(400, "武汉");
map1.put(400, "深圳");
System.out.println(map1);
}
}
3、集合的嵌套
总述:任何集合内部都可以存储其它任何集合
List嵌套List
public class Test1 {
public static void main(String[] args) {
/*
集合的嵌套:
- List嵌套List
- List嵌套Map
- Map嵌套Map
结论:任何集合内部都可以存储其它任何集合
*/
// List嵌套List
// 创建一个List集合,限制元素类型为String
List<String> list1 = new ArrayList<>();
// 往集合中添加元素
list1.add("AAA");
list1.add("BBB");
list1.add("CCC");
// 创建一个List集合,限制元素类型为String
List<String> list2 = new ArrayList<>();
// 往集合中添加元素
list2.add("DDD");
list2.add("EEE");
list2.add("FFF");
// 创建一个List集合,限制元素类型为List集合 (List集合中的元素是List集合)
List<List<String>> list = new ArrayList<>();
list.add(list1);
list.add(list2);
// 遍历
for (List<String> e : list) {
for (String name : e) {
System.out.println(name);
}
System.out.println("=============");
}
System.out.println(list);
}
}
List嵌套Map
public class Test2 {
public static void main(String[] args) {
/*
List嵌套Map:
*/
// 创建Map集合对象
Map<String,String> map1 = new HashMap<>();
map1.put("it001","AAA");
map1.put("it002","BBB");
// 创建Map集合对象
Map<String,String> map2 = new HashMap<>();
map2.put("h001","CCC");
map2.put("h002","DDD");
// 创建List集合,用来存储以上2个map集合
List<Map<String,String>> list = new ArrayList<>();
list.add(map1);
list.add(map2);
System.out.println(list.size()); // 2
for (Map<String, String> map : list) {
// 遍历获取出来的map集合对象
Set<String> keys = map.keySet();// 获取map集合所有的键
// 根据键找值
for (String key : keys) {
System.out.println(key + ","+ map.get(key));
}
}
}
}
Map嵌套Map
public class Test3 {
public static void main(String[] args) {
/*
Map嵌套Map:
*/
// 创建Map集合对象
Map<String,String> map1 = new HashMap<>();
map1.put("it001","AAA");
map1.put("it002","BBB");
// 创建Map集合对象
Map<String,String> map2 = new HashMap<>();
map2.put("h001","CCC");
map2.put("h002","DDD");
// 创建Map集合,把以上2个Map集合作为值存储到这个map集合中
Map<String, Map<String, String>> map = new HashMap<>();
map.put("001",map1);
map.put("002",map2);
System.out.println(map.size());// 2
// 获取map集合中的所有键
Set<String> keys = map.keySet();
// 遍历所有的键
for (String key : keys) {
// 根据键找值
Map<String, String> value = map.get(key);
// 遍历value这个Map集合
Set<String> keySet = value.keySet();
for (String k : keySet) {
String v = value.get(k);
System.out.println(k+","+v);
}
}
}
}