集合
集合
集合概述
-
什么是集合?有什么用?
数组其实就是一个集合,集合其实是一种容器。可以容纳其他类型的数据。
为什么集合在开发中使用较多?
集合是一种容器,是一个载体,可以一次容纳多个对象。
在实际开发中,假设连接数据库,数据库有10条数据,那么假设查询这10条数据,
Java程序会将这10条数据封装成10个对象,然后将10个Java对象放到一个集合当中,
由集合传到前端,然后遍历集合,将数据展现出来。
-
集合不能直接储存基本数据类型,另外集合也不能直接储存Java对象,集合中储存的都是Java对象的地址
或者说集合储存的是引用。
注意:
list.add(100);//自动装箱 集合在Java中本身就是一个容器,一个对象。
在任何情况下储存的都是引用。
-
在Java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中储存元素,等于将数据放到不同的数据结构当中。
什么是数据结构?数据存储的结构就是数据结构,不同的数据结构数据储存方式不同。例如:
数组、二叉树、哈希表、链表....这些都是常见的数据结构。
向c1集合中放数据,可能放到了数组上。
向c2集合中放数据,可能放到了二叉树上。
......
使用不同的集合想当于使用了不同的数据结构。
在Java中掌握的不是精通数据结构,Java中已经将数据结构实现了,写好了常用的集合类,只需要掌握去用,选择合适的集合使用即可。
new ArrayList();创建一个集合对象,底层是数组。new LinkedList();创建一个集合对象,底层是链表。new TreeSet();创建一个集合对象,底层是二叉树。....
-
集合在Java JDK中那个包下?
java.util.* 所有的集合类和集合接口都在这个包下。
-
为了熟练掌握这一章节,最好将集合的继承结构背会!
对于集合这个体系是一个什么样的结构,要有个大致印象。
-
在Java中集合分为两大类:
一类是单个方式储存元素:
单个方式储存元素,这一类集合中超级父接口:
java.util.Collection;一类是以键值对的方式储存元素
以键值对的方式储存元素,这一类集合中超级父接口:
java.utli.map; -
总结:(所有的实现类)
ArrayList:底层是数组
LinkedList:底层是双向链表
HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合的key部分
TreeSet:底层是HashMap,放到TreeSet集合中的元素等同于放到HashMap集合的key部分
HashMap:底层是哈希表
Hashtable:底层也是哈希表(线程安全,效率低,使用较少)
Properties:是线程安全的,且key和value只能存储字符串
TreeMap:底层是二叉树,TreeMap集合的key可以按照大小顺序排序
List集合储存元素的特点:
有序可重复
有序:存进去的顺序和取出来的顺序相同,每个元素都有下标
可重复:允许储存相同的元素
Set集合储存元素的特点:
无序不可重复
无序:存进去的顺序和取出来的顺序,不一定相同,另外Set元素集合没有下标
不可重复:不允许储存相同的元素
SortedSet集合储存元素的特点:
首先是无序不可重复,但是SortedSet集合中的元素是可排序的
可排序:可以按照大小顺序排列
Map集合的key,就是一个Set集合
往Set集合中放数据,实际上放到Map集合中的Key部分
集合继承结构图


Collection接口常用方法
import java.util.ArrayList;
import java.util.Collection;
/*
关于java.util.Collection接口中常用的方法
1. Collection能存放什么元素?
没有使用"泛型"之前,Collection中可以储存Object的所有子类型。
使用"泛型"之后,Collection中只能储存某个具体的类型。
集合后期我们会学习"泛型"语法,目前先不管,Collection中什么都能存。
只要是Object的的子类型就行。(集合中不能直接储存基本数据类型,也不能存储Java对象,储存的是内存地址。)
2. Collection中的常用方法
boolean add(E e) 向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否含有元素o,包含返回ture,不包含返回false。
boolean remove(Object o) 删除集合中的某个元素。
boolean isEmpty() 判断该集合是否为空。
Object[] toArray() 调用这个方法可以将集合转化为数组。
*/
public class CollectionTest {
public static void main(String[] args) {
//创建一个集合对象
//Collection c = new Collection();接口是抽象的,无法实例化。
//多态
Collection c = new ArrayList();
//测试Collection接口中的常用方法
c.add(1200);//自动装箱,实际上是放进去一个对象的内存地址,等同于 Integer x = new Integer(1200);c.add(x);
c.add(3.14);//自动装箱
c.add(new Object());
c.add(new Student());
c.add(true);//自动装箱
//获取集合中元素的个数
System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是5
//清空集合
c.clear();
System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是0
//添加元素
c.add("Hello");
c.add("World");
c.add("绿巨人");
c.add("浩克");
c.add(1);
//判断集合中是够包含"绿巨人"
System.out.println(c.contains("绿巨人"));//true
System.out.println(c.contains("绿巨人1"));//false
System.out.println(c.contains(1));//true
System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是5
//删除集合中某个元素
c.remove(1);
System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是4
//判断集合是否为空
System.out.println(c.isEmpty());//false
c.clear();
System.out.println(c.isEmpty());//true
c.add("Hello");
c.add("World");
c.add("绿巨人");
c.add("浩克");
c.add(1);
c.add(new Student());
//转换为数组
Object[] objs = c.toArray();
for (int i = 0; i < objs.length; i++) {
System.out.println(objs[i]);
/*
Hello
World
绿巨人
浩克
1
Student@6b884d57
*/
}
}
}
class Student {
}
Collection集合迭代/遍历
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/*
关于集合迭代/遍历专题
*/
public class CollectionTest01 {
public static void main(String[] args) {
//注意:以下讲解的遍历/迭代方式,是所有Collection通用的一种方法。
//在Map集合中不能用,在所有Collection以及子类中使用。
//创建集合对象
Collection c = new HashSet();//后面的集合无所谓,主要是看前面的Collection接口,怎么迭代/遍历。
//添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
//对集合Collection进行迭代/遍历
//第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
//第二步:通过以上获取的迭代器对象开始迭代/遍历集合
/*
以下两个方法
boolean hasNext() 如果仍有元素可以迭代,则返回ture。
Object next() 返回迭代的下一个元素。
*/
while (it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
}
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class CollectionTest02 {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();
//添加元素
c.add(1);
c.add(2);
c.add(3);
c.add(4);
//迭代集合
Iterator it =c.iterator();
while (it.hasNext()) {
//存进去什么类型,取出来就是什么类型。
Object obj = it.next();
if (obj instanceof Integer) {
System.out.println("Integer类型");
}
//只不过在输出的时候会转换为字符串
System.out.println(obj);
/*
Integer类型
1
Integer类型
2
Integer类型
3
Integer类型
4
*/
}
//HashSet集合:无序不可重复
Collection c1 = new HashSet();
//无序:存进去和取出来的顺序,不一定相同。
//不可重复:存进去100就不能再存100。
c1.add(100);
c1.add(100);
c1.add(200);
c1.add(300);
c1.add(400);
c1.add(500);
c1.add(600);
/*
400
100
500
200
600
300
*/
Iterator it1 = c1.iterator();
while (it1.hasNext()) {
System.out.println(it1.next());
}
}
}
contains方法
import java.util.ArrayList;
import java.util.Collection;
/*
深入了解Collection集合的contains方法:
boolean contains(Object o)
判断集合中是否包含某个对象o
如果包含返回true,不包含返回false。
contains方法是用来判断集合中是否包含某个元素的方法,它在底层调用了equal方法进行比对。
*/
public class CollectionTest03 {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();
//向集合中储存元素
String s1 = new String("abc");
c.add(s1);
String s2 = new String("def");
c.add(s2);
System.out.println("元素的个数为" + c.size());
String s3 = new String("abc");
System.out.println(c.contains(s3));//true
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
public class CollectionTest04 {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();
//创建用户对象
User u1 = new User("jack");
User u2 = new User("jack");
//加入集合
c.add(u1);
//判断集合中是否包含u2
//没有重写equals之前这个是false,重写之后就变为了true
//重写之后就比较内容了
System.out.println(c.contains(u2));//true
//其实底层相当于u2.equals(u1);
c.remove(u2);
//其实底层相当于u2.equals(u1);
System.out.println(c.size());//0
}
}
class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
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;
User user = (User) o;
return Objects.equals(name, user.name);
}
}
关于集合中元素的删除
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionTest05 {
public static void main(String[] args) {
//创建集合
Collection c = new ArrayList();
//添加元素
c.add(1);
c.add(2);
c.add(3);
//获取迭代器
Iterator it = c.iterator();
while (it.hasNext()) {
//编写代码时next()方法返回值必须是Object。
Object obj = it.next();
System.out.println(obj);
}
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/*
关于集合元素的remove
重点:当集合结构发生改变时,迭代器必须重新获取,如果还在使用以前的迭代器会出现异常:java.util.ConcurrentModificationException
重点:在迭代元素的过程中,不能调用集合对象的remove()方法 删除元素:c.remove(o);
迭代过程中不能这样,会出现:java.util.ConcurrentModificationException
重点:获取迭代器对象遍历集合,相当于对当前集合拍了一个快照,迭代器会按照当前快照进行迭代。
直接通过集合去删除元素,没有通知迭代器,导致快照和集合状态不同,迭代器去删除时,会自动更新迭代器和集合。
在迭代元素中,一定要使用迭代器Iterator的remove方法,删除元素,不要使用集合自带的remove方法删除元素。
*/
public class CollectionTest05 {
public static void main(String[] args) {
//创建集合
Collection c = new ArrayList();
//注意:此时获取的迭代器,指向的是那个集合中没有元素状态的迭代器。
//一定要注意:只要集合结构发生改变,迭代器必须重新获取。
//当集合结构发生了改变,迭代器没有重新获取,调用next()方法时:java.util.ConcurrentModificationException
Iterator it = c.iterator();
//添加元素
c.add(1);
c.add(2);
c.add(3);
while (it.hasNext()) {
//编写代码时next()方法返回值必须是Object。
Object obj = it.next();
it.remove();//删除的一定是迭代器指向的当前元素
System.out.println(obj);
}
}
}
List接口特有方法
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
测试List接口常用方法
1. List集合储存元素的特点:有序可重复
有序:List集合每个元素都有下标,从0开始,以1递增。
可重复:允许储存相同的元素
2. List既然是Collection的子接口,那么List接口肯定有自己的方法
以下只列出List接口特有且常用方法:
void add(int index, E element)
E get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
E remove(int index)
E set(int index, E element)
以上方法不需要死记硬背,自己多试理解
以后开发还是要翻阅文档
*/
public class ListTest01 {
public static void main(String[] args) {
//创建List类型的集合
List myList = new ArrayList();
//添加元素
myList.add("A");//默认都是向集合末尾添加元素
myList.add("B");
myList.add("C");
myList.add("C");
myList.add("D");
//将指定元素插入此列表中的指定位置(第一个数是下标)
//这个方法使用的不多,因为对于ArrayList集合来说效率比较低。
myList.add(1,"KING");
//迭代
Iterator it = myList.iterator();
while (it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
}
//根据下标获取元素
Object firstObj = myList.get(0);
System.out.println(firstObj);
//因为有下标,所以List集合有自己比较特殊的遍历方式
//通过下标遍历
for (int i = 0; i < myList.size(); i++) {
Object object = myList.get(i);
System.out.println(object);
}
//获取指定对象第一次出现处的索引
System.out.println(myList.indexOf("KING"));//1
///获取指定对象最后一次出现处的索引
System.out.println(myList.lastIndexOf("C"));//4
//删除指定下标位置的元素
//删除下标为0的元素
myList.remove(0);
System.out.println(myList.size());//5
//修改指定位置的元素
myList.set(2,"Soft");
//遍历集合
for (int i = 0; i < myList.size(); i++) {
Object object = myList.get(i);
System.out.println(object);
}
}
}
ArrayList
ArrayList集合
-
默认初始化容量是10
-
集合底层是一个Object[] 数组
-
构造方法:
构造器 描述 ArrayList()构造一个初始容量为10的空列表。 ArrayList(int initialCapacity)构造具有指定初始容量的空列表。 ArrayList(Collection<? extends E> c)按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。 import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; public class ArrayList01 { public static void main(String[] args) { //创建一个HashSet集合 Collection c = new HashSet(); c.add(1); c.add(2); c.add(3); c.add(4); c.add(5); c.add(6); c.add(7); c.add(8); c.add(9); c.add(10); //通过这个构造方法就可以将HashSet集合转换为List集合 List list = new ArrayList(c); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } } -
ArrayList集合的扩容:
原容量的1.5倍
LinkedList
链表的优点:
由于链表上的元素,在空间储存上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率极高,在以后的开发中,如果遇到随机增删集合中元素的业务较多时,建议使用LInkedList
链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止,所以查找的效率较低。
ArrayList:将检索发挥到了极致
LinkedList:把随机增删发挥到了极致
但是一般加元素都是向末尾添加,所以ArrayList用的多
import java.util.LinkedList;
import java.util.List;
public class LinkedListTest01 {
public static void main(String[] args) {
List list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");
for (int i = 0; i <list.size(); i++) {
System.out.println(list.get(i));
}
//LinkedList集合有初始化容量吗?没有。
//最初这个链表什么元素都没有,first和last都是null
//不管是ArrayList还是LinkedList,以后写代码不需要关心使用那个集合
//因为我们要面向接口编程,调用的方法都是接口的方法。
}
}
Vector
import java.util.*;
/*
1. 底层是一个数组
2. 初始化容量为10
3. 每扩容一次容量就是原来的二倍
4. ArrayList每次扩容增加1.5倍
5.Vector是线程安全的,但是效率低
6. 怎么将一个线程不安全的ArrayListj集合转换为线程安全的呢?
使用集合工具类
java.util.Collections;
java.util.Collection 是集合接口
java.util.Collections 是集合工具类
7.
*/
public class VectorTest01 {
public static void main(String[] args) {
Vector vector = new Vector();
vector.add(1);
vector.add(2);
vector.add(3);
vector.add(4);
vector.add(5);
vector.add(6);
vector.add(7);
vector.add(8);
vector.add(9);
vector.add(10);
//满了自动扩容,容量变为20
vector.add(11);
//这个可能以后会使用
List myList = new ArrayList();
//变成线程安全的
Collections.synchronizedList(myList);//这里没办法看效果,没学多线程。
myList.add(111);
myList.add(222);
myList.add(333);
}
}
泛型
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
JDK5.0之后推出的新特性:泛型
泛型这种语法机制,只在程序编译的时候起作用,只是给编译器参考的,运行阶段泛型没用!
使用泛型的好处是:
1. 集合储存的元素类型统一
2. 取出的事指定类型,不需要进行大量的向下转型
泛型的缺点是:
集合中储存的元素缺乏多样性
大多数业务中,集合中元素类型还是统一的,所以这种泛型特性被大家认可
*/
public class GenericTest01 {
public static void main(String[] args) {
/*
List myList = new ArrayList();
//准备对象
Cat c = new Cat();
Bird b = new Bird();
//将对象添加到集合当中
myList.add(c);
myList.add(b);
//遍历集合 取出每个Animal 让他Move
Iterator it =myList.iterator();
while(it.hasNext()) {
Object obj = it.next();
Animal a = (Animal)obj;
a.move();
}
*/
//使用JDK5的泛型机制
//使用泛型List<Animal>之后,表明List集合中只允许存储Animal类型的数据。
//用泛型来指定集合中储存的数据类型。
List<Animal> myList = new ArrayList<Animal>();
//指定List集合中只能储存Animal,那么储存String就报错。
//这样使用泛型过后,集合中的元素类型更加统一。
//myList.add("123");
Cat c = new Cat();
Bird b = new Bird();
myList.add(c);
myList.add(b);
//获取迭代器
//这个表示迭代器迭代的是Animal对象
Iterator<Animal> it = myList.iterator();
while (it.hasNext()) {
//使用泛型之后,每一次迭代返回的数据都是Animal类型
//Animal a = it.next();
//这里不需要强制类型转换,直接调用
//a.move();
//调用子类特有的方法还是需要向下转型
Animal a = it.next();
if (a instanceof Cat) {
Cat cat = (Cat)a;
cat.catchMouse();
}
if (a instanceof Bird) {
Bird bird = (Bird) a;
bird.fly();
}
}
}
}
class Animal {
//父类方法
public void move() {
System.out.println("动物在移动");
}
}
class Cat extends Animal {
//特有方法
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
class Bird extends Animal {
//特有方法
public void fly() {
System.out.println("鸟在飞翔");
}
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
JDk8之后引入了:自动类型推断(又称为钻石表达式)
*/
public class GenericTest02 {
public static void main(String[] args) {
//ArrayList<这里的类型会自动推断>() ,前提是JDK8之后允许。
//自动类型判断,钻石表达式
List<Animal> myList = new ArrayList<>();
myList.add(new Animal());
myList.add(new Cat());
myList.add(new Bird());
Iterator<Animal> it = myList.iterator();
while (it.hasNext()) {
//这样迭代器取出的都是Animal类型
Animal a = it.next();
//这里不需要强制类型转换,直接调用
a.move();
}
}
}
/*
自定义泛型可以吗?可以
自定义泛型的时候,<> 尖括号中是一个标识符,随便写。
Java源代码经常出现的是:
<E>和<T>
E是Element单词首字母
T是Type单词首字母
*/
public class GenericTest03<标识符随便写> {
public void doSome(标识符随便写 o) {
System.out.println(o);
}
public static void main(String[] args) {
//new对象是时指定了泛型是:String类型
GenericTest03<String> gt = new GenericTest03<>();
//类型不匹配
//gt.doSome(100);
gt.doSome("abc");
}
}
增强for循环(foreach)
/*
JDK5。0之后推出了新特性:叫增强for循环,或者叫Foreach
*/
public class ForEachTest01 {
public static void main(String[] args) {
//int类型数组
int[] arr = {1,2,3,4,5,6};
//遍历数组(普通for循环)
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//增强for循环(foreach)
//以下是语法
/*
for (元素类型 变量名 : 数组或者集合) {
System.out.println(变量名);
}
*/
//foreach有个缺点就是没有下标,在需要使用下标的循环,不需要使用。
for (int data : arr) {
//data就是数组的每一个元素
System.out.println(data);
}
}
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
集合使用增强for
*/
public class ForEachTest02 {
public static void main(String[] args) {
//创建List集合
List<String> strList = new ArrayList<>();
//添加元素
strList.add("Hello");
strList.add("World");
strList.add("kitty");
//遍历,使用迭代器迭代
Iterator<String> it = strList.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
//使用下标方式(只针对有下标的集合)
for (int i = 0; i < strList.size(); i++) {
System.out.println(strList.get(i));
}
//使用foreach
for (String s : strList) {
System.out.println(s);
}
}
}
Map
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/*
java.util.Map接口中常用方法:
1. Map和Collection没有继承关系
2. Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型,都是存储对象的内存地址,key起到主导作用,value是key的附属品。
3.Map接口中常用方法:
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数
Collection<V> values() 获取Map集合中所有的value ,返回一个Collection
Set<Map.Entry<K,V>> entrySet() 将Map集合转换为Set集合
Set<K> keySet() 获取集合所有的key
常用:Set<Map.Entry<K,V>> entrySet() 将Map集合转换为Set集合
假设现在有个Map集合,如下所示:
map集合对象
key value
-------------------------------
1 zhanhsan
2 lisi
3 wangwu
4 zhaoliu
Set set = map.entrySet();
set集合对象
1=zhanhsan
2=lisi
3=wangwu
4=zhaoliu
注意:Map集合通过entrySet()方法转换成这个Set集合,Set集合中元素的类型是Map.Entry<K,V>
Map.Entry和String一个样,都是一个类型的名字,只不过:Map.Entry是静态内部类。
*/
public class MapTest01 {
public static void main(String[] args) {
//创建Map集合对象
Map<Integer,String> map = new HashMap<>();
//向Map集合中添加键值对
map.put(1,"zhanhsan");//1在这里进行了自动装箱
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//通过key获取value
System.out.println(map.get(2));//lisi
//获取键值对的数量
System.out.println("键值对的数量为:" + map.size());//键值对的数量为:4
//通过key删除key-value
map.remove(2);
System.out.println("键值对的数量为:" + map.size());
//判断是否包含某个key
//contains方法底层都是调用equals方法进行比对,所以自定义的类型需要重写equals方法
System.out.println(map.containsKey(4));//true
//判断是否包含某个value
System.out.println(map.containsValue("wangwu"));//true
//获取所有的value
Collection<String> values = map.values();
//foreach
for (String value : values) {
System.out.println(value);
}
//清空map集合
map.clear();
System.out.println("键值对的数量为:" + map.size());//键值对的数量为:0
//判断是否为空
System.out.println(map.isEmpty());//true
}
}
import java.util.*;
/*
Map集合的遍历(非常重要)
*/
public class MapTest02 {
public static void main(String[] args) {
//第一种方式:获取所有的key,通过遍历key来遍历value
Map<Integer,String> map = new HashMap<>();
map.put(1,"zhanhsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//遍历map集合
//获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();
//遍历key,通过key获取value
//迭代器可以
Iterator<Integer> it = keys.iterator();
while (it.hasNext()) {
//取出其中其中的一个key
Integer key = it.next();
//通过key获取value
String value = map.get(key);
System.out.println(key + "=" + value);
}
//foreach也可以
for (Integer key : keys) {
System.out.println(key + "=" + map.get(key));
}
//第二种方法:Set<Map.Entry<K,V>> entrySet()
//以上这个方法是把Map集合直接转换为Set集合
//Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历Set集合,每次取出一个Node
//迭代器
Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while (it2.hasNext()) {
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}
//foreach
//这种方式效率较高,因为获取key和value都是直接从node对象中获取的属性值
//这种方式适合大数据量
for (Map.Entry<Integer, String> node : set) {
System.out.println(node.getKey() + "=" + node.getValue());
}
}
}
HashMap
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
HashMap集合:
1. HashMap集合底层是哈希表/散列表的数据结构
2. 哈希表是一个怎么样的数据结构
哈希表是一个数组和单向链表的结合体
数组:在查询的效率很高,在随机增删效率很低
单向链表:在随机增删效率很高,在查询的效率很低
哈希表将以上两种数据结构融合在了一起,充分发挥它们各自的优点
3. HashMap集合底层的源代码:
public class HashMap {
//HashMap底层其实是一个数组(一维数组)
transient Node<K,V>[] table;
//静态的内部类HashMap.node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果,hash值可以通过哈希函数/算法,可以转换储存成数组的下标)
final K key;//存储到Map集合中的那个Key
V value;//存储到Map集合中的那个value
Node<K,V> next;//下一个节点的内存地址
}
}
4. 哈希表/散列表:一维数组,这个数组每一个元素都是一个单向链表(数组和链表的结合体)
5. 最主要掌握的是:
map.put(k.v)
v = map.get(k)
map.put(k.v)实现原理:
第一步:先将k,v封装到Node对象中
第二步:底层会调用k的hashCode()方法得出hash值,然后通过哈希算法/哈希函数,将哈希值转换为数组的下标,下标位置如果没有任何元素
就将Node添加到这个位置上,如果下标对应的位置有链表,此时会将此时会拿着k和链表上的每一个节点z中的k进行equals
如果所有的节点进行equals返回的都是false,那这个新节点将会添加到末尾,如果有一个节点equals返回的是true
那么这个节点的value将会被覆盖
v = map.get(k)实现原理:
先调用k的hashCode()方法得出哈希值,通过哈希算法转换为数组下标,通过数组下表快速定位到某个位子上,如果什么都没有,返回null
如果这个位置上有单向链表,那么会拿着参数k进行equals,如果所有的节点进行equals都返回false。那么get()方法返回null
只要有其中的任意一个节点equals返回的是是true,那么这个节点就是我们要找的value,那么get()方法返回找到的value
注意:同一个单向链表上所有的节点hash值都相同,因为数组下标都一样,但是同一个链表上的k和k的equals方法肯定是返回的是false都不相等
通过上面的解释可以知道HashMap集合的key会先后调用两个方法,一个是hashCode()方法,一个是equals()方法,这两个方法都要重写
以上这两个方法的实现原理必须掌握
6. HashMap集合的key特点:
无序不可重复
为什么无序?因为不一定挂到那个单向链表上
不可重复是如何保证的?equals方法保证HashMap集合的key不可重复
如果key重复了,value会覆盖
放在HashMap集合的key其实就是放到HashSet集合中
所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法
7. 哈希表HashMap使用不当无法发挥性能
假设所有的hashCode()方法返回值是一个固定的值,那么对导致底层哈希表变成纯单向链表
这种情况我们称为:散列分布不均匀
什么是散列分布均匀?
假设有100个元素,10个单向链表,每个链表上有10个节点,这就是散列均匀分布
假设所有的hashCode()方法返回值都不一样,那哈希表就变为了一维数组,没有了链表的概念,这也是散列分布不均匀
散列分布均匀需要你重写hashCode()方法时有一定的技巧
8. 重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要重写hashCode和equals方法
9. HashMap集合的默认初始化容量为16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组容量达到%75的时候,就会扩容,容量是之前的2倍
重点:记住HashMap集合初始化容量必须是2的倍数,这是官方推荐,因为这是达到散列分布均匀,提高HashMap集合的存取效率,所必须的
*/
public class HashMapTest {
public static void main(String[] args) {
//测试hashMap集合key元素的特点
//Integer是key,它的hashCode和equals都被重写了
Map<Integer,String> map = new HashMap<>();
map.put(1111,"张三");
map.put(2222,"李四");
map.put(3333,"王五");
map.put(4444,"赵六");
map.put(4444,"Han");//key重复时,value会自动覆盖
System.out.println(map.size());//4
//遍历
Set<Map.Entry<Integer, String>> set = map.entrySet();
for (Map.Entry<Integer, String> entry : set) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
import javase.bean.Student;
import java.util.HashSet;
import java.util.Set;
/*
1. 向Map集合中存和取,都是先调用key的hashCode方法再调用equals方法
equals方法有可能调用,有可能不调用
拿put(k,v)举例,什么时候equals不调用?
数组为空
拿get(k)举例,什么时候equals不调用?
数组为空
2. 注意:如果一个类的equals方法重写,那么hashCode方法必须重写,并且equals方法返回的如果是true,那么HashCode返回值必须是一样的
3. hashCode方法和equals方法不用手写,IDEA能自动生成,但是这两个方法必须同时生成
4. 放在HashMap集合的key部分和放在HashSet集合中的元素,需要重写hashCode方法和equals方法
5. 在JDK8之后如果哈希表单向链表中元素超过8个,单向链表会变成红黑树这种数据结构,当红黑树上的节点少于6个时,会将红黑树变成单向链表数据结构
这种方式也是为了提高检索效率
6. 对于哈希表数据结构来说:
如果o1和o2d的hash值相同,一定是放到同一个单向链表上的
如果o1和o2d的hash值步同,但是有可能哈希算法执行结束后转换的数组下标相同,此时会发生"哈希碰撞"
*/
public class HashMapTest01 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan");
Student s2 = new Student("zhangsan");
//重写equals方法之前是false
//System.out.println(s1.equals(s2));false
//重写equals方法之后是ture
System.out.println(s1.equals(s2));//true
System.out.println("s1的hashCode=" + s1.hashCode());//951007336(重写之后:-1432604525)
System.out.println("s2的hashCode=" + s2.hashCode());//603742814(重写之后:-1432604525)
//但是经过重写后的s1.equals(s2)已经是true了,表示s1和s2是一样的,那么向HashSet集合中放元素,按道理只能放一个
//但是它们的hashCode结果却不一样,表示它们不一样,违背了不可重的原则!
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
System.out.println(students.size());//2 这个按道理来说应该是1,因为里面储存的都是"zhangsan",显然不符合HashSet集合储存特点
}
}
package javase.bean;
import java.util.Objects;
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student() {
}
public Student(String name) {
this.name = name;
}
//hashCode
//equals(如果学生名字一样,表示同一名学生)
/*
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Student)) return false;
if (this == obj) return true;
Student s = (Student) obj;
return name.equals(this.name);
}
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
import java.util.HashMap;
import java.util.Map;
/*
HashMap集合允许null吗?允许
*/
public class HashMapTest02 {
public static void main(String[] args) {
Map map = new HashMap();
//HashMap集合允许key为null
map.put(null,null);
System.out.println(map.size());//1
//key重复value会覆盖
map.put(null,100);
System.out.println(map.size());//1
//通过key获取value
System.out.println(map.get(null));//100
}
}
import java.util.Hashtable;
import java.util.Map;
/*
HashMap集合允许null吗?不允许
HashTable集合是线程安全的,因为都有synchronized关键词修饰
导致效率较低,使用较少
HashTable和HashMap一样,底层都是哈希表数据结构
Hashtable的初始化容量为11,默认加载因子是0.75F
Hashtable的扩容是:原容量 * 2 + 1
*/
public class HashTableTest {
public static void main(String[] args) {
Map map = new Hashtable();
//map.put(null,"123");
//java.lang.NullPointerException
//map.put(1,null);
//java.lang.NullPointerException
}
}
Properties
import java.util.Properties;
/*
目前只需要掌握Properties属性类对象的相关方法即可
Properties是一个Map集合,继承HashTable,properties的key和value都是String类型
Properties被称为属性类
Properties是线程安全的
*/
public class PropertiesTest {
public static void main(String[] args) {
//创建一个Properties对象
Properties pro = new Properties();
//掌握Properties的两个方法,一个存,一个取
pro.setProperty("url","jdbc:mysql://localhost:3306/Han");
pro.setProperty("driver","com.mysql.jdbc.Driver");
pro.setProperty("username","admin");
pro.setProperty("password","123456");
//通过key来获取value
String url = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.println(password);
}
}
TreeSet
import java.util.TreeSet;
/*
1. TreeSet集合底层其实是一个TreeMap
2. TreeMap集合底层是一个二叉树
3. 放到TreeSet集合中的元素,等同于放到TreeMap的key部分
4. TreeSet集合中的元素:无序不可重复,但会按照大小自动排序(可排序集合)
5.
*/
public class TreeSetTest {
public static void main(String[] args) {
//创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
//添加String
ts.add("a");
ts.add("d");
ts.add("q");
ts.add("f");
ts.add("k");
ts.add("s");
ts.add("b");
//遍历
for (String s : ts) {
//按照字典顺序排列,升序
System.out.println(s);
}
TreeSet<Integer> ts1 = new TreeSet<>();
ts1.add(100);
ts1.add(90);
ts1.add(10);
ts1.add(120);
ts1.add(60);
ts1.add(30);
for (Integer i : ts1) {
//按照字典顺序排列,升序
System.out.println(i);
}
}
}
/*
数据库有很多数据:
userId name birth
----------------------------------
1 zs 1980-11-11
2 ls 1980-10-11
3 ww 1981-11-11
4 zl 1979-11-11
编写程序从数据库取出数据,在页面显示用户信息的时候按照生日升序或者降序
这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来是有顺序的
*/
import java.util.TreeSet;
/*
对于自定义的类型,TreeSet可以排序吗?
以下程序对于Person类型来说,无法排序,因为没有指定Person对象的比较规则
没有说明谁大谁小
所以程序运行的时候出现了以下的异常:
Exception in thread "main" java.lang.ClassCastException: class Person cannot be cast to class java.lang.Comparable
(Person is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
出现这个的原因是:
Person类没有实现java.lang.Comparable接口
*/
public class TreeSetTest01 {
public static void main(String[] args) {
Person p1 = new Person(32);
Person p2 = new Person(20);
Person p3 = new Person(30);
Person p4 = new Person(25);
//创建TreeSet集合对象
TreeSet<Person> persons = new TreeSet<>();
//添加元素
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
//遍历
for (Person p : persons) {
System.out.println(p);
}
}
}
class Person {
private int age;
public Person(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person[age=" + age + "]";
}
}
import java.util.TreeSet;
public class TreeSetTest02 {
public static void main(String[] args) {
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
Customer c4 = new Customer(25);
//创建TreeSet集合对象
TreeSet<Customer> customers = new TreeSet<>();
//添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
customers.add(c4);
//遍历
for (Customer c : customers) {
System.out.println(c);
/*
Customer[age=20]
Customer[age=25]
Customer[age=30]
Customer[age=32]
*/
}
}
}
//放在TreeSet集合中的元素需要实现java.lang.Comparable接口
//并且是想compareTo方法,equals可以不写
class Customer implements Comparable<Customer> {
private int age;
public Customer(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Customer() {
}
@Override
public String toString() {
return "Customer[age=" + age + "]";
}
//需要在这个方法中编写比较的逻辑,或者比较的规则,按照什么进行比较!
//k.compareTo(t.key)
//拿着参数k和集合中的每一个k进行比较,返回值可能是 >0 <0 =0
//比较规则由程序员指定例如:按年龄升序/降序
@Override
public int compareTo(Customer c) { //c1.compareTo(c2);
//this是c1
//c是c2
//c1和c2比较时就是this和c的比较
/*
int age1 = this.age;
int age2 = c.age;
if (age1 == age2) {
return 0;
} else if (age1 > age2) {
return 1;
} else {
return -1;
}
*/
return this.age - c.age;// >0 <0 =0
}
}
import java.util.TreeSet;
/*
先按照年龄升序,如果年龄一样再按照姓名升序
*/
public class TreeSetTest03 {
public static void main(String[] args) {
TreeSet<Vip> vips = new TreeSet<>();
vips.add(new Vip("张三",20));
vips.add(new Vip("张四",20));
vips.add(new Vip("李四",18));
vips.add(new Vip("王五",17));
for (Vip vip: vips) {
System.out.println(vip);
}
}
}
class Vip implements Comparable<Vip>{
private String name;
private int age;
public Vip() {
}
public Vip(String name) {
this.name = name;
}
public Vip(int age) {
this.age = age;
}
public Vip(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 "Vip{" + "name='" + name + '\'' + ", age=" + age + '}';
}
/*
compareTo的返回值很重要:
返回0表示相同,value被覆盖
返回>0,会继续在右子树上找
返回<0,会继续在左子树上找
*/
@Override
public int compareTo(Vip v) {
if (this.age == v.age) {
//年龄相同时,按照姓名排序
//姓名是String类型,可以直接比,调用compareTo方法比较
return this.name.compareTo(v.name);
} else {
return this.age - v.age;
}
}
}
自平衡二叉树
-
TreeSet/TreeMap是自平衡二叉树,遵循左小右大原则存放
-
遍历二叉树的三种方法:
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
注意:
前中后说的是根的位置
根在前面是前序
根在中间是中序
根在后面是后序
-
TreeSet/TreeMap是集合采用的是:中序遍历方式(左根右)
实现比较器接口
import java.util.Comparator;
import java.util.TreeSet;
/*
TreeSet集合中元素排序的第二种方式:使用比较器的方式
结论:
第一种:放在集合的元素实现java.lang.Comparable接口
第二种:在构造TreeSet或者TreeMap集合的时候给他传个比较器对象
那么Comparable和Comparator怎么选择?
当比较规则不会发生改变的时候,或者当比较规则只有一个的时候,建议实现Comparable
如果比较规则有多个,并且需要多个比较规则来回切换,建议使用Comparator接口
Comparator接口的设计是符合OCP原则
*/
public class TreeSetTest04 {
public static void main(String[] args) {
//创建TreeSet这个集合的时候,需要使用这个比较器
//TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传入一个比较器
//给构造方法传递一个比较器
TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
//我们也可以采用匿名内部类的方式
/*
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.getAge() - o2.getAge();
}
});
*/
wuGuis.add(new WuGui(1000));
wuGuis.add(new WuGui(800));
wuGuis.add(new WuGui(810));
for (WuGui wuGui : wuGuis) {
System.out.println(wuGui);
}
}
}
//乌龟
class WuGui {
private int age;
public WuGui() {
}
public WuGui(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "小乌龟[" +
"age=" + age +
']';
}
}
//单独在这里编写一个比较器
//比较器实现java.lang.Comparator接口(Comparable是java.lang包下的,Comparator是java.util包下的)
class WuGuiComparator implements Comparator<WuGui> {
@Override
public int compare(WuGui o1, WuGui o2) {
//指定比较规则
//按照年龄排序
return o1.getAge() - o2.getAge();
}
}
Collections
import java.util.*;
/*
java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作
*/
public class CollectionsTest {
public static void main(String[] args) {
//ArrayList不是线程安全的
List<String> list = new ArrayList<>();
//变成线程安全的
Collections.synchronizedList(list);
//排序
list.add("abd");
list.add("abc");
list.add("abx");
list.add("abz");
Collections.sort(list);
for (String lists : list) {
System.out.println(lists);
}
List<WuGui2> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui2(1000));
wuGuis.add(new WuGui2(8000));
wuGuis.add(new WuGui2(800));
//注意:对List集合中的元素排序,需保证List集合中的元素实现了Comparable接口
Collections.sort(wuGuis);
for (WuGui2 wg : wuGuis) {
System.out.println(wg);
}
//对Set集合怎么排序?
//Set---->List
Set<String> set = new HashSet<>();
set.add("1");
set.add("2");
set.add("24");
set.add("3");
set.add("0");
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for (String s : myList) {
System.out.println(s);
}
//这种排序也可以
//Collections.sort(List集合,比较器对象);
}
}
class WuGui2 implements Comparable<WuGui2> {
private int age;
public WuGui2() {
}
public WuGui2(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(WuGui2 w) {
return this.age - w.age;
}
@Override
public String toString() {
return "WuGui2{" +
"age=" + age +
'}';
}
}

浙公网安备 33010602011771号