1、引言
前面我们保存多个数据使用的是数组,但是数组也有很多不足的地方
- 长度开始时必须指定,一旦指定,就不可以再更改
- 保存的必须为同一类型的元素
- 使用数组进行增加元素的示意代码 -比较麻烦
2、集合的框架体系
3、Collection&Map
3.1、常用方法(以ArrayList为例)
public class CollectionMethod {
@SuppressWarnings("all")
public static void main(String[] args) {
List list = new ArrayList();
// 添加单个元素
list.add("jack");
list.add(10); // 相当于list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);
// 删除指定元素
list.remove(1); // 删除第一个元素 如果参数是int类型 remove的是指定索引
list.remove("jack1"); // 指定删除某个元素
System.out.println("list=" + list);
// 查找元素是否存在
System.out.println(list.contains("jack"));
// isEmpty 集合是否为空
System.out.println(list.isEmpty());
// clear 清空集合
list.clear();
// addAll 添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));
// removeAll: 删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);
}
}
3.2、迭代器
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator仅用于遍历集合,其本身并不存放对象
package collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator {
@SuppressWarnings("all")
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
// 遍历集合
/**
* 1. 先得到集合对应的迭代器
*/
Iterator iterator = col.iterator();
while (iterator.hasNext()) {
// 返回下一个元素,类型是Object
// 虽然编译类型是Object 但是运行类型仍然是Book
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
/**
* 快速生成while + 迭代器循环 :itit
* ctrl + j 所有快捷提示键
*/
while (iterator.hasNext()) {
Object next = iterator.next();
}
// 3. 当退出while循环后 这时iterator迭代器 会指向最后的元素 NosuchElementException
iterator.next();
// 4. 如果希望再次遍历 需要重置迭代器 将迭代器的指向重新指向第一个元素
iterator = col.iterator();
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
3.3、List专用方法
public class Collection04ArrayList {
@SuppressWarnings("all")
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("张三丰");
list.add("贾宝玉");
list.add("贾宝玉1");
// add : 在index位置上插入ele元素
list.add(1, "韩顺平"); // 不带index默认加到最后
System.out.println("list=" + list);
// indexof 返回obj在集合中首次出现的位置
System.out.println(list.indexOf("贾宝玉"));
// lastIndexOf 返回obj在集合中最后一次出现的位置
System.out.println(list.lastIndexOf("贾宝玉"));
// remove 移除指定index位置的元素(后面的元素往前移动一个元素) 并返回此元素
System.out.println(list.remove(1));
System.out.println(list.get(1));
// set 设置指定index位置的元素为ele,相当于替换
// 与add不同的是 set是替换 add是在硬塞
list.set(1, "玛丽");
System.out.println(list);
// subList 返回从fromIndex 到 toIndex 位置的子集合
List returnList = list.subList(0, 2);
System.out.println("returnList=" + returnList);
}
}
3.4、ArrayList
元素可以为空
3.5、ArrayList源码跟踪
3.6、Vector
3.7、LinkedList初识
package collection;
public class Collect05LinkedList {
public static void main(String[] args) {
Node jack = new Node("jack");
Node tom = new Node("tom");
Node hsp = new Node("hsp");
// 连接三个节点 形成双向链表
jack.next = tom;
tom.next = hsp;
hsp.pre = tom;
tom.pre = jack;
Node first = jack;
Node last = hsp;
// 演示 遍历
while (true) {
if (first == null) {
break;
}
System.out.println(first);
first = first.next;
}
// 演示链表添加节点
Node smith = new Node("smith");
smith.next = hsp;
smith.pre = tom;
hsp.pre = smith;
tom.next = smith;
// 添加完smith后
first = jack;
while (true) {
if (first == null) {
break;
}
System.out.println(first);
first = first.next;
}
}
}
// 定义一个Node类 表示双向链表的一个节点
class Node {
public Object item;
public Node next;
public Node pre;
public Node(Object name) {
this.item = name;
}
public String toString() {
return "Node name=" + item;
}
}
3.8、LinkedList源码
package collection;
import java.util.LinkedList;
public class Col07LinkedListCrud {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
// 新增
linkedList.add(1);
linkedList.add(2);
// 删除
// 不带参数默认删除第一个节点
// 带参数删除指定节点
linkedList.remove();
linkedList.remove(1);
System.out.println(linkedList);
// 修改某个节点对象
linkedList.set(1, 999);
}
}
3.9、Set
3.10、HashSet
package collection;
import java.util.HashSet;
import java.util.Set;
@SuppressWarnings("all")
public class Collection07Set {
public static void main(String[] args) {
/*Set set = new HashSet();
set.add("john");
set.add("lucy");
set.add("john");
set.add("jack");
set.add(null);
set.add(null);*/
/**
* HashSet源码解析
* 1、 public HashSet() {
* map = new HashMap<>();
* }
* 2、HashSet可以存放空值,但是只能存一个空值,即元素不可以重复
*
*/
Set set = new HashSet<>();
System.out.println(set.add("john"));
System.out.println(set.add("lucy"));
System.out.println(set.add("john"));
System.out.println(set.add("jack"));
System.out.println(set.add("rose"));
set.remove("john");
set.clear();
set.add("lucy");
set.add("lucy");
set.add(new Dog("Tom"));
set.add(new Dog("Tom"));
System.out.println("set=" + set); // 最终返回的仍然是两个Tom 因为这两个对象在堆中是不一样的
// 继续上强度
set.clear();
set.add(new String("hsp"));
set.add(new String("hsp")); // 这样是加不进去了 为什么呢? new String不是两个对象吗?这里要看源码才能知道原因
System.out.println("set=" + set);
}
}
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
HashSet:
1、HashSet底层是HashMap
2、添加一个元素时,先得到hash值,然后将hash值转化为索引值
3、找到存储数据表table,看这个索引位置是否已经有元素存在
4、如果没有,就直接加入
5、如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后,这里一定要注意,equals是程序员决定的,换言之,程序员可以通过重写equals方法决定什么才是想等,什么才是不相等!
6、在Java8中,如果一条链表的元素个数超过8,并且table的大小大于等于64,就会树化,转为红黑树
/**
* 1、 构造器实际上new了个hashMap
* public HashSet() {
* map = new HashMap<>();
* }
* 2、add方法
* public boolean add(E e) {
* return map.put(e, PRESENT)==null;
* }
* 3、执行put 得到key对应的hash值
* public V put(K key, V value) {
* return putVal(hash(key), key, value, false, true);
* }
* 4、hash的过程
* static final int hash(Object key) {
* int h;
* return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
* }
* 5、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;
* // 代表了第一次扩容
* if ((tab = table) == null || (n = tab.length) == 0)
* n = (tab = resize()).length;
* // 1、根据key 得到hash 去计算该key应该存放到table表的哪个索引位置 并吧这个位置的对象 赋给P
* // 2、判断p 是否为 null
* // 2.1、如果p是空 表示这个位置还没有存元素,就创建一个node
* // 2.2、如果p不是空 就放在该位置
* if ((p = tab[i = (n - 1) & hash]) == null)
* tab[i] = newNode(hash, key, value, null);
* else {
* // 一个开发技巧提示:在需要局部变量(辅助变量)的时候,哪里需要哪里创建 不要一开始就咔咔咔创建一大推
* Node<K,V> e; K k;
* // 如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
* // 并且满足 下面两个条件之一的
* // 1、准备加入的key 和 p 指向的Node节点 的key 是同一个对象
* // 2、p指向的node节点的key的equals和准备加入的key比较后相同 (就是说怎么比较 是程序员自己决定的 就比如key类型是String,比较实际上String重写了equals方法,比较的实际上是内容而不是地址)
* // 如果key是对象 你完全可以重写里面的equals方法 编写你自己自定义的equals规则
* if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
* e = p;
* // 判断key是不是一颗红黑树
* // 如果是一颗红黑树 就调用putTreeVal方法
* else if (p instanceof TreeNode)
* e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
* // 如果table对应的索引位置,已经是一个链表,该步就是循环链表里的元素 看看要插入的这个元素是否跟链表里的元素相同的,然后就会有两种情况
* // 1、依次和链表里的每一个元素比较后,都不相同,则加到链表的屁股后面 然后break
* // 1.1、注意在把元素添加到链表后,立即判断,该链表是否已经达到了8个节点,如果达到,就调用红黑树(treeifyBin(tab, hash))树化,对当前这个链表进行树化,如果table数组长度小于64,不会树化
* // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
* resize();
* 如上代码所示,如果table长度小于64,会先对table扩容
* 如果table长度大于64,才进行转成红黑树
* // 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
* treeifyBin(tab, hash);
* break;
* }
* if (e.hash == hash &&
* ((k = e.key) == key || (key != null && key.equals(k))))
* break;
* 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;
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict);
* return null;
* }
*
*
*/
3.10.1、hashSet的扩容调试
package collection;
import java.util.HashSet;
import java.util.Objects;
public class Collection09SetIncrement {
public static void main(String[] args) {
/**
* HashSet底层是hashmap 第一次添加时,table数组扩容到16
* 临界值是 16*装载因子(0.75) = 12
* 如果table数组使用到了临界值12 就会扩容到 16 * 2 = 32
* 新的临界值就是 30*0.75=24 依次类推
*/
HashSet<Object> hashSet = new HashSet<>();
/*for (int i = 1; i <= 100; i++) {
hashSet.add(i); // 1 2 3 4 5 ... 100
}
hashSet.clear();*/
/**
* 什么时候才能达到阈值呢?
* 是看table的个数吗? 还是看所有table上的链表/红黑数的节点总数?
* 答案是后者
*/
for (int i = 1; i <= 12; i++) {
hashSet.add(new A(i));
}
System.out.println("hashSet=" + hashSet);
}
}
class A {
private int n;
public A (int n) {
this.n = n;
}
@Override
public int hashCode() {
return 100;
}
}
3.10.2、练习1
package collection;
import java.util.HashSet;
import java.util.Objects;
public class Collection10SetPractice {
/**
* 1、创建三个Employee对象放入HashSet中
* 2、当 name 和 age 的值相同时,认为是相同员工,不能添加到HashSet中
* @param args
*/
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("milan",3));
hashSet.add(new Employee("smith",3));
hashSet.add(new Employee("milan",3));
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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
3.11、LinkedHashSet
特点:有顺序!
3.12、HashMap
3.13、HashMap底层机制
扩容示意图
树化示意图
源码分析:
package collection;
import java.util.HashMap;
@SuppressWarnings({"all"})
public class Collection12HashMap {
public static void main(String[] args) {
/*HashMap map = new HashMap();
// 相同的key替换
map.put("no1","韩顺平");
map.put("no2","张无忌");
map.put("no1","张三丰");
map.put("no3","张三丰");
map.put(null,null);
map.put(null,"abc");
map.put("no4",null);
System.out.println(map); // {no2=张无忌, null=abc, no1=张三丰, no4=null, no3=张三丰}*/
HashMap map = new HashMap();
map.put("java", 10);
map.put("php", 10);
map.put("java", 20);
System.out.println(map);
// 解读hashMap源码
/**
* 1、执行构造器
* 初始化加载因子 0.75
* hashMap$Node[] table = null
* 2、执行put方法 调用hash方法计算出key的hash值
* public V put(K key, V value) {
* return putVal(hash(key), key, value, false, true);
* }
* 3、执行 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 数组为null 或者length=0 就扩容到16
* if ((tab = table) == null || (n = tab.length) == 0)
* n = (tab = resize()).length;
* // 取出hash值对应的table表的索引位置的node节点 如果为空 就直接把加入的 k-v 创建成一个node 加入到该位置
* if ((p = tab[i = (n - 1) & hash]) == null)
* tab[i] = newNode(hash, key, value, null);
* else {
* Node<K,V> e; K k;
* // 如果table的索引位置的key的hash值和新加的key的hash值相同 并满足 (table现有的节点的key和准备添加的key是同一个对象 || equals返回为真)
* if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
* e = p;
* else if (p instanceof TreeNode) // 如果当前的table的已有的node是红黑树 就按照红黑树的方式处理
* e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
* else {
* // 如果找到的节点 后面是链表 就循环的比较
* for (int binCount = 0; ; ++binCount) {
* if ((e = p.next) == null) { // 如果整个链表, 没有和他相同的 就加到该链表的最后
* p.next = newNode(hash, key, value, null);
* // 加入后 判断当前链表的个数 是否到了8个 到了8个后 就调用treeifyBin树化
* if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
* treeifyBin(tab, hash);
* break;
* }
* if (e.hash == hash &&
* ((k = e.key) == key || (key != null && key.equals(k)))) // 如果在循环比较后 发现已经有了相同的元素 就break 后面替换key value
* break;
* p = e;
* }
* }
* if (e != null) { // existing mapping for key
* V oldValue = e.value;
* if (!onlyIfAbsent || oldValue == null)
* e.value = value; // 替换key对应的value
* afterNodeAccess(e);
* return oldValue;
* }
* }
* ++modCount; // 每增加一个Node 就size++
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict);
* return null;
* }
*
*
* 5、关于树化 如果table表为空 或者大小还不够64 暂时不会树化 而是进行扩容
* 另外还有一个剪枝的概念 就是如果红黑树的节点一直删除 删除到了树化的标准后 就开始剪枝,重新转化为链表
* final void treeifyBin(Node<K,V>[] tab, int hash) {
* int n, index; Node<K,V> e;
* if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
* resize();
* else if ((e = tab[index = (n - 1) & hash]) != null) {
* TreeNode<K,V> hd = null, tl = null;
* ...
* }
*/
}
}
3.14、HashTable
介绍
与HashMap平级
3.15、Properties
是hashtable的子类,简介实现了Map接口
3.16、如何选择正确的集合或者map?
3.17、TreeSet
带排序的Set,自定义排序规则
package collection;
import java.util.Comparator;
import java.util.TreeSet;
@SuppressWarnings({"all"})
public class Collection13TreeMap {
public static void main(String[] args) {
/**
* 1、当我们使用无参构造器。创建TreeSet时,仍然是无序的
* 2、如果我现在想按照字符串大小排序
* 3、使用TreeSet提供的一个构造器 可以传入一个比较器 并指定规则
* 4、简单看源码
*
* 1、构造器会把传入的比较器对象赋给TreeSet的底层的TreeMap属性 this.comparator
* 2、在调用 treeSet.add("tom") 在底层会执行进行比较
*/
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length(); // [a, sp, tom, jack]
//return ((String)o1).compareTo((String)o2); // [a, jack, sp, tom]
}
});
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("sp");
treeSet.add("a");
System.out.println(treeSet);
}
}
3.18、TreeMap
仍然可以传比较器:
具体做法同TreeSet,比较的是键值(K)
3.19、工具类Collections