第十四章、集合
1.1、集合的理解和好处
1、集合
(1)可以动态保存任意多个对象,使用比较方便
(2)提供一系列方便的操作对象的方法:add、remove、set、get等
(3)使用集合添加,删除新元素的示意代码——更简介
1.2集合的框架体系图【背下来】
1、Collection

2、Map

集合主要分两组(单列集合、双列集合)
1、单列集合主要是只有一个对象
2、双列集合是一对键值对,存在key-value
3、Collection接口有两个重要的子接口
1.3、因为接口不能实现,只能用其子类来完成对接口的描述和实现
1.4、Collection(List和Set都有的接口)
Collection接口实现类的特点
1、collection实现可以存放多个元素,每个元素可以是Object
2、有些Collection的实现类,可以存放重复的元素,有些不可以
3、有些Collection的实现类,有些是有序的(List),有些是无序的(Set)
4、Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
Collection接口的常用方法。
1、add:添加单个元素
(1)eg:
list.add("sadiao");
list.add(10); //list.add(new Integer(10)
// 因为添加的数据是对象
2、remove:删除指定元素
(1)eg:
remove删除
list.remove(0); //删除第一个元素
System.out.println(list);
list.remove("sadiao"); //指定删除某个数
System.out.println(list);
3、contains:查找某个元素是否存在 // 返回Boolean值
(1)eg:
查找元素是否存在
System.out.println(list.contains(10));//T
4、size:获取元素个数
(1)eg:
获取元素个数
System.out.println(list.size()); //1
5、isEmpty:判断是否为空
1)eg:
System.out.println(list.isEmpty()); //F
6、clear:清空
1)eg:
list.clear();
7、addAll:添加多个元素 //可以放集合进去
1)eg:
addall() 放集合进去
ArrayList list2 = new ArrayList();
list.add("sb");
list.add("ssss");
list.addAll(list2);
8、containsAll:查找多个元素是否都存在 //返回Boolean值
1)eg:
containsAll:查找多个元素是否存在,放集合进去
System.out.println(list.containsAll(list2));
9、removeAll:删除多个元素 //可以放集合进去
1)eg:
System.out.println(list.removeAll(list2));
Collection接口遍历元素方式1-使用Iteratior(迭代器)
1、Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
2、所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,及可以返回一个迭代器。
3、Iterator的结构图
4、Iterator仅用于遍历集合,Iterator本身并不存放对象。
迭代器原理

1、注意:
(1)在调用iterator.next()方法之前必须要调用
(2)iterator.hasNext()进行检查。若不调用,且下一条记录无效,直接调用it.next()会抛出异常:
(3)NoSuchElementException异常
(4)当while循环后,iterator迭代器,指向最后一个元素,想要再次遍历,要重置迭代器。
2、eg:
Collection col = new ArrayList();
col.add(new Book("三国演义","罗贯中",1000));
col.add(new Book("爱情买卖","二狗",1100));
col.add(new Book("Java从入门到入土","身体日",888888));
// 遍历col集合
Iterator iterator = col.iterator();
// 使用while循环
while(iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj="+obj);
}
// 退出后,迭代器指向最后一行
// 重新迭代
iterator = col.iterator();
Collection接口遍历对象方法2-for循环增强。
1、增强for循环,可以代替iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历结合或数组。
2、基本语法
(1)for(元素类型 元素名:集合名或数组名){
访问元素
}
3、eg:
for(Object book : col) {
System.out.println("book = "+book);
}
int [] nums = {1,2,4,4,5,6};
for(int i :nums) {
System.out.println("i="+i);
}
4、增强for循环,低层仍然是迭代器
1.5、List接口的方法
基本介绍:List接口是Collection接口的子接口
1、List集合类中元素是有序的(即添加顺序和取出顺序一致)、且可重复
(1)eg:
List list = new ArrayList();
list.add("aha1");
list.add("Tom2");
list.add("Fuc3");
list.add("lululu4");
list.add("lalala5");
list.add("lalala5");
System.out.println("list="+list);
2、List集合中的每一个元素都有其对应的顺序索引,支持索引
(1)eg:
System.out.println(list.get(3)); //lululu4且下标从0开始
3、JDK中API中List的接口实现类很多
(1)常用的有:ArrayList、LinkedList和Vector
List接口常用方法
1、void add(int index , Object ele):在index位置插入ele元素
2、Object get(int index):获取指定index位置的元素
3、int indexOf(Object obj):返回obj在集合中首次出现的位置
4、int lastIndexOf(Object obj ):返回obj在集合中末次出现的位置
5、Object remove(int index):移除指定位置的元素,并返回此元素
6、Object set(int index , Object ele):设置指定index位置的元素为ele,相当于替换
7、List subList(int formIndex ,int toIndex):返回从fromIndex到toIndex位置的子集合。下标前闭后开
8、eg:
List list = new ArrayList();
list.add("黄飞绿");
list.add("大狗");
list.add("二狗");
list.add("三狗");
list.add("四狗");
list.add(2,"傻狗");
System.out.println(list);
// addAll
List list2 = new ArrayList();
list2.add("五狗");
list2.add("二狗");
list.addAll(1,list2);
System.out.println(list);
// 首次位置
System.out.println(list.indexOf("二狗"));//2
// 末次位置
System.out.println(list.lastIndexOf("二狗")); //5
// 移除
System.out.println(list.remove(0));
// 替换
System.out.println(list.set(1, "憨包"));
// 返回选中值
List returnList = list.subList(0, 2);
System.out.println(returnList);
普通for循环
// 3、使用普通for循环
for(int i=0;i<list.size();i++) {
System.out.println("对象="+list.get(i));
}
ArrayList注意事项
1、permits all elements, including null ,ArrayList可以加入null,并且多个
2、ArrayList是由数组来实现数据存储的
3、ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码,在多线程情况下,不建议使用ArrayList
1.6、ArrayList底层结构和源码
1、ArrayList 中维护了一个Object类型的数组elementData.transient Object[] elementData;
2、当创建ArrayList对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容到10,如需再次扩容,则扩容elementData为1.5倍。
3、如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

1.7、Vector低层源码及剖析
基本介绍
1、Vector类的定义说明
2、Vector低层也是一个对象数组,protected Object [] elementData;
3、Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized
4、在开发中,需要线程安全时,考虑使用Vector
5、低层理解
(1)首先Vector在调用无参构造器时, Vector vec = new Vector();时是默认为10,传入
(2)调用add方法,添加数据到vec集合中
(3)确定是否需要扩容条件:minCapacity - elementData.length>0
(4)如果需要的数组大小,不够用,就扩容,
(5)扩容的算法:



Vector低层结构和ArrayList的比较

1.8、LinkedList
LinkedList的低层操作机制
1、LinkedList低层是维护了一个双向链表
2、LinkedList中维护了两个属性first和last分别指向首节点和尾节点
3、每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
4、所有LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
5、模拟一个简单的双向链表
6、老韩源码阅读
(1)添加

(2)删除:默认删除第一个节点
ArrayList和LinkedList比较
1、

如何选择ArrayList和LinkedList
1、如果我们改查的操作较多,选择ArrayList
2、如果我们增删的操作多,选择LinkedList
3、一般来说,大多数情况都是查询,因此大部分选择ArrayList
4、项目中,根据业务逻辑,一个模块使用的是ArrayList另一个模块选择的是LinkedList
1.9、Set接口和常用方法
基本介绍
1、无序(添加和取出顺序不一致),没有索引
2、不允许重复元素,所以最多包含一个null
3、JDK API中Set接口的实现类有
4、注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定。
Set常用方法和遍历方法
1、和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
2、遍历
(1)迭代器
(2)增强for循环
Set set = new HashSet();
set.add("jack");
set.add("tom");
set.add("mary");
set.add("qt");
set.add("qt");
set.add(null);
set.add(null);
for(int i=0;i<set.size();i++) {
System.out.println("set="+set);
}
// 使用迭代器
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj1="+obj);
}
// 使用增强for循环
for(Object object :set) {
System.out.println("o="+object);
}
1.10、Set接口实现类-HashSet
HashSet的全面说明
1、hasSet实现了Set接口
2、HashSet实际上是HashMap
3、可以存放null值,但是只能有一个null
4、HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证存放元素的顺序和取出 顺序一致)
5、不能重复元素/对象.在前面Set接口使用已经讲过
HashSet低层机制说明
1、HashSet低层是HashMap
2、添加一个元素时,先得到hash值-会转成->索引值
3、找到存储数据表table,看这个索引位置是否已经存放的有元素
4、如果没有,直接加入
5、如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后(程序员可以重写调用equals方法)
6、在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=Min_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
7、扩容机制

8、补充
(1)每加入一个结点Node,size++。而不是table增加才算
HashSet—实践
package set_;
import java.util.HashSet;
import java.util.Objects;
public class HashSetHomeword01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*
* 定义一个Employee类,该类包含:private成员属性name,sal,birthday(MyDate类型)要求:
* 其中birthday为MyDate类型(属性包括:year,month,
* 创建3个Employee 对象放入HashSet中
* 当name和age的值相同时,人为是相同员工,不能添加到HashSet集合中
*/
HashSet hashSet = new HashSet();
hashSet.add(new Employee1("傻瓜",5000,new MyDate(2022, 10, 5)));
hashSet.add(new Employee1("傻瓜",5000,new MyDate(2022, 11, 25)));
hashSet.add(new Employee1("黄瓜",5000,new MyDate(2022, 10, 5)));
hashSet.add(new Employee1("憨包",5000,new MyDate(2022, 10, 5)));
System.out.println(hashSet);
}
}
class Employee1{
private String name;
private int sal;
private MyDate birthday;
public Employee1(String name, int sal, MyDate birthday) {
super();
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee1 [name=" + name + ", sal=" + sal + ", birthday=" + birthday + "]\n";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSal() {
return sal;
}
public void setSal(int sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public int hashCode() {
return Objects.hash(birthday, name, sal);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee1 other = (Employee1) obj;
return Objects.equals(birthday, other.birthday) && Objects.equals(name, other.name) && sal == other.sal;
}
}
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 String toString() {
return "MyDate [year=" + year + ", month=" + month + ", day=" + day + "]";
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public int hashCode() {
return Objects.hash(day, month, year);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyDate other = (MyDate) obj;
return day == other.day && month == other.month && year == other.year;
}
}
1.11、Set接口实现类LinkedHashSet
LinkedHashSet的全面说明
1、LinkedHashSet是HashSet的子类
2、LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
3、LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
4、LinkedHashSet不允许添加重复元素
LinkedHashSetSource底层

2.1、Map接口和常用方法
2.1.1Map接口实现类的特点
1、Map于Collection并列存在。用于保存具有映射关系的数据:Key-Value
2、Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
3、Map中的key不允许重复
4、Map中的value可以重复
5、Map的key可以为null。value也可以为null,注意key为null,只能有一个(主要原因还是key不能重复),value为null,可以有多个
6、常用String类作为Map的key
7、key 和value之间存在的单向一对一关系,即通过指定的key总能找到对应的value值
8、Map存放数据的key-value示意图,一堆k-v是存放在一个Node中的,有因为Node实现了 Entry接口,有些书上也说一堆k-v就是一个Entry
2.1.2Map接口常用方法
1、put()方法:
Map map = new HashMap();
map.put("邓超", "马蓉");
2、remove():删除映射关系
// 删除
map.remove("宋喆");
System.out.println("map="+map);
3、get():根据键获取值
(1)返回的是要用一个对象接受
// 获取键所对应的值
Object val = map.get("王宝强");
System.out.println("val="+val);
//结果是马蓉
4、size():获取键值对的尺寸
// 获取尺寸
System.out.println("k-v="+map.size());
5、clear():清空键值对
6、containsKye():查看键是否存在返回boolean值
7.KeySet():获取所有的键
8、entrySet():获取所有关系k-v
9、values():获取所有的值
2.1.2、Map接口六大遍历方式
数据:
Map map = new HashMap();
map.put("邓超", "马蓉");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘凌波", null);
map.put("邓超", "蓉");
1、第一种:先取出所有key,在用key取value
Set keyset = map.keySet();
// 增强for循环
for(Object obj :keyset) {
System.out.println(obj+"——"+map.get(obj));
}
// 迭代器
Iterator iterator = keyset.iterator();
while(iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj+"——"+map.get(obj));
}
2、第二种:把所有的values取出
Collection values = map.values();
// 增强for循环
for(Object obj :values) {
System.out.println("obj="+obj);
}
// 迭代器
Iterator iterator2 = values.iterator();
while(iterator2.hasNext()) {
Object obj = iterator2.next();
System.out.println("obj="+obj);
}
3、第三种:通过EntrySet来获取k-v
(1)理解:就是HashMap$Node 实现了Map.Entry()的接口,就实现接口,调用该接口中的getKey()和getValue()方法
// 第三组:通过EntrySet来获取k-v
// 增强for循环
Set entrySet = map.entrySet();
for(Object entry:entrySet) {
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey()+"——"+m.getValue());
}
// 迭代器
Iterator iterator3 = entrySet.iterator();
while(iterator3.hasNext()) {
Object next = iterator3.next();
// System.out.println(next.getClass());
//// 向下转型
Map.Entry m =(Map.Entry)next;
System.out.println(m.getKey()+"———"+m.getValue());
}
2.1.3、hashMap小结
1、Map接口的常用实现类:HashMap、Hashtable和Properties
2、HashMap是Map接口使用频率最高的实现类。[]()
3、HashMap是以key-val对的方式对存储数据(HashMap$Node类型)
4、key不能重复,但是是值可以重复,允许使用null见和null值
5、如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key不会替换,val会替换)
6、与HahsSet一样,不保证映射 的顺序,因为低层是以hash表的方式来存储的。
7、HashMap没有实现同步,因此线程不安全,方法没有做同步互斥的操作,没有synchronized。
2.1.4、hashMap底层机制及源码剖析

1、扩容机制和HashSet相同
2、HashMap 底层维护了Node类型的数组table,默认为null
3、当创建对象时,加载因子(loadfactor)初始化为0.75
4、当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,作出相应处理。如果添加时发现容量不够,则需要扩容。
5、第一次添加,则需要扩容table容量为16,临界值(threshold)为12
6、以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,以此类推
7、在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化。
2.2、Map接口实现类—Hashtable
2.2.1、Hashtable基本介绍
1、存放的元素是键值对:即k-v
2、hashtable的键和值都不能为null,否则抛出NullPointerException
3、hashTable使用方法基本上和HashMap一样
4、hashTable是线程安全的,hashMap是线程不安全的
2.2.2、Hashtable底层扩容
1、执行方法addEntry(hash key ,value,index);添加k-v 封装到Entry
2、当if(count>=threshold)满足时,就进行扩容
3、按照int newCapacity=(oldCapacity<<1)+1;的大小扩容。
2.2.3、Hashtable和HashMap对比

2.3、Map接口实现类—Properties
2.3.1、基本介绍
1、Properties类继承自Hashtable类且实现了Map接口,
也是使用一种键值对的形式来保存数据
2、使用特点和Hashtable类似
3、Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改。
4、说明:工作后xxx.properties文件通常作为配置文件,这个知识点在IO流中。
2.3.2、基本使用
// 基本使用
Properties properties = new Properties();
// 增加
// properties.put(null, "aaa");k-v不能为null
properties.put("lucy",100);
properties.put("lic", 100);
// 替换/修改
properties.put("lic", 88);
System.out.println(properties);
// 通过k获取v 查
System.out.println(properties.get("lic"));//88
// 删除
properties.remove("lic");
3、??总结开发中如何选择集合实现类(记住)
1、开发随机应变
3.1、先判断存储的类型

4、Collections工具类
4.1、Collections工具类的介绍
1、Collections是一个操作Set、List和Map等集合的工具类
2、Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
4.2、排序操作(均为static方法)
1、reverse(List):反转List中元素的顺序
2、shuffle(List):对List集合元素进行随机排序
3、sort(List):根据元素的自然顺序对指定List集合元素按升序排序
4、sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
5、swap(List,int,int):将指定List集合中的i处元素和j出元素进行交换
6、Object max(Collection):根据元素的自然顺序给定集合中的最大元素
7、Object max(Collection,Comparator):根据Comparator指定的顺序, 返回给定集合中的最大元素
8、Object min(Collection)
9、Object min(Collection,Object):返回指定集合中指定元素的出现次数
10、int frequency(Collection,Object):返回指定集合何总指定元素的出现次数
11、void copy(List 的dest,List src):将src中的内容复制到dest中
12、boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值。
方法举例:
List list = new ArrayList<>();
list.add("tom");
list.add("tom");
list.add("smith");
list.add("king");
list.add("milanssss");
// reverse(List):反转
Collections.reverse(list);
System.out.println("list="+list);
// 随机排序:shuffle()
Collections.shuffle(list);
System.out.println("list="+list);
// 自然排序:字符串排序
Collections.sort(list);
System.out.println("list="+list);
// 希望按照字符串的长度大小排序
Collections.sort(list,new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println("字符串长度大小排序="+list);
// 指定位置交换i和j交换
Collections.swap(list, 0, 1);
System.out.println("交换后="+list);
// 根据元素自然顺序
System.out.println("集合元素最大值"+Collections.max(list));
// 根据Comparator比较器指定顺序,返回
// 比如,返回长度最大值
Object maxObject =Collections.max(list,new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
return ((String) o1).length() - ((String)o2).length();
}
});
System.out.println("长度最大="+maxObject);
// int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现的次数="+Collections.frequency(list, "tom"));
// void cpoy(List dest ,List src):将src中的内容复制到dest中
ArrayList dest = new ArrayList();
// 需要先给dest复制,大小和list.size()一样
for(int i=0 ;i<list.size();i++) {
dest.add(" ");
}
// 拷贝,
Collections.copy(dest, list);
System.out.println("dest= " +dest);
// boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值。
// 如果list中有tom,就替换成傻瓜
Collections.replaceAll(list, "tom", "傻瓜");
System.out.println("list="+list);
5、总结
