目录
第十二章 集合
学习目标
1. 集合框架的由来
2. 集合框架的继承体系
3. Collection接口
3.1 Collection接口的常用方法
4. Iterator接口
4.1 Iterator接口的抽象方法
4.2 获取迭代器接口实现类
4.3 迭代器的实现原理
4.4 并发修改异常
4.5 集合存储自定义对象并迭代
5. List接口
5.1 List接口的特点
5.2 List接口自己的方法(带有索引)
5.3 List集合的特有迭代器
5.4 List接口的实现类的数据结构
6. ArrayList实现类
6.1 ArrayList集合的特点
6.2 ArrayList源码解析
6.2.1 ArrayList类的成员变量
6.2.2 ArrayList集合类的构造方法
6.2.3 ArrayList集合类的方法add()
7. LinkedList实现类
7.1 LinkedList集合的特点
7.2 LinkedList集合特有方法
7.3 LinkedList源码解析
7.3.1 LinkedList集合的成员变量
7.3.2 LinkedList集合的成员内部类Node(结点)
7.3.4 LinkedList集合的方法add()添加元素
7.3.5 LinkedList集合的方法get()获取元素
8. Set接口
8.1 Set集合存储和遍历
8.2 Set接口实现类HashSet类
8.3 对象的哈希值
8.4 String类的哈希值
8.5 哈希值的相关问题
8.6 哈希表的数据结构
8.7 哈希表存储对象的过程
8.8 哈希表存储自定义的对象
8.9 哈希表源码
8.10 哈希表面试的问题
9. 红黑树
9.1 TreeSet集合使用
9.2 TreeSet存储自定义对象
10. LinkedHashSet
11. Collections工具类
12. 泛型 Generic
12.1 泛型的安全机制
12.2 泛型中的 E 问题
12.3 自定义泛型类
12.4 泛型方法
12.5 泛型接口
12.6 泛型通配符
12.7 泛型限定
13. 增强型的for循环
13.1 for的格式
14. Map接口
14.1 Map接口方法
14.2 Map集合的遍历——键找值
14.3 Map集合的遍历——键值对映射关系
15. HashMap 实现类
16. Hashtable 实现类
17. LinkedHashMap 实现类
18. Vector 实现类
19. TreeMap 实现类
20. Properties 实现类
认识集合框架
List集合的结构
ArrayList集合使用
ArrayList源码解析
LinkedList集合使用
LinkedList源码解析
对象的哈希值
哈希表数据结构
Collections工具类
了解泛型及其定义
哈希表确定对象唯一性
HashSet源码解析
红黑树结构Red/Black Tree Visualization
对象的自然顺序与比较器
JDK1.2版本后,出现这个集合框架,到JDK1.5后,大幅度优化。
集合本质上是存储对象的容器
数组也能存储对象,数组弊端就是定长
解决数组的问题,开发出来集合框架,集合框架无需考虑长度
集合和数组的区别与共同点
集合,数组都是容器,都可以存储数据
集合只存储引用数据类型,不存储基本数据类型(如int,就会换成包装类Integer)
数组可以存储基本类型,也可以存储引用类型
数组定长,集合容器变长
牢记:数据多了存数组,对象多了存集合
集合学习的关键点
怎么存储数据
怎么取出数据
选择哪种容器
是所有单列集合的顶级接口,任何单列集合都是他的子接口,或者是实现类,该接口中定义的方法,是所有单列集合的共性方法。
Collection
方法的定义 | 方法作用 |
---|---|
boolean add(E) | 元素添加到集合 |
void clear() | 清空集合容器中的元素 |
boolean contains(E) | 判断元素是否在集合中 |
boolean isEmpty() | 判断集合的长度是不是0,是0返回true |
int size() | 返回集合的长度,集合中元素的个数 |
boolean remove(E) | 移除集合中指定的元素,移除成功返回true |
T[] toArray(T[] a) | 集合转成数组 |
(1) boolean add(E)
/**
* boolean add(E) 元素添加到集合中
* 返回值,目前都是true
*/
public static void collectionAdd(){
//接口多态创建集合容器对象,存储的数据类型是字符串
Collection coll = new ArrayList<>();
//集合对象的方法add添加元素
coll.add("hello");
coll.add("world");
coll.add("java");
coll.add("money");
coll.add("wife");
/**
* 输出语句中,输出集合对象,调用的是方法toString()
* 看到的内容是一个完整的字符串, 不叫遍历
* 遍历时一个个过,一个个输出
*/
System.out.println(coll);
}
(2)void clear(),int size(),boolean isEmpty()
/**
* void clear() 清空集合中的所有元素
* int size() 集合的长度
*/
public static void collectionClear(){
Collection coll = new ArrayList<>();
coll.add(1);
coll.add(2);
coll.add(3);
System.out.println(coll);
System.out.println("集合的长度::"+ coll.size());//长度
coll.clear();
System.out.println(coll);
System.out.println("集合的长度::"+ coll.size());
System.out.println("集合是空吗?" + coll.isEmpty());//长度=0,isEmpty()返回true
}
(3)boolean contains(),boolean remove()
/**
* boolean contains(E) 判断是否包含
* boolean remove(E) 移除元素
*/
public static void collectionContains(){
//接口多态创建集合容器对象,存储的数据类型是字符串
Collection coll = new ArrayList<>();
//集合对象的方法add添加元素
coll.add("hello");
coll.add("wife");
coll.add("world");
coll.add("java");
coll.add("money");
coll.add("wife");
//判断集合中是否包含某个元素
boolean b = coll.contains("world");
System.out.println("b = " + b);
//移除集合中的元素
//删除成功返回true,如果有多个相同的对象,删除最先遇到的那个
boolean b1 = coll.remove("wife");
System.out.println("b1 = " + b1);
System.out.println(coll);
}
迭代器接口 Iterator,为集合进行遍历的。迭代器技术是所有Collection集合的通用遍历形式。
boolean hasNext() 判断集合中是否有下一个可以遍历的元素,如果有返回true
E next() 获取集合中下一个元素
void remove() 移除遍历到的元素
迭代器就是为了遍历集合而产生。集合的顶层接口Collection中定义了方法:方法的名字就是 iterator(),返回值是Iterator接口类型, 返回的是Iterator接口实现类的对象
Collection接口中的方法摘要 :
public Iterator iterator() ; 返回迭代器接口实现类的对象
使用的对象ArrayList,实现接口Collection,重写方法iterator();
public static void main(String[] args) {
//迭代器遍历集合
//接口多态创建集合容器对象,存储的数据类型是字符串
Collection coll = new ArrayList<>();
//集合对象的方法add添加元素
coll.add("hello");
coll.add("world");
coll.add("java");
coll.add("money");
coll.add("wife");
//1 遍历 集合对象,调用方法iterator() 获取迭代器接口的实现类对象
Iterator it = coll.iterator();
//2 迭代器对象的方法,判断集合是否有下元素
//boolean b = it.hasNext();
//System.out.println(b);
//3 迭代器对象的方法,取出元素
//String str = it.next();
//System.out.println(str);
//条件,集合中有下一个元素就可以
while ( it.hasNext() ){
String str = it.next();
System.out.println(str);
}
}
每个集合容器,内部结构不同,但是迭代器都可以进行统一的遍历实现
结论:迭代器是隐藏在集合的内部的(即为成员内部类),提供公共的访问方式。
//Iterator接口的源码和ArrayList中的iterator的源码
interface Iterator{
boolean hasNext();
E next();
void remove();
}
public class ArrayList {
public Iterator iterator(){
return new Itr();
}
private class Itr implements Iterator{
boolean hasNext(); //重写
E next(); //重写
void remove(); //重写
}
}
图1:ArrayList的内部类实现接口Iterator的源码图片
如何不发生这个异常
异常的产生原因:在迭代器遍历集合的过程中,使用了集合的功能,改变了集合的长度造成。
public static void main(String[] args) {
//迭代器遍历集合
//接口多态创建集合容器对象,存储的数据类型是字符串
Collection coll = new ArrayList<>();
//集合对象的方法add添加元素
coll.add("hello");
coll.add("world");
coll.add("java");
coll.add("money");
coll.add("wife");
//迭代器遍历集合
Iterator it = coll.iterator();
while ( it.hasNext() ){
String str = it.next();
//判断,遍历到的集合元素是不是java
if (str.equals("java")){
//添加元素 出现并发修改异常
coll.add("add");
}
System.out.println(str);
}
}
public static void main(String[] args) {
//创建集合,存储自定义的对象
Collection coll = new ArrayList<>();
//集合的方法add存储Person对象
coll.add( new Person("张三",21) );
coll.add( new Person("李四",22) );
coll.add( new Person("王五",23) );
//迭代器遍历集合
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Person person = iterator.next();
System.out.println(person);
System.out.println(person.getName());
}
}
/**
* 定义私有成员
* get set方法
* 无参数构造方法
*
* 满足以上的三个条件 ,这个类,换一个名字,叫JavaBean
*/
public class Person {
private String name;
private int age;
public Person(){}
public Person(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 String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
List接口,继承Collection接口,是单列集合。
这个接口的集合都具有索引
这个接口中的元素允许重复
这个接口中的元素是有序的
元素不会排序 ,有序指的是 ,元素存储和取出的顺序是一致的
List接口的所有实现类,都具有以上三个特征
(1)void add(int index ,E e);
/**
* List接口的方法 add(int index, E e)
* 指定的索引位置,添加元素
*
* IndexOutOfBoundsException 集合越界异常 长度是size()
* StringIndexOutOfBoundsException 字符串越界异常 长度是 length()
* ArrayIndexOutOfBoundsException 数组越界异常 长度是 length
*/
public static void listAdd(){
List list = new ArrayList<>();
list.add("a") ;//集合的尾部添加
list.add("b");
list.add("c");
list.add("d");
list.add("e");
System.out.println(list);
//指定的索引上,添加元素 ,3索引添加元素
list.add(3,"QQ");
System.out.println(list);
}
(2)E get(int index);
/**
* List接口的方法 E get(int index)
* 返回指定索引上的元素
* List集合可以使用for循环像数组一样的方式遍历
*/
public static void listGet(){
List list = new ArrayList<>();
list.add("a") ;//集合的尾部添加
list.add("b");
list.add("c");
list.add("d");
list.add("e");
//List接口方法get取出元素
//String s = list.get(3);
//System.out.println(s);
for(int i = 0 ; i < list.size() ; i++){
System.out.println(list.get(i));
}
}
(3)E
set(int index,E e); E remove(int index);
/**
* List接口方法
* E set (int index , E e) 修改指定索引上的元素,返回被修改之前的元素
* E remove(int index) 移除指定索引上的元素, 返回被移除之前的元素
*/
public static void listSetRemove(){
List list = new ArrayList<>();
list.add("a") ;//集合的尾部添加
list.add("b");
list.add("c");
list.add("d");
list.add("e");
System.out.println(list);
//修改指定索引上的元素,3索引
String str = list.set(3,"https://www.baidu.com");
System.out.println(list);
System.out.println(str);
//删除指定索引上的元素,删除3索引
str = list.remove(3);
System.out.println(list);
System.out.println(str);
}
List接口中的方法 listIterator() 返回迭代器,迭代器的接口是ListIterator,List集合的专用迭代器。
ListIterator迭代器接口的方法
boolean hasNext()
E next()
boolean hasPrevious() 判断集合中是否有上一个元素,反向遍历
E previous() 取出集合的上一个元素
/**
* List接口的方法:
* listIterator() List集合的特有迭代器
* 反向遍历
*/
public static void iterator(){
List list = new ArrayList<>();
list.add("a") ;//集合的尾部添加
list.add("b");
list.add("c");
list.add("d");
list.add("e");
//获取特有迭代器接口实现类对象
ListIterator lit = list.listIterator();
//先要正向遍历
while (lit.hasNext()){
String s = lit.next();
System.out.println(s);
}
System.out.println("=============");
//判断上一个元素
while (lit.hasPrevious()){
//取出元素
String s = lit.previous();
System.out.println(s);
}
}
LinkedList(链表)
数组 :
有索引,数组中元素的地址是连续,查询速度快
数组的长度为固定,新数组创建,数组元素的复制,增删的效率慢
链表
链表没有索引,采用对象之间内存地址记录的方式存储
查询元素,必须通过第一个节点依次查询,查询性能慢
增删元素,不会改变原有链表的结构,速度比较快
ArrayList类实现接口ListArrayList具备了List接口的特性 (有序,重复,索引)
ArrayList集合底层的实现原理是数组,大小可变 (存储对象的时候长度无需考虑)。
数组的特点:查询速度快,增删慢。
数组的默认长度是10个,每次的扩容是原来长度的1.5倍。
ArrayList是线程不安全的集合,运行速度快。
private static final int DEFAULT_CAPACITY = 10; //默认容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空数组
transient Object[] elementData; //ArrayList集合中的核心数组
private int size; //记录数组中存储个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //数组扩容的最大值
//无参数构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //数组没有长度
//有参数的构造方法
public ArrayList(int 10) {
if (initialCapacity > 0) {
//创建了10个长度的数组
this.elementData = new Object[10];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
new ArrayList<>().add("abc"); //集合中添加元素
public boolean add("abc") {
//检查容量 (1)
ensureCapacityInternal(size + 1);
//abc存储到数组中,存储数组0索引,size计数器++
elementData[size++] = "abc";//数组扩容为10
return true;
}
//检查集合中数组的容量, 参数是1
private void ensureCapacityInternal(int minCapacity = 1) {
//calculateCapacity 计算容量,方法的参是数组 , 1
// ensureExplicitCapacity (10) 扩容的
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算容量方法, 返回10
private static int calculateCapacity(Object[] elementData, int minCapacity = 1) {
//存储元素的数组 == 默认的空的数组 构造方法中有赋值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回最大值 max(10,1)
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//扩容
private void ensureExplicitCapacity(int minCapacity = 10) {
modCount++;
// 10 - 数组的长度0 > 0
if (minCapacity - elementData.length > 0)
//grow方法(10) 数组增长的
grow(minCapacity);
}
//增长的方法,参数是(10)
private void grow(int minCapacity = 10) {
//变量oldCapacity保存,原有数组的长度 = 0
int oldCapacity = elementData.length; // 0
//新的容量 = 老 + (老的 / 2)
int newCapacity = oldCapacity + (oldCapacity >> 1);// 0
// 0 - 10 < 0 新容量-计算出的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; //新容量 = 10
//判断是否超过最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//数组的赋值,原始数组,和新的容量
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList类实现接口List,LinkedList具备了List接口的特性 (有序,重复,索引)
LinkedList底层实现原理是链表,双向链表
LinkedList增删速度快
LinkedList查询慢
LinkedList是线程不安全的集合,运行速度快
集合是链表实现,可以单独操作链表的开头元素和结尾元素
void addFirst(E e) 元素插入到链表开头
void addLast(E e) 元素插入到链表结尾
E getFirst() 获取链表开头的元素
E getLast() 获取链表结尾的元素
E removeFirst() 移除链表开头的元素
E removeLast() 移除链表结尾的元素
void push(E e)元素推入堆栈中
E pop()元素从堆栈中弹出
public static void main(String[] args) {
linkedPushPop();
}
//- void push(E e)元素推入堆栈中
//- E pop()元素从堆栈中弹出
public static void linkedPushPop(){
LinkedList linkedList = new LinkedList();
//元素推入堆栈中
linkedList.push("a"); //本质就是addFirst() 开头添加
linkedList.push("b");
linkedList.push("c");
System.out.println("linkedList = " + linkedList);
String pop = linkedList.pop(); // removeFirst()移除开头
System.out.println(pop);
System.out.println("linkedList = " + linkedList);
}
//- E removeFirst() 移除链表开头的元素
//- E removeLast() 移除链表结尾的元素
public static void linkedRemove(){
LinkedList linkedList = new LinkedList();
linkedList.add("a"); //结尾添加
linkedList.add("b"); //结尾添加
linkedList.add("c"); //结尾添加
linkedList.add("d"); //结尾添加
System.out.println("linkedList = " + linkedList);
//移除开头元素,返回被移除之前
String first = linkedList.removeFirst();
//移除结尾元素,返回被移除之前的
String last = linkedList.removeLast();
System.out.println("first = " + first);
System.out.println("last = " + last);
System.out.println("linkedList = " + linkedList);
}
//- E getFirst() 获取链表开头的元素
//- E getLast() 获取链表结尾的元素
public static void linkedGet(){
LinkedList linkedList = new LinkedList();
linkedList.add("a"); //结尾添加
linkedList.add("b"); //结尾添加
linkedList.add("c"); //结尾添加
linkedList.add("d"); //结尾添加
System.out.println("linkedList = " + linkedList);
//获取开头元素
String first = linkedList.getFirst();
//获取结尾元素
String last = linkedList.getLast();
System.out.println("first = " + first);
System.out.println("last = " + last);
System.out.println("linkedList = " + linkedList);
}
// void addFirst(E e) 元素插入到链表开头
// void addLast(E e) 元素插入到链表结尾
public static void linkedAdd(){
LinkedList linkedList = new LinkedList();
linkedList.add("a"); //结尾添加
linkedList.add("b"); //结尾添加
linkedList.add("c"); //结尾添加
linkedList.add("d"); //结尾添加
System.out.println("linkedList = " + linkedList);
//结尾添加
linkedList.addLast("f");
linkedList.add("g");
//开头添加
linkedList.addFirst("e");
System.out.println("linkedList = " + linkedList);
}
transient int size = 0; //集合中存储元素个数计数器
transient Node first; //第一个元素是谁
transient Node last; //最后一个元素是谁
//链表中,每个结点对象
private static class Node {
E item; //我们存储的元素
Node next; // 下一个结点对象
Node prev; // 上一个结点对象
//构造方法,创建对象,传递上一个,下一个,存储的元素
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
//添加元素 e 存储元素 abc
//再次添加元素 e
void linkLast(E "abc") {
//声明新的节点对象 = last
final Node l = last; // l = null l "abc"节点
//创建新的节点对象,三个参数, 最后一个对象,"abc", 上一个对象null
final Node newNode = new Node<>(l, e, null);
//新节点赋值给最后一个节点
last = newNode;
if (l == null)
//新存储的几点赋值给第一个节点
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//集合的获取的方法
//index是索引, size 长度计数器
Node node(int index) {
//索引是否小于长度的一半,折半思想
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
Set集合,是接口Set,继承Collection接口。Set集合不存储重复元素。
Set集合存储的数据是无序、不重复、无索引。
无序指的是存储和取出的顺序不一样,但是由于使用迭代器遍历,Set集合的输出会根据自然顺序输出,例如“a”自然排序在“b”之前,所以先输出“a”。
Set接口下的所有实现类,都会具有这个特性。
Set接口的方法,和父接口Collection中的方法完全一样。
public static void main(String[] args) {
//Set集合存储并迭代
Set set = new HashSet();
//存储元素方法 add
set.add("b");
set.add("a");
set.add("c");
set.add("d");
set.add("d");
System.out.println("set = " + set);
Iterator it = set.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
HashSet集合类的特点 :
实现Set接口,底层调用的是HashMap集合
HashSet的底层实现原理是哈希表
HashSet不保证迭代顺序,元素存储和取出的顺序不一定
线程不安全,运行速度快
图二:创建HashSet其实就是创建HashMap
每个类继承Object类,Object类定义方法:
public native int hashCode(); // C++语言编写,不开源
方法使用没有区别:方法返回int类型的值,就称为哈希值
哈希值的结果不知道是怎么计算的,调用toString()方法的时候,返回的十六进制数和哈希值是一样的,@1b6d3586叫哈希值 (根本和内存地址是无关的)
public static void main(String[] args) {
Person p = new Person();
int code = p.hashCode();
// int 变量 460141958 (是什么,无所谓, 数字就是对象的哈希值)
System.out.println(code);
// com.atguigu.hash.Person@1b6d3586
System.out.println(p.toString());
}
可以重写父类的hashCode()方法,再输出该哈希值或toString()方法
/**
* 重写父类的方法
* 返回int值
*/
public int hashCode(){
return 9527;
}
字符串类重写方法hashCode(),自定义了哈希值,哈希值的计算方法是:
h = 31 * 上一次的计算结果 + 字符数组中元素的ASCII码值
*31 的目的,减少相同哈希值的计算
String类的哈希值:
//字符串String对象的哈希值
private static void stringHash(){
String s1 ="abc";
String s2 ="abc";
System.out.println(s1 == s2); //T
//String类继承Object,可以使用方法hashCode
System.out.println(s1.hashCode() == s2.hashCode()); //T
/**
* String类继承Object类
* String类重写父类的方法 hashCode() 自己定义了哈希值
*/
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println("=============");
/**
* 字符串内容不一样,有没有可能计算出相同的哈希值
* String s1 ="abc";
* String s2 ="abc";
*/
String s3 = "通话";
String s4 = "重地";
//1179395
//1179395
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
System.out.println(s3.equals(s4));
}
问题 : ① 两个对象A、B 两个对象哈希值相同,equals方法一定返回true吗?
② 两个对象A、B 两个对象equals方法返回true,两个对象的哈希值一定相同吗?
结论 : 两个对象的哈希值相同,不要求equals一定返回true. 两个对象的equals返回true,两个对象的哈希值必须一致
Sun 公司官方规定 : 上面的结论
数组 + 链表的组合体
class Node{
E element; //存储的元素
Node next; //下一个元素
}
main(){
Node[] node = new Node[5];
}
哈希表的底层数组长度默认是16个,扩容为原来长度的2倍
加载因子默认是0.75F,数组中存储元素的个数达到长度的75%就扩容
public static void main(String[] args) {
Set set = new HashSet();
//存储对象
set.add("abc");
set.add("bbc");
set.add(new String("abc"));
set.add("通话");
set.add("重地");
System.out.println("set = " + set);
}
需要重写自定义类的hashCode()和equal()两个方法
public class Student {
private int age;
private String name;
public Student(){}
public Student( String name,int age) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = age;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
Set set = new HashSet();
//存储Student的对象
set.add(new Student("a1",201));
set.add(new Student("a2",202));
set.add(new Student("a2",202));
set.add(new Student("a3",203));
set.add(new Student("a4",204));
System.out.println("set = " + set);
}
HashSet集合本身不具备任何功能,内部调用了另一个集合对象HashMap
public HashSet() {
map = new HashMap<>();
}
//哈希表数组的初始化容量,16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//价值因子
static final int TREEIFY_THRESHOLD = 8;//阈值,转红黑树
static final int UNTREEIFY_THRESHOLD = 6;//阈值,解除红黑树
static final int MIN_TREEIFY_CAPACITY = 64;//阈值,转红黑树
//节点
static class Node implements Map.Entry {
final int hash; //对象哈希值
final K key; //存储的对象
V value; //使用Set的集合,value没有值
Node next; //链表的下一个节点
}
Set集合存储方法add(),调用的是HashMap集合的方法put()【大致了解就行】
//HashMap存储对象的方法put,Key存储的元素,V是空的对象
public V put(K key, V value) {
//存储值,传递新计算哈希值,要存储的元素
return putVal(hash(key), key, value, false, true);
}
//传递存储的对象,再次计算哈希值
//尽量降低哈希值的碰撞
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//存储值,重写计算的哈希值,要存储值
final V putVal(int hash, K key, V value, boolean false,
boolean true) {
//Node类型数组, Node类型数组 n, i
Node[] tab; Node p; int n, i;
//tab =Node[]=null
if ((tab = table) == null || (n = tab.length) == 0){
//n=赋值为 tab数组=resize()方法返回数组,默认长度的数组16
n = (tab = resize()).length;// 16
//数组的长度-1 & 存储对象的哈希值,确定存储的位置
//判断数组的索引上是不是空的
if ((p = tab[i = (n - 1) & hash]) == null)
//数组索引 赋值新的节点对象,传递计算的哈希值,存储的对象
tab[i] = newNode(hash, key, value, null);
else{
//数组的索引不是空,要存储的对象,已经有了
//判断已经存在的对象,和要存储对象的哈希值和equals方法
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//遍历该索引下的链表,和每个元素比较hashCode和equals
}
}
}
JDK7版本和JDK8版本的哈希表的区别
JDK7没有转红黑树
JDK8转成红黑树
转成树的两个参数
当一个数组中存储的链表长度>=8 转树
数组的整体长度超过64
树转回链表
链表的长度 <=6
JDK7元素采用头插法,JDK8元素采用尾插法
红黑树(Red-Black-Tree)
二叉树,本质就是链表
查询速度快
每个一个节点,只有两个子节点,左和右
树长偏了
自然平衡二叉树
二叉树的基础上,改进,保证树是平衡的
红黑树
每个节点有颜色,要么红,要么是黑
根节点必须是黑色
叶子节点必须是黑色
变量表示颜色,true黑色,false红色
TreeSet集合,底层是红黑树结构,依赖于TreeMap的实现
红黑树特点查找速度快,线程不安全
可以对存储到红黑树的元素进行排序,元素的自然顺序 abcd.. 字典顺序
public static void treeSetString(){
Set set = new TreeSet<>();
//存储元素
set.add("abcd");
set.add("ccdd");
set.add("z");
set.add("wasd");
set.add("bbaa");
System.out.println("set = " + set);
}
/**
* TreeSet集合存储Student对象
*/
public static void treeSetStudent(){
Set set = new TreeSet();
set.add(new Student("a",10));
set.add(new Student("b",20));
System.out.println("set = " + set);
}
程序出现了异常,类型的转换异常 ClassCastException
异常原因,Student类不能进行类型的转换,有接口没有实现java.lang.Comparable.
类实现接口Comparable,这个类就具有了自然顺序。
Student类具有自然顺序
实现接口Comparable,重写方法compareTo
/**
* 重写方法compareTo
* 返回int类型
* 参数 : 要参与比较的对象
* this对象和student对象
*
* 红黑树,后来的对象是this,原有的对象是参数
*/
public int compareTo(Student student){
return this.age - student.age;
}
自定义比较器
java.util.Comparator接口
/**
* 自定义的比较器
* 实现接口,重写方法
*/
public class MyCom implements Comparator {
@Override
/**
* TreeSet集合自己调用方法
* 传递参数
* Student o1, Student o2
* o1是后来的对象
* o2是已经有的对象
*/
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
}
Set set = new TreeSet( new MyCom());
图三:TreeSet集合的构造器
底层的数据结构是哈希表,继承HashSet
LinkedHashSet数据是双向链表有序的集合,存储和取出的顺序一样
public static void main(String[] args) {
Set set = new LinkedHashSet<>();
set.add("b");
set.add("e");
set.add("c");
set.add("a");
set.add("d");
System.out.println("set = " + set);
}
java.util.Collection 集合的顶级接口
java.util.Collections 操作集合的工具类
工具类的方法全部静态方法,类名直接调用
主要是操作Collection系列的单列集合,少部分功能可以操作Map集合
/**
* 集合操作的工具类
* Collections
* 工具类有组方法: synchronized开头的
*
* 传递集合,返回集合
* 传递的集合,返回后,变成了线程安全的集合
*/
public class CollectionsTest {
public static void main(String[] args) {
sort2();
}
//集合元素的排序,逆序
public static void sort2(){
List list = new ArrayList();
list.add(1);
list.add(15);
list.add(5);
list.add(20);
list.add(9);
list.add(25);
System.out.println("list = " + list);
//Collections.reverseOrder() 逆转自然顺序
Collections.sort(list,Collections.reverseOrder());
System.out.println("list = " + list);
}
//集合元素的排序
public static void sort(){
List list = new ArrayList();
list.add(1);
list.add(15);
list.add(5);
list.add(20);
list.add(9);
list.add(25);
System.out.println("list = " + list);
Collections.sort(list);
System.out.println("list = " + list);
}
//集合元素的随机交换位置
public static void shuffle(){
List list = new ArrayList();
list.add(1);
list.add(15);
list.add(5);
list.add(20);
list.add(9);
list.add(25);
System.out.println("list = " + list);
Collections.shuffle(list);
System.out.println("list = " + list);
}
//集合的二分查找
public static void binarySearch(){
List list = new ArrayList();
list.add(1);
list.add(5);
list.add(9);
list.add(15);
list.add(20);
list.add(25);
int index = Collections.binarySearch(list, 15);
System.out.println(index);
}
}
泛型技术是JDK版本一大升级,源自于JDK1.5
泛型就是集合类<泛型>
//无泛型写法
public static void main(String[] args) {
/**
* JDK没有泛型技术,就是这样写
* 集合可以存储任何数据类型
* 添加元素的数据类型是Object
*/
List list = new ArrayList();
list.add("a");
list.add(1);
Iterator it = list.iterator();
while (it.hasNext()){
Object obj = it.next();//不能类型转换
System.out.println(obj);
}
}
软件升级:安全性提高,修复Bug错误,改善用户体验,增加功能,提升性能
JDK1.5里程碑版本
泛型作用:强制了集合存储固定的数据类型
泛型的书写格式:
集合类<存储的数据类型> 变量名 = new 集合类<存储的数据类型>();
类型可以不写:钻石操作符
加入泛型后,程序的安全性提升了
public static void main(String[] args) {
/**
* JDK没有泛型技术,就是这样写
* 集合可以存储任何数据类型
* 添加元素的数据类型是Object
*/
List list = new ArrayList();
list.add("a");
list.add(1); //编译错误,数据类型不匹配
Iterator it = list.iterator();
while (it.hasNext()){
String obj =it.next(); //类型转换不需要
System.out.println(obj);
}
}
使用泛型的好处:
安全性提高了
程序的代码量减少
避免了类型的强制转换
程序的问题,由运行时期,提前到编译时期
E没有什么实际价值,只是一个变量而已
特殊:等待接收指定的数据类型
//ArrayList
//创建对象
ArrayList al = new ArrayList();
//E 不在是E了,变成String
public boolean add(String e) {
}
/**
* 定义类,类名叫工厂
* 自定义泛型类
* Factory<什么都可以写> 只是变量名而已
*/
public class Factory {
private QQ q;
public void setQ(QQ q){
this.q = q;
}
public QQ getQ(){
return q;
}
}
public static void main(String[] args) {
//创建对象Factory类对象
// Factory factory = new Factory();//没有泛型,QQ就是Object
Factory factory = new Factory();
factory.setQ("abc");
String s = factory.getQ();
System.out.println(s);
Factory factory2 = new Factory();
factory2.setQ(1.5);
Double q = factory2.getQ();
System.out.println(q);
}
/**
* 泛型的方法,方法参数上
*/
public class Factory {
/*
* 静态方法
* Q是非静态的, Q的数据类型,是new的时候指定的
*
* 静态方法参数中的泛型,不能和类一样
* 静态方法的泛型,需要在方法上单独定义
* 写在返回值类型的前面
*/
public static void staticMethod(T q){
System.out.println(q);
}
public void print(Q q){
System.out.println(q);
}
}
实现类实现接口,不实现泛型
实现类实现接口,同时指定泛型
//泛型接口
public interface Inter {
public abstract void inter(T t);
}
/**
* 实现接口,不理会泛型
* 对象创建的时候,指定类型
*/
public class InterImpl implements Inter{
public void inter(T t){
System.out.println(t);
}
}
/**
* 实现接口,同时指定泛型
*/
public class InterImpl2 implements Inter {
public void inter(String s) {
System.out.println("s=="+s);
}
}
public class GenericTest {
public static void main(String[] args) {
Inter in = new InterImpl();
in.inter("ok");
Inter in2 = new InterImpl2();
in2.inter("kkk");
}
}
实现接口,但是不实现泛型,会在调用方法传入参数时才确认数据类型,即参数类型会变成Object。
?指可以是任意引用类型
//泛型的通配符
public class GenericTest {
public static void main(String[] args) {
List stringList = new ArrayList();
stringList.add("abc");
stringList.add("bbc");
List integerList = new ArrayList();
integerList.add(1);
integerList.add(2);
each(stringList);
each(integerList);
}
/**
* 定义方法,可以同时迭代器 遍历这两个集合
* 方法的参数,是要遍历的集合,不确定是哪个集合
* 定义参数,写接口类型,不要写实现类
*/
public static void each(List> list){
Iterator> it = list.iterator();
while (it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
}
泛型限定:限制的是数据类型
extends Company> 传递类型可以是Company或者是他的子类
extends E>传递E类型或者是E的子类,泛型上限限定
super E >传递E类型或者是E的父类,泛型下限限定
public static void main(String[] args) {
//创建集合,存储员工对象
//开发部的
List devList = new ArrayList();
//存储开发部员工对象
Development d1 = new Development();
d1.setName("张三");
d1.setId("开发部001");
Development d2 = new Development();
d2.setName("张三2");
d2.setId("开发部002");
devList.add(d1);
devList.add(d2);
//财务部集合
List finList = new ArrayList();
Financial f1 = new Financial();
f1.setName("李四");
f1.setId("财务部001");
Financial f2 = new Financial();
f2.setName("李四2");
f2.setId("财务部002");
finList.add(f1);
finList.add(f2);
System.out.println(devList);
System.out.println(finList);
each(devList);
each(finList);
// List integerList = new ArrayList<>();
// integerList.add(1);
// each(integerList);
}
/**
* 要求 : 定义方法
* 同时遍历2个集合
* 遍历的同时取出集合元素,调用方法work()
* ? 接收任何一个类型
* 只能接收 Company和子类对象
* 明确父类,不能明确子类
*/
public static void each(List extends Company> list){
Iterator extends Company> it = list.iterator();
while (it.hasNext()){
//取出元素
Company obj =it.next();
obj.work();
}
}
JDK1.5出现的特性:循环的特性 (少些代码)
Collection是单列集合的顶级接口,但是到JDK1.5后,为Collection找了个爹
java.lang.Iterable接口:实现接口,就可以成为 "foreach"语句的目标
Collection、List、Set都继承了接口,包括数组
Iterable接口定义了foreach()和iterator()两个方法
for(数据类型 变量名 : 集合或者数组){}
遍历数组
/**
* for循环遍历数组
* for(数据类型 变量名 : 集合或者数组){}
*/
public static void forArray(){
int[] arr = {1,3,5,7,9};
for(int i : arr){
System.out.println(i+1);
}
System.out.println("arr=="+arr[0]);
}
遍历集合
/* for循环遍历集合
*/
public static void forList(){
List list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
for(String s : list){
System.out.println(s);
}
}
java.util.Map接口,是双列集合的顶级接口。
Map集合容器每次存储2个对象,一个对象称为键(Key),一个对象称为值(Value)。
在一个Map的集合容器中,键保证唯一性,不包含重复键,每个键只能对应一个值
V put(K,V)存储键值对,存储重复键,返回被覆盖之前的值
/**
* put方法,存储键值对
* Map接口的实现类HashMap
*/
public static void mapPut(){
//创建对象,指定键的数据类型,值的数据
Map map = new HashMap();
map.put("a",1);
map.put("b",2);
map.put("c",3);
map.put("d",4);
//相同键,返回被覆盖的值
Integer value = map.put("c",5);
System.out.println("map = " + map);
System.out.println("value = " + value);
}
V get(K)通过键获取值,参数传递键,找这个键对应的值,没有这个键返回null
/**
* V get(K)通过键获取值,参数传递键,找这个键对应的值,没有这个键返回null
*/
public static void mapGet(){
//创建对象,指定键的数据类型,值的数据
Map map = new HashMap();
map.put("a",1);
map.put("b",2);
map.put("c",3);
map.put("d",4);
//键找值
Integer value = map.get("f");
System.out.println(value);
}
boolean containsKey(K)判断集合是否包含这个键,包含返回true
boolean containsValue(V)判断集合是否包含这个值,包含返回true
int size() 返回集合长度,Map集合中键值对的个数
V remove(K)移除指定的键值对,返回被移除之前的值
Collection
/*boolean containsKey(K)判断集合是否包含这个键,包含返回true
- boolean containsValue(V)判断集合是否包含这个值,包含返回true
- int size() 返回集合长度,Map集合中键值对的个数
- V remove(K)移除指定的键值对,返回被移除之前的值
- Collection values() Map集合中的所有的值拿出,存储到Collection集合
*/
public static void mapMethod(){
//创建集合,键是整数,值是String
Map map = new HashMap();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c");
map.put(4,"d");
map.put(5,"e");
//boolean containsKey(K)判断集合是否包含这个键,包含返回true
boolean b = map.containsKey(1);
System.out.println("集合中包含键:"+b);
//boolean containsValue(V)判断集合是否包含这个值,包含返回true
b = map.containsValue("c");
System.out.println("集合中包含值:"+b);
//size()返回集合的长度
int size = map.size();
System.out.println("集合长度:"+size);
//V remove(K)移除指定的键值对,返回被移除之前的值
String value = map.remove(1);
System.out.println("被删除之前的:"+value);
System.out.println(map);
//Collection values() Map集合中的所有的值拿出,存储到Collection集合
Collection coll = map.values();
for(String s : coll){
System.out.println(s);
}
}
实现思想 :
Map接口定义了方法 keySet() 所有的键,存储到Set集合
遍历Set集合
取出Set集合元素 Set集合的元素是Map集合的键
Map集合方法get()传递键获取值
/**
* - Map接口定义了方法 keySet() 所有的键,存储到Set集合
* - 遍历Set集合
* - 取出Set集合元素 **Set集合的元素是Map集合的键**
* - Map集合方法get()传递键获取值
*/
public static void mapKeySet(){
Map map = new HashMap();
map.put("a","java");
map.put("b","c++");
map.put("c","php");
map.put("d","python");
map.put("e","erlang");
//Map接口定义了方法 keySet() 所有的键,存储到Set集合
Set set = map.keySet();
//遍历Set集合
Iterator it = set.iterator();
//取出Set集合元素 **Set集合的元素是Map集合的键**
while (it.hasNext()){
String key = it.next();
//Map集合方法get()传递键获取值
String value = map.get(key);
System.out.println(key+"==="+value);
}
}
实现思想 :
Map接口的方法 Set< Map.Entry
方法返回Set集合,集合中存储的元素,比较特别
存储的是Map集合中,键值对映射关系的对象,内部接口 Map.Entry
遍历Set集合
取出Set集合的元素
是Map.Entry接口对象
接口的对象方法:getKey(),getValue()
public static void mapEntrySet(){
Map map = new HashMap();
map.put("a","java");
map.put("b","c++");
map.put("c","php");
map.put("d","python");
map.put("e","erlang");
//Map接口的方法 Set< Map.Entry > entrySet()
Set> set = map.entrySet();
//- 遍历Set集合
Iterator> it = set.iterator();
while (it.hasNext()){
//取出Set集合的元素
Map.Entry entry = it.next();
//- 接口的对象方法: getKey() ,getValue()
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key +"==="+ value);
}
}
HashMap集合特点
是哈希表结构【Set和Map的底层数据结构都是是哈希表】
保证键唯一性,用于键的对象,必须重写hashCode,equals方法【伞公司写的hashCode方法不对外开放的,所以不知道写了什么。但是能确定的一点是,只要是桐哥类建立的对象,他们的哈希值都是一样的。所以保证键唯一性需要重写hashCode,equals方法】
线程不安全集合,运行速度快
集合运行使用null,作为键或者值
/**
* HashMap集合
* 键是Person,值是String
*/
public static void hashMap2(){
Map map = new HashMap();
map.put(new Person("a",20),"广东");
map.put(new Person("b",22),"香港");
map.put(new Person("b",22),"贵港");
map.put(new Person("c",24),"澳门");
map.put(new Person("d",26),"深圳");
System.out.println("map = " + map);
}
/**
* HashMap集合
* 键是字符串,值是Person
*/
public static void hashMap1(){
Map map = new HashMap();
map.put("a",new Person("张三",20));
map.put("b",new Person("张三",20));
map.put("c",new Person("张三",20));
map.put(null,null);
//Set set = map.keySet();
for(String key : map.keySet()){
//Person person = map.get(key);
System.out.println(key+"==="+map.get(key));
}
System.out.println("==============");
//Set> set = map.entrySet();
for(Map.Entry entry : map.entrySet()){
System.out.println(entry.getKey()+"==="+entry.getValue());
}
Map接口的实现类Hashtable,Hashtable类诞生于JDK1.0版本,Map接口诞生于JDK1.2版本。 Hashtable类从JDK1.2开始,改进为实现Map接口。
Hashtable类的特点
底层数据结构是哈希表
线程安全的,运行速度慢,被更加先进的HashMap取代
不允许null值,null键,存储null直接抛出空指针异常。
LinkedHashMap继承HashMap实现Map接口,LinkedHashMap底层实现原理是哈希表,双向链,存取有序。其它的特性和父类HashMap一样。
public static void main(String[] args) {
Map map = new LinkedHashMap();
map.put("aa","qq");
map.put("123","qq");
map.put("bbb","qq");
System.out.println(map);
}
List接口的实现Vector,命运和Hashtable一样。
Vector类的特点
底层实现结构是数组
数组的默认容量是10,每次扩容是原来的长度*2
线程安全,运行速度慢,被ArrayList取代
TreeMap集合的特点
底层实现是红黑树结构 (添加查询速度比较快)
存储到TreeMap中元素,对键进行排序
排序依据 :
对象的自然顺序,作为键的对象,实现了接口Comparable
自己提供比较器,实现接口Comparator,优先级高
线程不安全的,运行速度快
/**
* TreeMap集合存储对象
* Student作为键,字符串是值
* 自定义的比较器排序
*/
public static void treeMap2(){
Map map = new TreeMap( new MyCom() );
map.put(new Student("a",20),"广东");
map.put(new Student("b",19),"广西");
System.out.println("map = " + map);
}
/**
* TreeMap集合存储对象
* Person作为键,字符串是值
*/
public static void treeMap1(){
Map map = new TreeMap();
map.put(new Person("a",20),"广东");
map.put(new Person("b",19),"广西");
System.out.println("map = " + map);
}
/**
* 自定义的比较器,实现接口 Comparator
*/
class MyCom implements Comparator{
/**
* 方法compare 是TreeMap调用
* 传递参数,后来的对象传递到s1, 已经有的对象传递到s2
*/
public int compare(Student s1, Student s2){
return s1.getAge() - s2.getAge();
}
}
/**
* 进行比较:
* compareTo方法由,集合TreeMap调用
* 传递相关的参数 集合中后来的对象是this,先来的对象是参数 p
*/
public int compareTo(Person p){
return this.age - p.age;
}
Properties集合特点
继承Hashtable,实现Map接口
底层是哈希表结构
线程是安全的,运行速度慢
集合没有泛型的写法,键和值的数据类型锁定为String类型
集合有自己的特有方法
此集合可以和IO流对象结合使用,实现数据的持久存储
方法和IO相关:load(输入流)
/**
* 集合遍历
* Properties类的方法 stringPropertyNames() [等效于map.keySet()] 返回Set集合
* Set集合存储的是 Properties集合的所有键
*/
public static void prop3(){
Properties prop = new Properties();
prop.setProperty("a","1");
prop.setProperty("b","2");
prop.setProperty("c","3");
Set set = prop.stringPropertyNames();
for(String key : set){
System.out.println(key +"=="+ prop.getProperty(key));
}
}
/**
* 集合取出元素
* Properties集合取出方法 getProperty(String key)
*/
public static void prop2(){
Properties prop = new Properties();
prop.setProperty("a","1");
prop.setProperty("b","2");
prop.setProperty("c","3");
System.out.println(prop);
String value = prop.getProperty("a");
System.out.println(value);
}
/**
* 集合存储键值对
* Map接口,存储方法put
* Properties集合存储方法 setProperty(String key,String value)
*/
public static void prop1(){
Properties prop = new Properties();
prop.setProperty("a","1");
prop.setProperty("b","2");
prop.setProperty("c","3");
System.out.println(prop);
}