总体内容
集合
集合的好处
数组只要初始化了,长度就不能变了,增删都困难
集合体系图
简单演示
Collection
Collection接口实现类的特点
Collection接口常用方法
方法使用说明:
接口(Collection和List)不能被实例化,所以实例化接口的实现类(ArrayList)调用它的方法
List list = new ArrayList();
//这里会自动装箱 int->包装类Integer
//相当于list.add(new Integer(10));
list.add(10);
list.remove(0);//删除第一个元素,返回被删除对象
list.remove("jack");//删除指定元素,返回boolean
list.contains("jack");//返回boolean(是否存在)
//list2是一个存放多个数据的ArrayList,
//containsAll(…)和removeAll(…)也是传入一个集合
list.addAll(list2);
迭代:1. 迭代器遍历
介绍
代码演示
步骤:
- 先得到 集合col 对应的 迭代器
- 使用while循环遍历
- 如果希望再次遍历,需要重置迭代器
快捷键:
1. 快速生成while快捷键 => itit
2. 显示所有快捷键 的快捷键 ctrl+j
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Excise {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection col = new ArrayList();
col.add("jack");
col.add("mary");
col.add("lina");
//使用迭代器遍历 集合col
//1. 先得到 集合col 对应的 迭代器
Iterator iterator1 = col.iterator();
Iterator iterator = iterator1;
//2. 使用while循环遍历
// 快速生成while快捷键 => itit
// 显示所有快捷键 的快捷键 ctrl+j
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
//3. 当退出while后,这时iterater迭代器指向最后一个元素,再使用该iterator会报NoSuchElementException
//4. 如果希望再次遍历,需要重置迭代器
Iterator iterator2 = col.iterator();
}
}
迭代:2. 集合增强for
- 增强for也可以在数组中使用
- 增强for 底层仍然是 迭代器
- 增强for快捷键:I(大写I)
List(Collection)
介绍
List接口方法
- list集合支持索引
- subList(int form,int to) 范围是前闭后开
小练习
List的三种遍历方式
List的子类:Vector、LinkedList、ArrayList都可以用这三种遍历方法–>可以用下标
小练习(对集合进行排序)
先把需要交换的两个数取出来,然后再用set()进行交换
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Book("www", 331, "rrr"));
list.add(new Book("eee", 551, "ggg"));
list.add(new Book("ddd", 131, "vvv"));
System.out.println("=====排序前=====");
for (Object o : list) {
System.out.println(o);
}
System.out.println("=====排序后=====");
sortList(list);
for (Object o : list) {
System.out.println(o);
}
}
public static void sortList(List list) {
//为提高效率,把list.size()放在循环外计算
int listSize = list.size();
for (int i = 0; i < listSize; i++) {
for (int j = 0; j < listSize - 1 - i; j++) {
//先把需要交换的两个数取出来,然后再用set()进行交换
Book book1 = (Book) list.get(j);
Book book2 = (Book) list.get(j + 1);
if (book1.getPrice() > book2.getPrice()) {
//交换(集合可以使用set()来指定下标对应的元素)
list.set(j, book2);
list.set(j + 1, book1);
}
}
}
}
}
@SuppressWarnings({"all"})
class Book {
private String name;
private double price;
private String author;
public Book(String name, double price, String author) {
this.name = name;
this.price = price;
this.author = author;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
String format = String.format("名称:%s\t价格:%.2f\t\t作者:%s", name, price, author);
return format;
}
}
ArrayList
注意事项
扩充机制及底层源码
无参构造器创建和使用ArrayList的源码
(minCapacity是需要的最小容量,elementData.length是数组的实际容量)
有参构造器创建和使用ArrayList的源码
Vector
注意事项
ArrayList和Vector比较
Vector的扩容机制可以自己用debug追一下源码,和ArrayList差不多
LinkedList
介绍
底层操作机制
模拟双向链表
①双向链表的形成,
②从头到尾和从尾到头遍历,
③添加数据
添加结点:四条链条的指向
public class Excise {
public static void main(String[] args) {
//模拟一个简单的双向链表 ==> ①双向链表的形成,②从头到尾和从尾到头遍历,③添加数据
//1. 先定义链表的 三个结点
Node mary = new Node("mary");
Node jack = new Node("jack");
Node tom = new Node("tom");
//2. 把三个结点联系起来 形成双向链表
//mary -> jack -> tom
mary.next = jack;//底层就是:mary的next属性 指向了jack对象
jack.next = tom;
//mary <- jack <- tom
tom.pre = jack;
jack.pre = mary;
//定义双向链表的 头节点
Node first = mary;
//定义双向链表的 尾结点
Node last = tom;
// //3. 从头到尾遍历
// System.out.println("===从头到尾遍历===");
// while (true) {
// if (first == null) {
// break;
// }
// try {
// System.out.println(first);
// } catch (Exception e) {
// System.out.println(e.getMessage());
// }
// //改变指向:first指向当前结点的next结点(直到链表最后一个元素时,next为null,first指向了null,跳出循环)
// first = first.next;
// }
//
// //4. 从尾到头遍历
// System.out.println("===从尾到头遍历===");
// while (true){
// if (last==null){
// break;
// }
// System.out.println(last);
// last=last.pre;
// }
//5. 演示链表添加对象/数据
//在mary 和 jack之间增加 smith
//只需修改 双向链表之间的关联(修改四个链条)
Node smith = new Node("smith");
smith.next = jack;
smith.pre = mary;
mary.next = smith;
jack.pre = smith;
//从头到尾遍历
System.out.println("===从头到尾遍历===");
while (true) {
if (first == null) {
break;
}
try {
System.out.println(first);
} catch (Exception e) {
System.out.println(e.getMessage());
}
//改变指向:first指向当前结点的next结点(直到链表最后一个元素时,next为null,first指向了null,跳出循环)
first = first.next;
}
}
}
//每一个结点都是Node对象
class Node {
public Object item;//存放数据
public Node next;//存放下一个结点
public Node pre;//存放上一个结点
public Node(Object item) {
this.item = item;
}
@Override
public String toString() {
return "item = " + item;
}
}
增删改查方法及部分源码
add()的部分源码–添加在末尾
remove()有三种(无参,下标,对象)
无参的是删除链表第一个结点
ArrayList和LinkedList的比较
ArrayList和LinkedList如何选择
Set(Collection)
介绍
Set接口方法和遍历
使用add方法给HashSet添加元素时,该方法会返回一个布尔值–>是否添加成功
无序是因为:存放数据的顺序不是按照 存放顺序来的,而是根据存放的内容来的
HashSet
介绍
添加元素例题(add)
原因看下面的 扩容机制
模拟数组链表
也就是:定义一个数组 数组中每个元素都是 一个链表(增删高效)
public class Excise {
public static void main(String[] args) {
//模拟HashSet的底层 (HashMap的底层结构)
//1. 创建一个数组,数组的类型是 Node[]
//2. 可以把 Node[]数组称为 表
Node[] table = new Node[16];
//3. 创建结点
Node mary = new Node("mary", null);
Node jack = new Node("jack", null);
Node smith = new Node("smith", null);
//4. 将mary结点 放在数组 索引为2 的位置上
table[2] = mary;
//5. 将其它结点挂载到该节点上,形成链条
mary.next = jack;//将jack挂载到mary上
jack.next = smith;
}
}
class Node {
public Object item;//存放数据
public Node next;//存放下一个结点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
扩容机制(结论和add()源码)
结论
源码
- 执行HashSet()
public HashSet() {//本质是HashMap
map = new HashMap<>();
}
- 执行add()
public boolean add(E e) {//此时e 为"mary"
return map.put(e, PRESENT)==null;
//PRESENT在HashSet定义为:
//private static final Object PRESENT = new Object();
}
- 执行put()
该方法会执行 hash(key),得到key对应的hash值(不等价于hashCode,因为它有自己的算法,算法为:(h = key.hashCode()) ^ (h >>> 16)
)
public V put(K key, V value) {//key="mary" value=PRESENT共享的(固定)
return putVal(hash(key), key, value, false, true);
}
- 执行putVal()–>解读写在代码注释中
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义辅助变量
Node<K,V>[] tab; Node<K,V> p; int n, i;
//table是HashMap定义的数组,类型是Node[]
//if语句表示如果当前table是null,或者长度=0
//就进行第一次扩容,到16个空间(阈值=0.75*16=12,即存储到12开始第二次扩容)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key得到hash,去计算该key应该存放到table表的哪个索引位置,并把这个位置的对象赋给p
//(2)判断p是否为null
//(2.1)若为null,表示还没存放元素,就创建一个Node(key="mary" value=PRESENT next指向null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//开发技巧提示:需要局部变量(辅助变量)时,再定义
Node<K,V> e; K k;
//p指的是:准备添加的数据要添加到数组中的位置table[i]
//如果数组的该索引位置存放的链表的第一个结点的hash值 = 准备添加的数据的hash值
//并且满足下面两个条件之一:
//(1)准备加入的数据key 和 链表第一个结点的key 是同一对象(地址相同)
//(2)用equals()比较准备加入的数据的key 和 链表第一个结点的key 为true(equals方法可以重写-按地址或按内容)
//若满足,则不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断p是不是一颗红黑树
//若是,则调用putTreeVal()来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果数组对应索引位置,已经是一个链表,则使用for循环比较
//(1)依次和该链表的每一个元素比较后,若都不相同(p.next == null)则将准备添加的数据 挂在该链表的最后
/*
注意:在把元素添加到链表末尾后,立即判断 该链表是否已经到达8个结点
- 若到达8个结点,就调用treeifyBin方法
在treeifyBin方法中会对数组长度进行判断
- 若tab==null||tab.length()<64,就调用resize()对数组进行扩容
- 若上面条件不成立,就将该链表转成一颗红黑树
*/
//(2)若在比较过程中,有相同情况(地址或内容),就break
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st,TREEIFY_THRESHOLD = 8
treeifyBin(tab, hash);
break;
}
//在这里判断两元素是否相等(地址或内容)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//移动p到p.next
p = e;
}
}
//如果未被添加到最后(有相同数据),则进行这段,
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//threshold是用容量*0.75计算出来的(防止数据突增,容量不够)
//注意:size 就是我们每加入一个结点
//意思说这个threshold是和加入的结点数比较,不仅是和数组第一个存放的,也包括存放在链表中的
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
转成红黑树机制结论
结论
注意:
①threshold是临界值的意思(threshold是和加入的结点数比较)
②数组中添加的元素个数 到达临界值(0.75数组长度)时,数组就会进行扩容–>数组长度2
HashSet最佳实践
练习1
底层源码思路:
- 计算hash值
- 根据hash值 计算出key在表中的索引值
- 若该位置为空,则直接添加
- 若该位置非空,则比较equals(),若相等,则不添加,若不相等,则添加到链表的末尾
已知:new出的对象,计算出来的hash值一定不等(即存放的索引值也不等),所以默认会存放在表中不同位置。
在本题中:要求name和age相同时,不添加
所以就要让name和age相同的对象返回相同的hash值,再让它们的equals()相等 -->用快捷键重写这两个方法
import java.util.HashSet;
import java.util.Objects;
public class Excise {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//如果name和age相同,则不能添加到hashset中
HashSet hashSet = new HashSet();
hashSet.add(new Employee("王胖子", 21));
hashSet.add(new Employee("高瘦子", 22));
hashSet.add(new Employee("王胖子", 21));
System.out.println(hashSet);
}
}
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.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;
}
//name和age相同的对象返回相同的hash值,再让它们的equals()相等 -->用快捷键重写这两个方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
练习2(hash()源码)
hash()源码–>分析在代码中
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
//重点是这句: element.hashCode()
//计算hash值会调用传进参数的hashCode()
//name是String类型的 String重写了hashCode,所以内容相同则hash值相等
//但是因为birthday是new出来的,默认情况下调用hashCode()计算出的每一个birthday的hash值都不相等
//所以要重写birthday的hashCode(),当三个元素都相等时,hash值相等
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
总体思路:
- 若name和birthday相同,则返回相同的hash值
- hash值相等时,调用Employee类的equals()
细节(可查看源码):
Objects.hash(name, birthday)
会调用birthday的hashCode()Objects.equals(birthday, employee.birthday)
会调用birthday的equals()
import java.util.HashSet;
import java.util.Objects;
public class Excise {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("王胖子", 1000, new MyDate(2001, 1, 1)));
hashSet.add(new Employee("高瘦子", 2000, new MyDate(1999, 2, 2)));
hashSet.add(new Employee("王胖子", 3000, new MyDate(2001, 1, 1)));
System.out.println(hashSet);
}
}
class MyDate {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MyDate)) return false;
MyDate myDate = (MyDate) o;
return year == myDate.year &&
month == myDate.month &&
day == myDate.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
class Employee {
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
//hash+equals
//总体思路:
// 1. 若name和birthday相同,则返回相同的hash值
// 2. hash值相等时,调用Employee类的equals()
//这里会调用birthday的equals()-->将Employee类中equals()重写,当三属性相等时,equals()返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(birthday, employee.birthday);
}
//这里会调用birthday的hashCode()-->看源码
@Override
public int hashCode() {
return Objects.hash(name, birthday);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}' + "\n";
}
}
结果:
LinkedHashSet(HashSet子类)
介绍
HashSet维护的是单向链表
LinkedHashSet维护的是双向链表,有头有尾所以存储有序
底层机制+源码解读
//按照这段代码进行debug
import java.util.LinkedHashSet;
import java.util.Set;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add(new java.lang.String("王胖子"));
set.add(456);
set.add(456);
System.out.println(set);
}
}
- LinkedHashSet 加入顺序和取出顺序一致
- LinkedHashSet 底层维护的是LinkedHashMap(是HashMap的子类)
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
- LinkedHashSet 底层结构是:数组(table)+双向链表
- 第一次添加时:直接将数组table扩容到16,存放的数据结点类型是 LinkedHashMap$Entry
- 数组是 HashMap $Node[],存放的数据是 LinkedHashMap $Entry(LinkedHashMap里定义的内部类Entry)
5.1 因为LinkedHashMap中的内部类Entry继承了HashMap中的Node,所以Node数组中可以存放Entry
5.2 这个entry就是双向链表中的结点,包含了before, after属性
//继承关系是在内部类中完成
//Node类是HashMap中的静态内部类,才能用类名去访问
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
-
LinkedHashSet中的add()还是用了HashSet中的add(),HashSet中的add()调用了HashMap中的put()和putVal()
-
before中存放前一个结点,after存放后一个结点,形成数组+双向链表结构
TreeSet
源码解读
debug用的源码
import java.util.Comparator;
import java.util.TreeSet;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//① 调用String的compareTo方法进行字符串大小比较
// return ((String)o1).compareTo((String)o2);
//② 按照长度大小排序 --> 因为长度相等的返回值为0,所以添加不进去
//这里可以理解为:如果o1长度>o2长度,则交换,所以前面的数据长度小,为从小到大排序
return ((String)o1).length() - ((String)o2).length();
}
});
treeSet.add("ani");
treeSet.add("clo");
treeSet.add("bmi");
System.out.println(treeSet);
}
}
输出结果:
按字符串大小排序输出
按字符串长度大小输出–>因为长度都为3,所以只加入了一个数据
解读源码
① TreeSet构造器把传入的匿名内部类对象,赋给了TreeMap的属性this.Comparator(TreeSet本质是TreeMap)
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
② 执行TreeSet的add()方法
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
③ 执行TreeMap的put()方法
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {//cpr就是我们的匿名内部类对象
do {
parent = t;
//动态绑定到匿名内部类的compare()
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
//如果compare()的返回值是0,
//那么这个数据就加入不了
else
return t.setValue(value);
} while (t != null);
}
Map
Map接口实现类的特点
解读细节8(源码)
① k-v本质是存放在HashMap$ Node中的,但是为了更方便的获取k-v,让Node实现了Map.Entry接口,并把k-v包装成Node 封装到Entry中,再把这个Entry放在集合entrySet中,也就是创建entrySet集合,集合中存放的元素是Entry,但元素中实际上存放的内容是Node(集合中的k-v指向了Node中的k-v)
② 当要通过该集合获取k-v时,首先要向下转型为Entry,再调用Entry的getKey()或getValue()
-源码为:transient Set<Map.Entry<K,V>> entrySet;
- 在entrySet中,定义的类型是Map.Entry,但实际上,存放的内容还是 HashMap$Node,这是因为HashMap $Node 实现了 Map.Entry
- 当把 HashMap $Node 对象 存放到 entrySet 就方便我们遍历,因为Map.Entry 提供了重要方法 K getkey(); V getValue();
用法示例:
Map map = new HashMap();
map.put("no1","王胖子");
map.put("no2","高瘦子");
Map接口方法
Map四大遍历方式
- 先取出所有的key,通过key 取出对应的value(用get()方法)
- 取出所有value
- 通过EntrySet来获取 k-v -->用到向下转型
import java.util.*;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1", "王胖子");
map.put("no2", "高瘦子");
map.put("no3", "小艺艺");
//第一组:先取出所有的key,通过key 取出对应的value(用get()方法)
//① 增强for
System.out.println("=====①由key取出value-->增强for=====");
Set keyset = map.keySet();
for (Object key : keyset) {
System.out.println(key + " - " + map.get(key));
}
//② 迭代器-->获取到keySet的迭代器
System.out.println("=====②由key取出value-->迭代器=====");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {//快捷键itit
Object key = iterator.next();
System.out.println(key + " - " + map.get(key));
}
//第二组:取出所有value
Collection values = map.values();
//使用Collection的遍历方式
//增强for
System.out.println("=====遍历值-->增强for=====");
for (Object value : values) {
System.out.println(value);
}
//迭代器
System.out.println("=====遍历值-->迭代器=====");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
//第三组:通过EntrySet来获取 k-v
Set entrySet = map.entrySet();
//① 增强for
System.out.println("=====③使用EntrySet-->增强for");
for (Object entry : entrySet) {
//将Object向下转型为Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + " - " + m.getValue());
}
//② 迭代器
System.out.println("=====④使用EntrySet-->迭代器");
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
//这里需要说明:HashMap$Node实现了Map$Entry,
//Entry接口默认是public,但是实现类Node是default
//所以HashMap包外的方法不能访问到Node类(即这里没办法转换成Node,只能转换成Entry)
Object node = iterator2.next();
Map.Entry m = (Map.Entry) node;
System.out.println(m.getKey() + " - " + m.getValue());
}
}
}
Map课堂练习
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("2019001111", new Employee("王胖子", 10000, "2019001111"));
map.put("2019002222", new Employee("高瘦子", 20000, "2019002222"));
map.put("2019003333", new Employee("小艺子", 19000, "2019003333"));
//遍历工资大于18000的员工
//方法1:
System.out.println("=====方法一:entrySet=====");
Set entrySet = map.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
//取出值->Employee对象
//因为下面还要用这个变量,所以就不简写了
//注意要在最外面写括号! ((Employee) entry.getValue()).getSal()
Employee employee = (Employee) entry.getValue();
if (employee.getSal() > 18000) {
System.out.println(employee);
}
}
//方法2:
System.out.println("=====方法二:keySet=====");
Set keySet = map.keySet();
for (Object key : keySet) {
//获取value
Employee employee = (Employee) map.get(key);
if (employee.getSal() > 18000) {
System.out.println(employee);
}
}
}
}
class Employee {
private String name;
private double sal;
private String id;
public Employee(String name, double sal, String id) {
this.name = name;
this.sal = sal;
this.id = id;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", id='" + id + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
HashMap
HashMap底层机制结论
HashMap扩容机制结论
hashSet底层就是hashMap
要求明确put()方法的流程 --> 可以看一下前面hashSet的源码解读
hashMap源码解读
根据这段代码debug
-
debug中解读put方法 请看hashSet的源码解读 和 hashMap小结中解读细节5(如何替换)
表的扩容:
-
还有扩容树化前面也有源码解读
① 当链表长度>8且数组长度<64时 在链表中每添加一个node都会对数组进行扩容(调用resize()),直到数组长度为64
② 当数组扩容到64时 再往链表中添加一个node,就会把该链表转成一棵红黑树,结点变为TreeNode
HashMap小结
解读细节5:
将原先key的结点赋值给p,当添加相同的key时,将p赋值给e,e.value是数组中该索引位置的value,将新添加的value赋给这个索引位置的value,就相当于替换了
HashTable(Dictionary的子类)
介绍
HashTable扩容机制
① 初始化table数组Entry[] 大小为11,临界值 大小为8
② put()方法添加结点时,调用addEntry,把结点的K-V封装到Entry中,满足条件的Entry(K-V不为null)放到Entry数组table中(若key重复,则替换value)
③ 当结点数>=临界值(Threshold),则对数组进行扩容
④ 扩容:数组大小*2+1,临界值 *0.75
HashTable和HashMap对比
Properties(HashTable的子类)
介绍
配置文件
常用方法也是增(put)删(remove)改(put相同key)查(get(“key”))
TreeMap
TreeSet本质是TreeMap,只是前者是单例的,只有key,后者是双例的,K-V,前者调用TreeMap方法时,value的值是固定的Present,后者是变化的
源码解读
debug的源码
import java.util.Comparator;
import java.util.TreeMap;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//字符串大小
return ((String) o1).compareTo((String) o2);
//字符串长度
//return ((String)o1).length() - ((String)o2).length();
}
});
treeMap.put("wpz", "王胖子");
treeMap.put("gsz", "高瘦子");
System.out.println(treeMap);
}
}
输出结果
按字符大小
按字符串长度
① 构造器
② put()方法
第一次添加时:把k-v封装到Entry中,放入root
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
第二次添加时:遍历所有key,动态绑定匿名内部类的compare方法
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
集合选型规则!
Collection工具类
介绍
排序操作
查找替换操作
本章练习
练习1:截取和遍历
用到的方法:
ArrayList中:
- 增:add()
- 获取集合中元素: get() --> 可以使用下标获取
- 长度: size()
String中:
- 截取子字符串:subString()
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
//将新闻对象添加到集合中,直接在参数中new,因为只使用一次
arrayList.add(new News("新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧"));
arrayList.add(new News("男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生"));
//1. 倒序遍历->注意:这里不能使用Collections的reverse方法,因为reverse是翻转(会改变集合本身)
//2. 标题超过15个字只保留前15个,然后在后边加"…"
int size = arrayList.size();
for (int i = size - 1; i >= 0; i--) {
//处理前输出
// System.out.println(arrayList.get(i));
//处理标题-->另外写一个方法去处理
News temp = (News) arrayList.get(i);
System.out.println(processTitle(temp.getTitle()));
}
}
//写一个方法来截取标题
public static String processTitle(String title) {
//判断标题有效性
if (title == null || title.equals("")) {
return "";
}
if (title.length() <= 15) {
return title;
}
//若上面的都不满足,则对title进行处理
//title.substring(0,15)会返回截取的字符串
return title.substring(0, 15) + "...";//[0,15)
}
}
class News {
private String title;//标题
private String content;//内容
public News(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "News{" +
"title='" + title + '\'' +
'}' + "\n";
}
}
练习2:ArrayList常用方法
import java.util.ArrayList;
import java.util.Iterator;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
//add,remove,contains
//addAll,constainsAll,removeAll
//clear,size,isEmpty
//使用增强for和迭代器来遍历所有的car
Car car1 = new Car("宝马", 400000);
Car car2 = new Car("宾利", 5000000);
ArrayList arrayList1 = new ArrayList();
ArrayList arrayList2 = new ArrayList();
//1. add()和addAll()
arrayList2.add(car1);
arrayList2.add(car2);
arrayList1.addAll(arrayList2);
//2. remove()和removeAll()
arrayList1.remove(0);
arrayList1.remove(car2);
arrayList1.removeAll(arrayList2);
//3. contains()和containsAll()
System.out.println(arrayList1.contains(car1));
System.out.println(arrayList1.containsAll(arrayList2));
//4. clear
arrayList1.clear();
//5. size()
System.out.println(arrayList1.size());
//6. isEmpty()
System.out.println(arrayList1.isEmpty());
//7. 增强for
for (Object o : arrayList1) {
System.out.println(o);
}
//8. 迭代器
Iterator iterator = arrayList1.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
练习3:HashMap
HashMap中:
- 增加/修改:put() --> 相同key,不同value,会对value进行替换
- 获取key对应的value:get(key) --> 不能用下标,只能传key值,且get只能获取,不能修改
比如:所有员工工资加100元
- 获取到key对应的value,然后对value进行修改
- 相同key,传入不同的value,新value会替换掉旧value
map.put(key, (Integer) map.get(key) + 100);
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class Excise {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("jack", 650);//int-->Integer
map.put("tom", 1200);
map.put("smith", 2900);
System.out.println(map);
//1. 将jack工资更改为2600
map.put("jack", 2600);//替换
System.out.println(map);
//2. 为所有员工工资加100元
// - 思路:要更改工资,那么使用put()替换就可以(用key获取value,然后用put对key的value值进行替换)
Set keySet = map.keySet();
for (Object key : keySet) {
//map.get(key)返回的是Object,所以向下转型为Integer
//用key来获取value值,再对value值进行替换
map.put(key, (Integer) map.get(key) + 100);
}
System.out.println(map);
//3. 遍历集合中所有员工
Set set = map.entrySet();
for (Object o : set) {
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey() + " - " + entry.getValue());
}
//4. 遍历集合中所有工资
Collection values = map.values();
for (Object o : values) {
System.out.println(o);
}
}
}
练习4:TreeSet去重
TreeSet去重
- add()最终调用put(),put()中调用compare(),若已经传入了一个comparator匿名对象,则调用此匿名对象里面的compare()。如果没有传入匿名对象,则会调用 准备添加的对象 实现的Comparable接口的compareTo()去重
- 若传入的是String,String实现了Compareable接口,compareTo方法是按照字符串是否相同做的比较
- 若传入的是自定义的类,且在add()方法中,没有写Comparator匿名内部类,则必须实现Comparable接口,并实现compareTo()方法
练习5:HashSet的add()易错
用到的知识:
- 先计算添加的数据的hash值,
- 然后计算出该hash值应该存放在数组的哪个位置上,
- 若该位置为空,则直接添加,若不为空,比较和数据该位置第一个数据的equals是否true,若true则不添加,若F,则看该链表是否为红黑树,若F,则用equals循环比较链表上的结点,若有一个相同则不添加,若都不相同,则添加到链表末尾
该题:
- 若不重写hashCode(),则若每次new对象都返回不同的hash值(也就是对象存放的地址决定了hash值),若该对象内部属性修改了,hash值还是和原来相等
- 若重写hashCode(),则对象中属性的内容决定了hash值,若name和id相等则hash值相等,若name和id修改,hash值也会跟着修改
- 本题的坑就在于上面说的两句话,当修改了对象的某个属性,hash值会随之改变
解读本题:
- p1修改name为CC,hash值随之发生改变
set.remove(p1)
,会根据修改后p1的hash值计算出p1在数组中的位置,这时的位置不是p1开始时存放的位置了,所以删除失败- 此时输出,会输出p1和p2
再次添加1001和CC,计算出一个地址,这个地址是空的,所以可以添加
此时输出,会输出3个数据,p1,p2和第三次添加的对象
再次添加1001和AA,计算出一个地址,该地址存放着p1,但是p1已经发生了变化(1001和CC),所以equals为F,新添加的数据存到了链表的末尾- 此时输出,会输出4个数据,p1,p2,第三次添加的对象和第四次添加的对象