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是List的实现类,那么LinkedList也是允许重复,有序
且LinkedList集合也有关于下标操作集合的方法,但是还提供了一些关于操作开头和结尾的方法
底层是使用
链表
实现.
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
*/
}
底层是双向链表实现,空间不连续
虽然有下标,但是不能直接定位元素,是通过位运算判断下标离左边还是右边近,然后再从左边或者右边一个个的挨个查的 —> 所以查询慢
但是又因为空间不连续,插入删除时不影响其他元素,相对于ArrayList更快
查找,更新时效率比较低
插入,删除时效率比较高
Set是Collection集合的子接口,主要特性是
不允许重复元素
Set接口中的操作集合的API与Collection中一模一样
Set接口有两个主要的实现类:HashSet,TreeSet
HashSet类实现了Set接口,也是
不允许重复元素
HashSet集合底层是HashMap(哈希表),存储的元素
无序
,无序是指迭代顺序和插入顺序不一致不保证线程安全,线程不同步
构造方法
HashSet()
构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 16
,加载因子是 0.75
。HashSet(Collection 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 );
}
// 自习演示
// 添加,删除,大小,判断(空,包含),遍历
}
HashSet底层HashMap,其实是Hash表,存储数据是散列存储
可以理解为存储进去是随机的默认初始容量16,加载因子0.75 —> 扩容的阈值= 容量 * 因子 = 16 * 0.75 = 12
即超过12个元素时就要触发扩容,扩容成原来的2倍
(ps: 初始容量和加载因子是可以通过构造方法创建时修改的…)
练习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 构造...
}
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是接口,代表是键映射到值的对象,一个Map
不能包含重复的键
,值允许重复
每个键最多只能映射到一个值,
可以通过键找到值
,但是不能通过值找键
.
方法都是非常常见的方法,但是Map是接口无法演示
Map有两个常用实现类
- HashMap
- TreeMap
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的特性,包括去重原理
构造方法
HashMap()
构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity)
构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor)
构造一个带指定初始容量和加载因子的空 HashMap。
HashMap(Map 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 );
}
Map集合本身并没有设计独立的迭代Map的方法,但是
Map
接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个Map的内容
- Set keySet() 键集,返回一个Set集合,其中只有键
- Collection values() 值集,返回一个Collection集合,其中只有值
- Set
> entrySet() 键值映射集,返回一个Set集合,其中放着key-value对象
// 键集,返回一个集合,只有键
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 );
}
// 值集,返回一个集合,只有值
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 );
}
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);
}
HashMap的
键去重
其实就是之前讲的HashSet的去重,因为HashSet底层就是HashMap
所以HashMap的
键的去重原理
就是
- 向键存储数据时,先调用键的hashcode()方法
- 如果hashcode值不一样则直接存储
- 如果hashcode值一样,再调用元素的equals()方法
- 如果equals方法返回false,则存储
- 如果equals方法返回true,则不存储
场景一: 适合有
关联映射
的场景
- 电话 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}