今日内容
1.复习
2.LinkedList
3.Set(HashSet、TreeSet)
4.Map(HashMap)
一、复习
List集合的特点?
- 有序,允许重复
ArrayList的底层实现原理,以及特点
- 数组,初始10,扩容1.5倍
- 查询更新快,删插入慢
- 解释为什么快,慢?
增强for循环语法
写出以下几个集合的方法签名
- 向集合添加元素 boolean add(E e)
- 向集合指定下标处添加指定元素 void add(int index,E e)
- 根据下标查询集合中元素 E get(int index)
- 查询集合的大小 int size()
二、LinkedList[熟悉]
LinkedList是List的实现类,那么LinkedList也是允许重复,有序
且LinkedList集合也有关于下标操作集合的方法,但是还提供了一些关于操作开头和结尾的方法
底层是使用
链表
实现.
2.1 演示方法
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>( );
list.add(3);
list.add(1);
list.add(1);
list.add(4);
list.add(4);
list.add(2);
// 有序,允许重复
System.out.println(list );
// 且也有关于的下标的操作方法
list.add(0,0);
System.out.println(list );
Integer e = list.get(1);
System.out.println(e );
// 也可以遍历
for (Integer i : list) {
System.out.println(i );
}
// 专门提供了关于头尾的操作
list.addFirst(-1);
list.addLast(8);
System.out.println(list );
/**
* 类似还有
* getFirst
* getLast
* removeFirst
* removeLast
*/
}
2.2 底层原理
底层是双向链表实现,空间不连续
虽然有下标,但是不能直接定位元素,是通过位运算判断下标离左边还是右边近,然后再从左边或者右边一个个的挨个查的 —> 所以查询慢
但是又因为空间不连续,插入删除时不影响其他元素,相对于ArrayList更快
2.3 特点
- 有序
- 允许重复
查找,更新时效率比较低
插入,删除时效率比较高
三、Set
Set是Collection集合的子接口,主要特性是
不允许重复元素
Set接口中的操作集合的API与Collection中一模一样
Set接口有两个主要的实现类:HashSet,TreeSet
3.1、HashSet[重点]
HashSet类实现了Set接口,也是
不允许重复元素
HashSet集合底层是HashMap(哈希表),存储的元素
无序
,无序是指迭代顺序和插入顺序不一致不保证线程安全,线程不同步
3.1.1 方法演示
构造方法
HashSet()
构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 16
,加载因子是 0.75
。HashSet(Collection<? extends E> c)
构造一个包含指定 collection 中的元素的新 set- HashSet(int initialCapacity) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。
- HashSet(int initialCapacity, float loadFactor) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和指定的加载因子。
方法
HashSet类中的方法与父接口Set接口中的方法一致,即又跟Collection接口中方法一致,无特殊方法,没有关于的下标的方法
public static void main(String[] args) {
HashSet<Integer> set = new HashSet<>( );
boolean r1 = set.add(31);
System.out.println(r1 );
boolean r2 = set.add(11);
System.out.println(r2 );
// set集合不允许重复
boolean r3 = set.add(11);
System.out.println(r3 );
set.add(41);
set.add(41);
set.add(21);
// 遍历顺序和插入顺序不一致
for (Integer i : set) {
System.out.println(i );
}
// 自习演示
// 添加,删除,大小,判断(空,包含),遍历
}
3.2.2 扩容机制[面试]
HashSet底层HashMap,其实是Hash表,存储数据是散列存储
可以理解为存储进去是随机的默认初始容量16,加载因子0.75 —> 扩容的阈值= 容量 * 因子 = 16 * 0.75 = 12
即超过12个元素时就要触发扩容,扩容成原来的2倍
(ps: 初始容量和加载因子是可以通过构造方法创建时修改的…)
3.2.3 去重原理[面试]
练习1: 将学生存储到HashSet集合中,如果属性一致则去重
- 调用add(E e)方法时,会在底层调用元素e的hashcode方法来获得对象的地址值
- 如果地址值不一样,直接存储
- 如果地址值一样时,会再调用元素的equals方法判断元素的内容是否一样
- 如果equals为false,那么存储 但是如果equals判断值为true,那么去重
总结: 以后只需要使用工具生成hashcode和equals就可以再HashSet中去重!
package com.qf.set;
import java.util.HashSet;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class TestHashSet2 {
public static void main(String[] args) {
/**
* 调用hashcode获得地址值
* 如果地址值不一样,直接存储
* 如果地址值一样,也没有直接舍弃不存储,而是再调用
* equals判断对象内容
* 如果内容判断不一样,存储
* 如果内容一样,舍弃不存储
*/
HashSet<Student> set = new HashSet<>( );
set.add(new Student(18,"张三"));
System.out.println("------------------" );
set.add(new Student(18,"张三"));
System.out.println("------------------" );
set.add(new Student(19,"李四"));
System.out.println("------------------" );
set.add(new Student(19,"李四"));
System.out.println("------------------" );
set.add(new Student(20,"王五"));
System.out.println("------------------" );
for (Student student : set) {
System.out.println(student );
}
}
}
// Student类要重写hashcode和equals
package com.qf.set;
import java.util.Objects;
public class Student {
private int age;
private String name;
// idea 生成hashcode和equals方法
@Override
public boolean equals(Object o) {
System.out.println("equals()...." );
if (this == o) return true;
if (o == null || getClass( ) != o.getClass( )) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
// setget toString 构造...
}
3.2 TreeSet[了解]
TreeSet基于TreeMap,TreeMap底层是红黑树(一种平衡二叉树),会实现对存储的元素
排序
TreeSet是Set集合的实现,也是
不允许重复
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>( );
set.add(33);
set.add(3);
set.add(3);// 3 存储不了,去重
set.add(41);
set.add(11);
set.add(12);
System.out.println(set );// 迭代有顺序,默认是升序
}
四、Map<K,V>[重点]
Map代表
双列集合
,一次存储一对键值对(K,V)Map是接口,代表是键映射到值的对象,一个Map
不能包含重复的键
,值允许重复
每个键最多只能映射到一个值,
可以通过键找到值
,但是不能通过值找键
.
方法都是非常常见的方法,但是Map是接口无法演示
Map有两个常用实现类
- HashMap
- TreeMap
4.1 HashMap[重点]
HashMap是Map的实现类,现在JDK8及以后底层是由数组+链表+红黑树实现
并允许使用null
值和null
键HashMap存储的元素是
不保证迭代顺序,存储的键不允许重复,值允许重复
除了非同步和允许使用 null 之外,
HashMap
类与Hashtable
大致相同
补充: Hashtable是线程安全的map集合(Hashtable是不允许null值null键),效率低 ; HashMap是线程不安全的,效率高
ConcurrentHashMap 即安全又高效的Map集合
HashMap的容量和扩容: 初始容量16,加载因子0.75 阈值是 16 * 0.75,达到阈值扩容至原来的2倍
ps: 昨天学习的HashSet所有特性,其实就是HashMap的特性,包括去重原理
4.1.1 方法演示
构造方法
HashMap()
构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity)
构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor)
构造一个带指定初始容量和加载因子的空 HashMap。
HashMap(Map<? extends K,? extends V> m)
构造一个映射关系与指定 Map 相同的新 HashMap。
方法
每个都很重要!!!
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>( );
map.put("A",1);
map.put("B",2);
map.put("C",3);
map.put("D",4);
System.out.println("初始map:"+map );
// 通过键获得值
Integer v = map.get("E");// 找不到返回null
Integer v2 = map.get("B");
System.out.println("键找值: "+ v2 );
// 根据键移除整个键值对,返回值
Integer a = map.remove("A");
System.out.println("移除键A,返回值 " + a );
System.out.println("移除后"+map );
// 大小
int size = map.size( );
System.out.println("size:"+size );
// 判断是否为空
System.out.println("是否为空: "+map.isEmpty() );
// 清空
map.clear();
System.out.println("是否为空: "+map.isEmpty() );
// boolean containsKey(Object key)
// 判断集合是否包含键
// boolean containsValue(Object value)
// 判断集合是否包含值
}
private static void show1() {
HashMap<String, Integer> map = new HashMap<>( );
System.out.println(map );
// 存储数据 V put(K k,V v)
// 返回值是该键之前的值
Integer old = map.put("A", 1);
// 存储相同的键,键不保留,但是值会替换
Integer old2 = map.put("A", 2);
System.out.println(old +"----"+old2 );
// 值允许重复,值2可以保留
map.put("C",2);
map.put("D",4);
map.put("B",2);
// 无序,键不能重复,值允许重复
System.out.println(map );
}
4.1.2 迭代/遍历
Map集合本身并没有设计独立的迭代Map的方法,但是
Map
接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个Map的内容
- Set keySet() 键集,返回一个Set集合,其中只有键
- Collection values() 值集,返回一个Collection集合,其中只有值
- Set<Map.Entry<K,V>> entrySet() 键值映射集,返回一个Set集合,其中放着key-value对象
4.1.3 键集
// 键集,返回一个集合,只有键
Set<Integer> keySet = map.keySet();
Iterator<Integer> iterator = keySet.iterator( );
while (iterator.hasNext( )) {
System.out.println(iterator.next() );
}
System.out.println("------------" );
for (Integer key : keySet) {
System.out.println(key );
}
4.1.4 值集
// 值集,返回一个集合,只有值
Collection<String> values = map.values();
Iterator<String> iterator1 = values.iterator( );
while (iterator1.hasNext()) {
System.out.println(iterator1.next() );
}
System.out.println("-------------" );
for(String value : values) {
System.out.println(value );
}
4.1.5 键值映射集 [非常重要]
Entry是Map接口中的内部接口,代表是一个键值对,即包含键和值.
且该Entry接口中提供了关于操作单个键,值的方法
- K getKey()
- V getValue()
// 调用entrySet方法返回Set集合,集合中存储的是Map.Entry
// Entry是Map的内部接口,代表的是一项(键值对)
Set<Map.Entry<Integer,String>> entrySet = map.entrySet();
Iterator<Map.Entry<Integer,String>> iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
// 从迭代器取出来的是Entry对象
Map.Entry<Integer,String> entry = iterator2.next();
// 通过entry对象可以获得键和值
Integer key = entry.getKey( );
String value = entry.getValue( );
System.out.println(key +"-->" +value);
}
System.out.println("===============" );
// 增强for
for(Map.Entry<Integer,String> entry : entrySet) {
Integer key = entry.getKey( );
String value = entry.getValue( );
System.out.println(key +"-->" +value);
}
4.1.6 去重原理
HashMap的
键去重
其实就是之前讲的HashSet的去重,因为HashSet底层就是HashMap
在创建HashSet时,其实在底层创建了HashMap
在向set中添加元素时,其实是向map的key上添加
所以HashMap的
键的去重原理
就是
- 向键存储数据时,先调用键的hashcode()方法
- 如果hashcode值不一样则直接存储
- 如果hashcode值一样,再调用元素的equals()方法
- 如果equals方法返回false,则存储
- 如果equals方法返回true,则不存储
4.2 HashMap的应用
场景一: 适合有
关联映射
的场景
- 电话 110 --> 警察
- 行政区划 0371 --> 河南
- 简称 豫 --> 河南
- 身份 41011111 —> zhangsan
public static void main(String[] args) { HashMap<String, String> m = new HashMap<>( ); m.put("河南","豫"); m.put("河北","冀"); m.put("山西","晋"); m.put("陕西","陕"); m.put("安徽","皖"); }
设计方法,传入字符串,输出该字符串中每个字符出现的次数,使用HashMap实现
例如: “abcHelloabcWorld”,输出 a出现2次,b出现2次,l出现3次,H出现1次
// a --> 2
// b --> 2
public static void cishu(String str) {
// key存字符,value存次数
HashMap<String, Integer> map = new HashMap<>( );
// 方案1: split("")
// 方案2: toCharArray()
// 方案3: 遍历字符串
char[] chars = str.toCharArray( );
for (char c : chars) {
String s = String.valueOf(c);
// 判断map是否有改字符
boolean b = map.containsKey(s);
if (!b) {// 不包含,则第一次存入
map.put(s,1);
}else{ // 之前有过该字符,次数+1
Integer count = map.get(s);
count++;
map.put(s,count);
}
}
Set<Map.Entry<String, Integer>> entrySet = map.entrySet( );
for (Map.Entry<String, Integer> entry : entrySet) {
String key = entry.getKey( );
Integer value = entry.getValue( );
System.out.println("字符:"+key+"-->"+value+"次" );
}
}
场景二:
Map --> java对象
public class User{ private int id; private String username; //,,, } User user = new User(); user.setId(1); user.setUsername("zs"); sout(user); // User{id=1,username=zs}
HashMap map = new HashMap(); map.put("id",1); map.put("username","zs"); sout(map); // {id=1,username=zs}
五、集合总结
重点
- ArrayList: 方法(创建,crud,遍历),实现原理,特点
- HashSet: 方法,实现原理,特点
- 集合遍历(迭代) 增强for循环一定要熟悉
熟悉了解
- LinkedList 方法和实现原理
- TreeSet 会排序
以后如何选择集合
- 没有任何要求的,只是需要存储多个元素的 --> ArrayList
- 如果要求插入顺序和遍历顺序一致 --> ArrayList
- 如果要求保留重复元素 --> ArrayList
- 如果要求去重 --> HashSet
- 如果要求排序 --> TreeSet
以后存储的数据有关联的,用HashMap
其实经验上: 最最常用就俩: ArrayList,HashMap
HashMap map = new HashMap();
map.put(“id”,1);
map.put(“username”,“zs”);
sout(map); // {id=1,username=zs}