Set和Map
第一节:Set接口
常用方法:
方法名 | 描述 |
---|---|
boolean add(E e) | 确保此 collection 包含指定的元素(可选操作)。 |
boolean addAll(Collection<? extends E> c) | 将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。 |
void clear() | 移除此 collection 中的所有元素(可选操作)。 |
boolean contains(Object o) | 如果此 collection 包含指定的元素,则返回true。 |
boolean containsAll(Collection<?> c) | 如果此 collection 包含指定 collection 中的所有元素,则返回 true。 |
boolean equals(Object o) | 比较此 collection 与指定对象是否相等。 |
boolean isEmpty() | 如果此 collection 不包含元素,则返回true。 |
iterator() | 返回在此 collection 的元素上进行迭代的迭代器。 |
boolean remove(Object o) | 从此 collection 中移除指定元素的单个实例,如果存在的话(可选作)。 |
boolean removeAll(Collection<?> c) | 移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。 |
int size() | 返回此 collection 中的元素数。 |
Object[] toArray() | 返回包含此 collection 中所有元素的数组。 |
存储特点:
相对无序,不可以存储相同的元素,不可以通过下标访问
public class Demo1 {
public static void main(String[] args) {
//创建对象
Set<String> set=new HashSet<String>();
//1添加
set.add("菊花");
set.add("枸杞");
set.add("红枣");
set.add("人参");
set.add("灵芝");
set.add("枸杞");
System.out.println("元素个数:"+set.size());
System.out.println(set);
//2删除
//2.1删除一个
// set.remove("灵芝");
// System.out.println("删除之后:"+set); //
//2.2清空
// set.clear();
//3遍历
//3.1foreach
System.out.println("--------增强for----------");
for (String string : set) {
System.out.println(string);
}
//3.2使用迭代器
System.out.println("---------迭代器-------"); Iterator<String>
it=set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//4判断
System.out.println(set.contains("菊花"));
System.out.println(set.contains("梅花"));
}
}
Set常用的实现类:
- HashSet
此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。
它不保证set的迭代顺序;特别是它不保证该顺 序恒久不变。
此类允许使用null元素。
Hash:哈希——实际含义散列,就是一种算法,把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散 列值。
哈希表:数组加链表,既有数组的优点也有链表的优点。
存储特点: 相对无序存储,不可以存储相同元素(排重),通过哈希表实现的集合
- 重写hashCode()
hashCode()是Object中的方法,每个对象的hashCode值是唯一的,所以可以理解成hashCode值表示这个对象在 内存中的位置
字符串String的hashCode(),是根据内容计算的。
HashSet集合排重时,需要判断两个对象是否相同,对象相同的判断可以通过hashCode值判断,所以需要重写 hashCode()方法
- 重写equals()
equals()方法是Object类中的方法,表示比较两个对象是否相等,若不重写相当于比较对象的地址, 所以我们可以尝试重写equals方法,检查是否排重
- HashSet集合实现排重
HashSet的重复依据: hashCode和equals
需要同时重写hashCode和equals方法,实现排重
案例:设计一个Student类,同时重写hashCode和equals方法,检查是否实现排重
public class Student {
private String name;
public Student(String name) {
super();
this.name = name;
}
public Student() {
super();
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "Student [name=" + name + "]";
}
@Override
//重写equals
public boolean equals(Object obj) {
//先判断传入的参数对象是否是Student对象,若不是直接返回false
if(obj instanceof Student) {
//若是,强转成Student对象,并比较属性的值
Student s = (Student) obj;
if(this.name.equals(s.name)) {
//若属性的值相同,则返回true
return true;
}
}
return false;
}
@Override
public int hashCode(){
/*hashCode方法返回值是int类型,所以重写时需要找到int类型的数据返回,还
要保证此方法的返回值 与对象的所有属性都相关,所以返回姓名属性的字符串的长度
*/
return this.name.length();
}
}
- LinkedHashSet
LinkedHashSet类是具有可预知迭代顺序(相对有序)的Set接口的哈希表和链接列表实现。
是HashSet的子类。
存储特点: 有序存储,不可以存储相同元素(排重),通过链表实现的集合的有序。
LinkedHashSet集合的元素排重与HashSet集合排重方法一致
- TreeSet
TreeSet集合是可以给元素进行重新排序的一个Set接口的实现。
使用元素的自然顺序对元素进行排序,或者根据创 建 set 时提供的Comparator进行排序,
具体取决于使用的构造方法。
存储特点: 无序存储,排重,通过二叉树实现的集合,可以给元素进行重新排序
-
SortedSet
TreeSet除了实现了Set接口外,还实现了SortedSet接口
方法名 | 描述 |
---|---|
first() | 返回此 set 中当前第一个(最低)元素。 |
last() | 返回此 set 中当前最后一个(最高)元素。 |
headSet(E toElement) | 返回此 set 的部分视图,其元素严格小于toElement。 |
tailSet(E fromelement) | 返回此 set 的部分视图,其元素大于等于fromElement。 |
subSet(E fromElement, E toElement) | 返回此 set 的部分视图,其元素从 fromElement(包括)到toElement(不包括)。 |
- TreeSet的自然排序
元素所属的类需要实现java.lang.Comparable接口,并重写compareTo方法。
compareTo方法除了可以进行排序外,还有排重的功能,但是必须在compareTo方法中对类中
所有的属性值都进行 判断,否则不比较那个属性,排重就会忽略哪个属性
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
//重写compareTo方法,按照年龄升序,若年龄相同按照姓名降序排序
public int compareTo(Person o) {
//Person中有两个属性,此方法中必须对name和age两个属性都进行比较,
//否则将无法正确排重
if(this.age != o.age) {
return this.age - o.age;
}else {
return o.name.compareTo(this.name);
}
}
}
- TreeSet的定制排序
元素需要通过java.util.Comparator接口(比较器)中的compare方法进行比较大小,并排
序。
compare方法除了可以进行排序外,还有排重的功能,但是必须在compare方法中对类中所有
的属性值都进行判断, 否则不比较那个属性,排重就会忽略哪个属性
TreeSet集合中的无参数构造方法默认使用自然排序的方式对元素进行排序,使用TreeSet集
合的定制排序时,创建集合对象不可以直接使用无参数构造方法,需要使用传入一个Comparator比较器的构造方法创建集合对象。
public class Animal {
private String name;
private int age;
@Override
public String toString() {
return "Animal [name=" + name + ", age=" + age + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Animal(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Animal() {
super();
}
}
public class AnimalDemo{
public static void main(String[]args){
//创建一个TreeSet集合,使用Comparator接口的匿名内部类的匿名对象作为比较器
TreeSet<Animal> treeSet = new TreeSet<>(new Comparator() {
@Override
public int compare(Animal o1, Animal o2) {
if(o1.age!=o2.age) {
return o2.age - o1.age;
}else {
return o1.name.compareTo(o2.name);
}
}
});
//添加元素
treeSet.add(new Animal("大黄", 1));
treeSet.add(new Animal("旺财", 2));
//遍历集合
Iterator<Animal> it = treeSet.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
第二节:Map接口
Map接口是将键映射到值的对象。一个映射不能包含重复的键,每个键最多只能映射到一个值,
值可以重复。
cn--->中国
usa--->美国
uk--->英国
us--->美国
cn--->中华人民共和国
方法名 | 描述 |
---|---|
clear() | 从此映射中移除所有映射关系(可选操作)。 |
containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回 true。 |
containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回 true。 |
entrySet() | 返回此映射中包含的映射关系的 Set集合。 |
equals(Object o) | 比较指定的对象与此映射是否相等。 |
get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 |
hashCode() | 返回此映射的哈希码值。 |
isEmpty() | 如果此映射未包含键-值映射关系,则返回 true。 |
keySet() | 返回此映射中包含的键的 Set 集合 |
put(K key, V value) | 将指定的值与此映射中的指定键关联(可选操作) |
putAll(Map<? extends K,? extends V> m) | 从指定映射中将所有映射关系复制到此映射中(可选操作)。 |
remove(Object key) | 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 |
size() | 返回此映射中的键-值映射关系数。 |
/**
* Map集合的使用
* 1 存储的键值对,键不能重复,一个键对应一个值,值可以重复。
* 2 无序
*/
public class Demo1 {
public static void main(String[] args) {
//创建集合
Map<String, String> map=new HashMap<>();
//1添加元素
map.put("cn", "中国");
map.put("usa", "美国");
map.put("uk","英国");
map.put("kor", "韩国");
map.put("cn", "中华人民共和国");//会把原来的值覆盖掉
//map.put("us", "美国");
System.out.println("元素个数:"+map.size());
System.out.println("打印:"+map.toString());
//2删除
//map.remove("uk");
//System.out.println("删除之后:"+map.toString());
///map.clear();
//3遍历
//3.1使用keySet遍历
System.out.println("---------keySet()----------");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key+"..........."+map.get(key)); }
//3.2使用entrySet();
System.out.println("---------entrySet()---------");
Set<Entry<String, String>> entrySet =map.entrySet();
//Entry(映射对)
for (Entry<String, String> entry : entrySet){
System.out.println(entry.getKey()+"...."+entry.getValue());
}
//4判断
//4.1isEmpty
System.out.println("判断isEmpty():"+map.isEmpty());
//4.2判断是否包含指定的key
System.out.println("cn:"+map.containsKey("cn"));
//4.3判断是否包含指定的value
System.out.println("韩国:"+map.containsValue("韩国"));
}
}
常用类:
- HashMap
基于哈希表的Map接口的实现。此实现提供所有可选的映射操作,
并允许使用null值和null键。 此类不保证映射的顺序。
存储特点: 相对无序存储,元素以键值对形式存在,键不可以重复,值可以重复,
元素整体排重,可以快速的通过键查找到 所对应的值,通过哈希表实现的集合。
HashMap集合的排重,只需要重写键所属的类的hashCode和equals方法即可。
map集合中若向集合中添加相同键的键值对时,新的值会将旧的值覆盖。
- LinkedHashMap
LinkedHashMap集合是具有可预知迭代顺序的Set接口的哈希表和链接列表实现。此实现与
HashSet的不同之外在 于,后者维护着一个运行于所有条目的双重链接列表。
用法与HashSet类似。
存储特点: 有序存储,元素排重,通过链表实现的集合。
- HashTable
此类实现一个哈希表,该哈希表将键映射到相应的值。
任何非null对象都可以用作键或值。
Hashtable有一个子类Properties,Properties集合使用的比较频繁。
存储特点: 相对无序存储,元素排重,通过哈希表实现的集合
Map集合的遍历:
- 使用keySet方法与get方法结合
public class Demo {
public static void main(String[] args){
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 12);
map.put("bbb", 13);
map.put("ccc", 14);
//通过keySet获取map中所有键
Set<String> set = map.keySet();
//获取set的迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
//通过迭代的键,找到对应的值,一起输出
System.out.println(s+"---"+map.get(s));
}
}
}
-
使用Map.Entry方法
调用Map集合的entrySet方法,相当于将Map集合转成一个Set集合,再通过Set集合的遍历方式遍历即可。
public class Demo {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 111);
map.put("bbb", 222);
map.put("ccc", 333);
//将map转成一个Set集合
Set<Map.Entry<String, Integer>> set = map.entrySet();
//遍历set
Iterator<Map.Entry<String, Integer>> it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
第三节:Collections工具类
此类完全由在 Collection 上进行操作或返回 Collection 的静态方法组成。对集合进行
操作时,可以使用这个类中的静态方法。
常用方法:
1. 排序
/* static <T extends Comparable<? super T>> void sort(List<T> list)
根据元素的自然顺序 对指定列表按升序进行排序。
*/
ArrayList<Integer> list = new ArrayList<>();
list.add(-10);
list.add(5); list.add(3);
list.add(7);
System.out.println(list);
Collections.sort(list);//默认升序
System.out.println(list);
2.将集合中的元素进行反转
/*
static void reverse(List<?> list) 反转指定列表中元素的顺序。 */
Collections.reverse();
3.将集合中的元素打乱
/*
static void shuffle(List<?> list) 使用默认随机源对指定列表进行置换。
*/
4.获取集合中的最大值和最小值
/*
static T max(Collection coll)
根据元素的自然顺序,返回给定 collection 的最大元素。
static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
根据元素的自然顺序 返回给定 collection 的最小元素。
*/
int n1 = Collections.max(list)
5. 替换
/*
static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)
使用另一个值替换列表中出现的所有某一指定值
*/
//原因:List集合是不排重的,使用新的元素将集合中出现的所有的旧的元素替换掉
Collections.replaceAll(list,5,100);
6. 统计指定元素在集合中出现的次数
/*static int frequency(Collection<?> c, Object o)
返回指定 collection 中等于指定对象的元素数。
*/
int num = Collections.frequency(list,5);
7.二分法查找
/*
static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
使用二分搜索法搜索指定列表,以获得指定对象。
*/
//前提:必须是排好序的集合
int index = Collections.binarySearch(list,-10);
//注意:Collections工具类中的方法只操作Collection接口,主要操作的是List接口
8.集合和数组之间的转换
//1数组转成集合,集合不能做添加、删除操作
public static void arrayToList() {
String[] names= {"张三","李四","王五"};
List<String> list=Arrays.asList(names);
System.out.println("list:"+list);
//添加 //list.add("赵六");
//删除 //list.remove(0);
//System.out.println("添加或删除之后list:"+list);
list.set(0, "本伟");
System.out.println("替换之后:"+list);
}
//2 集合转成数组
public static void listToArray() {
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("苹果");
arrayList.add("小米");
arrayList.add("华为"); arrayList.add("三星");
//数组
String[] arr=arrayList.toArray(new String[0]);
System.out.println("--------遍历数组-------");
for (String string : arr) {
System.out.println(string);
}
}
//3特殊
public static void arrayToList2() {
Integer[] nums= {10,20,5,8,9};
List<Integer> arraylist=Arrays.asList(nums);
for (Integer integer : arraylist) {
System.out.println(integer);
}
}
}
总结:
Collection 父接口
|_____List (特点:有序的,可以重复)
|___ArrayList (存储结构:数组,适合遍历查找)
|___LinkedList (链表,适合添加和删除)
|___Vector 数组
|___Stack 数组(栈)先进后出
|_____Set(特点:无序的,不能重复)
|_____HashSet 哈希表(数组+链表+二叉树)
重复依据:1 执行hashCode()来计算存储的位置
2 执行equals比较结果
|_____LinkedHashSet 哈希表 可以保证顺序
|_____TreeSet 自平衡红黑二叉树
1 元素要实现Comparable接口
2 定制比较器 Comparator 重复依据:
Comparable接口的compareTo方法的返回值0,或比较器的返回为0
Map(特点:1存储键值对,一个键对应一个值,键不能重复,值可以重复 2 无序)
|______ HashMap 哈希表
1 执行hashCode()来计算存储的位置 ,
2 执行equals比较结果
|______ Hashtable 哈希表 不能存储null键和null值,线程安全的 jdk1.0
--Properties
|_______LinkedHashMap 哈希表 可以保证顺序
|_______TreeMap 自平衡红黑二叉树
1 key 要实现Comparable接口 ,
2 定制比较器 Comparator
Collections工具类的使用。
Set
特点: 无序 ,无下标 ,不重复
set 没有特有方法。都是继承于父接口。
底层通过哈希计算 在内存的地址。
相同对象,哈希值一定相同。不同对象尽可能不一样。换句话说有可能相同。
①HashSet 作为主要实现类
1. 判断hashCode 是否相同,如果不同,直接存储。当不重复处理
2. 当hashCode 相同时,它才会进行equals比较 内容,当内容相同了,不添加。
② TreeSet 实现 自然排序
要求 增加到该集合里的类 必须支持排序
必须要实现Comparable 接口 否则汇报类型转换异常
实现Comparable接口 要重写 CompareTo() 方法 ,确定比较的依据。
自然排序和定制排序相比:定制排序的优先级高,而且和类解耦!!!
③LinkedHashSet 基于链表的结构 多维护一层添加顺序。//按照添加的顺序输出内容
Map
遍历:
所有的key
keySet() ==> Set 集合
所有的value
values() ==》 Collection集合
所有的键值对
entrySet == > Set集合
在得到键值对后 键值对的类型 Map.Entry 类型
getKey() 获取该键值对 的 键
getValue() 获取该键值对的 值
HashMap 的底层数据结构:
数组 + 链表 + 红黑树
new HashMap()
(1) 加载因子 loadfactor = 0.75f
(2) Node<K,V>[] table ==》 tab
(3)resize() 初始长度为 16 DEFAULT_INITIAL_CAPACITY = 1 << 4;
Node[] 数组 长度 初始为 0
第一次调用put 长度为16 ;
数组里的元素 Node{
1. key
2. value
3. hash
4. next
}
每一个Node节点 存储时 是通过 key值的哈希值 %16,得到要存储的具体下标[0 15]
Node 节点包括四部分 k v next hash ,Node 相当于每个entry对象(键值对),实现了Map.entry接口
30 %16 14
14 %16 14
对于,存储下标相同时,判断equals 如果是key的哈希值以及equals都一样,key相同,后来的覆盖前面的。
如果下标相同,key内容不相同,通过链表的方式挂载节点。注意:不会无穷挂载,会在节点数等于8时,转成红黑树(平衡二叉树)。当二叉树超过6层后又转成链表。
HashMap 在设计 对key的哈希算法,尽量让 不同的key 存储在不同的下标,当数组 不够存储时,扩容机制。
是在 原数组长度*0.75时(达到阈值时) 扩容 原数组长度*2
比如: Node[] table 16
当存储的元素在数组中达到 16*0.75 ==12,扩容为 16*2 32 ;
下次再扩容 当 32*0.75 24 时, 扩容为 32*2 ==64
HashSet 的构造方法 ,
底层是 HashMap
public HashSet() {
map = new HashMap<>();
}
Set set = new HashSet(); // map = new HashMap<>();
当调用HashSet的add 方法时,其实调用的是map的put方法
set.add()// ==> //map.put(e, PRESENT)==null;
源码:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
put 方法会把 set存进的值当 key 存储,value 是一个常量对象 new Object();
HashSet 怎么 去 重?
1. HashSet 是通过 hashCode 和 equals 方法 辨别元素是否重复。
2. 由于 HashSet的底层是HashMap ,这样在add元素时,会调用 map的put方法,
把Set集合中要存储的值当成 key处理,而key 是唯一的。