集合的使用
主要内容
- 集合总体结构介绍
- List 实现类
- Set的实现类
- Map实现类
- Iterator 迭代器
- Collections 集合工具类
学习目标
知识点 | 要求 |
---|---|
集合总体结构介绍 | 掌握 |
List实现类 | 掌握 |
Set的实现类 | 掌握 |
Map实现类 | 掌握 |
Iterator | 掌握 |
Collections | 掌握 |
一. 集合介绍
1. 介绍
集合又称容器。是Java中对数据结构(数据存储方式)的具体实现。
我们可以利用集合存放数据,也可以对集合进行新增、删除、修改、查看等操作。
集合中数据都是在内存中,当程序关闭或重启后集合中数据会丢失。所以集合是一种临时存储数据的容器。
2. JDK中集合结构图(常见面试题)
集合作为一个容器,可以存储多个元素,但是由于数据结构的不同,java提供了多种集合类。将集合类中共性的功能,不断向上抽取,最终形成了集合体系结构。
常用实现类:ArrayList, LinkedList, HashSet(Linked记录顺序), HashMap(Linked记录顺序)
2.1 List接口和Set接口
List和Set的父接口
1. List接口:存储有序, 可重复数据。
1. Vector:List的实现类,底层为可变长度数组实现。所有方法都是同步操作(线程安全),每次扩容成本增长。新数组长度为原数组长度的2倍
2. ArrayList:List的实现类,底层为可变长度数组实现。所有方法都是非同步操作(非线程安全的),以1.5倍的方式在扩容。常用于: 查询较多的情况
3. LinkedList:List的实现类,双向非循环链表的实现。常用于: 删 增 较多的情况
2.Set接口:存储无序,不可重复数据。
1. HashSet:Set实现类,底层是HashMap 散列表(数组+链表+(红黑树 jdk1.8及之后))。所有添加到 HashSet 中的元素实际存储到了HashMap的key中
2. LinkedHashSet:HashSet子类. 使用LinkedHashMap来存储它的元素,存储的值插入到LinkedHashMap的可以key中, 底层实现(数组+链表+(红黑树 jdk1.8及之后) + 链表), 可记录插入的顺序
3. TreeSet:Set实现类,底层是TreeMap(红黑树实现), 存入到TreeSet中的元素, 实际存储到了TreeMap中, 根据存储元素的大小可以进行排序
2.2 Map接口
Map:独立的接口,映射。每个元素都包含Key(名称)和Value(要存储的值)两个值。
1. HashMap:Map实现类, 对散列表 (数组+链表+(红黑树Java8及之后))的具体实现,非同步操作(非线程安全的)。存储时以Entry类型存储(key, value)
2. LinkedHashMap: HashMap的子类,是基于HashMap和链表来实现的。在hashMap存储结构之上再添加链表, 链表只是为了保证顺序
3. TreeMap:Map实现类, 使用的不是散列表, 而是对红黑树的具体实现。根据key值的大小, 放入红黑树中, 可实现排序的功能(key值大小的排序)
4. HashTable:Map实现类, 和HashMap数据结构一样,采用散列表(数组+链表+(红黑树 jdk1.8及之后))的方法实现, 对外提供的public函数几乎都是同步的(线程安全)。
常用的集合: ArrayList, HashMap, HashSet
二. Collection接口
**1. **介绍
List和Set接口的父接口, 还有其他的实现类或子接口。
2. 继承关系
3. 包含的API
三. List接口
**1. **介绍
Collection接口的子接口。Collection中包含的内容List接口中可以继承。
List专门存储有序,可重复数据的接口。
2. 包含的API
四. ArrayList
**1. ** 介绍
实现了List接口, 底层实现可变长度数组。存储有序、可重复数据, 有下标。
**2. ** 实例化
常用向上转型进行实例化。绝大多数集合都支持泛型,如果不写泛型认为泛型是,使用集合时建议一定要指定泛型。
List<泛型类型> 对象 = new ArrayList<>();
**3. ** 内存结构图
4. 常用API
public class TestArrayList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
增
list.add("zwx");//添加到尾部
list.add(0,"htt");//添加到指定的位置
删
list.remove("zwx");//根据指定元素删除
list.remove(0);//根据指定下标删除
改
list.set(0,"aaa");
查
//遍历
for (String s : list) {//list可以看出是String[]
System.out.println(s);
}
//遍历
for (int i = 0; i <list.size(); i++) {
System.out.println(list.get(i));//得到list下标i对应的元素
}
//判断
int inx = list.indexOf("wx");//元素存在, 返回元素的下标. 不存在, 返回-1。虽然有zwx,但并没有wx,返回false
boolean bool = list.contains("wx");//元素存在, 返回true. 不存在, 返回false。虽然有zwx,但并没有wx,返回false
//查看元素个数
System.out.println(list.size());
//得到元素(根据下标)
System.out.println(list.get(1));
}
}
remove()方法强调
五. 泛型为集合类型
1. 介绍
在集合中泛型都是任意引用类型。既然是任意引用类型,也可以是集合类型。
2. 实例化语法
//例如:
List<List<Integer>> list = new ArrayList<>();
3. 内存结构图
4. 代码示例
public class Test {
public static void main(String[] args) {
List<List<Integer>> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
List<Integer> list3 = new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
list3.add(11);
list3.add(22);
list3.add(33);
//把list2,list3加到list1
list1.add(list2);
list1.add(list3);
for (List<Integer> itgList : list1) {//list1可以看成是List<Integer>[]类型
for (int i : itgList) {//itgList可以看成是int[]类型或Integer[]类型,故前面可以写成int i或Integer i
System.out.print(i + " ");
}
}
}
}
5.<? extends E>
泛型中<? extends E> 代表:集合只要是E类型或E类型的子类都可以。表示上界是E,? 继承 People
表示集合中的元素上限是People,即只能是People或People的子类,所以下面的赋值是合法的,编译时候不会报错:
但不能是其父类,否则编译时候就报错了:
6.<? super E>
泛型中<? super E> 代表:集合只要是E类型或E类型的父类都可以。表示下界是Man,?超越Man
该表示给出了集合中元素的下限是Man,即只能为Man或者Man的父类,而不能是Man的子类,如下:
六. LinkedList
1. 介绍
LinkedList是Java中对双向非循环链表的实现。实现了List接口。
具有ArrayList所有常用方法,额外还添加了头尾操作方法(实现了Deque接口),这些方法在List接口中是不存在的,所以如果希望使用这些头尾操作方法,实例化时不用向上转型。
故一般使用LinkedList时不向上转型
2. 实例化语法
LinkedList<泛型> 对象 = new LinkedList<>();
3. 常用API
ArrayList里面常用API在LinkedList中都可以使用。
下面的演示为LinkedList比ArrayList多的常用方法。
public class TestLinkedList {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
//增
//在头结点新加
linkedList.addFirst("aa");
linkedList.addFirst("bb");
//在尾结点新加
linkedList.addLast("cc");
linkedList.addLast("dd");
//删
//删头结点,没有头结点则java.util.NoSuchElementException
linkedList.removeFirst();
//删头结点,没有头结点则返回null
linkedList.pollFirst();
//String s1 = linkedList.removeFirst(); //返回被删除的元素
//System.out.println("本次删除的是:" + s1);
//删尾结点,没有尾结点则java.util.NoSuchElementException
linkedList.removeLast();
//删尾结点,没有尾结点则返回null
linkedList.pollLast();
//查
//获取头结点 没有则 java.util.NoSuchElementException
String first1 = linkedList.getFirst();
//获取头结点 没有则返回null
String first2 = linkedList.peekFirst();
//获取尾结点
String last1 = linkedList.getLast();
//获取尾结点
String last2 = linkedList.peekLast();
//遍历
// for (String str:linkedList) { //linkedList可以看成是String[]类型
// System.out.println(str);
// }
}
}
**问题1:**将ArrayList替换成LinkedList之后,变化的是什么?
底层的结构变了
ArrayList:数组 LinkedList:双向非循环链表
问题2:到底是使用ArrayList还是LinkedList
根据使用场合而定
大量的根据索引查询的操作,大量的遍历操作(按照索引0–n-1逐个查询一般),建议使用ArrayList
如果存在较多的添加、删除操作,建议使用LinkedList
问题3:LinkedList增加了哪些方法
增加了对添加、删除、获取(增,删,查)首尾元素的方法
addFirst()、addLast()、removeFirst()、removeLast()、getFirst()、getLast()
七、Java中栈和队列的实现类
Vector过时了,被ArrayList替代了,Stack也就过时了
public class Stack<E> extends Vector<E>
Deque和Queue的实现类,用的非常少了解即可
1.ArrayDeque 顺序栈 数组
2.LinkedList 链栈 链表
public interface Queue<E> extends Collection<E>
public interface Deque<E> extends Queue<E>
1. 早期的栈结构实现类 Stack
public class Test1 {
public static void main(String[] args) {
//创建栈对象
Stack<String> stack = new Stack<>();
//入栈,先进后出
stack.push("马云");
stack.push("马化腾");
stack.push("李彦宏");
stack.push("马老师");
System.out.println(stack);//输出栈
//弹出栈顶元素
String pop = stack.pop();
System.out.println("弹出" + pop);
System.out.println(stack);//输出栈
}
}
2.Queue单端队列
public class Test2 {
public static void main(String[] args) {
Queue<String> q=new LinkedList<String>();
// 入队,先进先出
q.offer("张三丰");
q.offer("张翠山");
q.offer("张无忌");
System.out.println(q);
// 出队 取出队首
String poll = q.poll();
System.out.println(poll);
System.out.println(q);
}
}
3.Deque双端队列
public class TestLinkedList2 {
public static void main(String[] args) {
//创建Deque双端队列对象
Deque<String> dq = new LinkedList<>();
//入队
dq.offerFirst("盘子1");//队首入队
dq.offerLast("盘子2");//队尾入队
//出队
dq.pollFirst();//队首出队
dq.pollLast();//队尾出队
System.out.println(dq.size());//输出队列中有多少个元素
}
}
八. Set接口
**1. **介绍
Set继承了Collection接口。继承的都是Collection中的方法, 没有提供额外方法。
Set经常称为实现无序, 不重复数据集合, 指的就是HashSet实现类
2. 包含API
九. HashSet
1. 介绍
完全基于HashMap(数组+链表+(红黑树))实现的。
存储无序, 无下标, 元素不重复数据。
2. 代码示例
public class TestHashSet {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
//创建HashSet对象
Set<Integer> set = new HashSet<>();
增
set.add(11);
set.add(22);
set.add(33);
//将其他集合中的元素添加到set集合中
list.add(3);list.add(4);list.add(4);
set.addAll(list);
删
//删除指定元素11
set.remove(11);
;
查
for (Integer integer : set) {
System.out.print(integer + " ");
}
//也可以直接用sout输出
System.out.println(set);
//是否包含指定元素
System.out.println(set.contains(22));
//元素个数
System.out.println(set.size());
}
}
十. TreeSet
**1. **介绍
底层是基于TreeMap红黑树。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("zwx");list.add("htt");
//创建TreeSet对象
Set<String> treeSet = new TreeSet<>();
增
//添加元素
treeSet.add("abc");
treeSet.add("abc");//不能添加相同的元素
treeSet.add("fgh");
treeSet.add("jkl");
System.out.println(treeSet);
//将其他集合中元素添加到set集合中
treeSet.addAll(list);
System.out.println(treeSet);
删
//删除指定元素“abc”
treeSet.remove("abc");
System.out.println(treeSet);
查
for (String str:
treeSet) {
System.out.println(str);
}
//是否包含指定元素
System.out.println(treeSet.contains("abc"));
//元素个数
System.out.println(treeSet.size());
}
2.使用Set集合分别存储学生对象
public class TestSet2 {
public static void main(String[] args) {
//创建一个集合对象
Set<Student> set1 = new HashSet<Student>();
Set<Student> set2 = new LinkedHashSet<Student>();
Set<Student> set3 = new TreeSet<Student>();//
//测试,set1,set2都运行没问题,set3直接异常无法运行
set1.add(new Student(2,"zwx",24,99));
set1.add(new Student(3,"htt",23,89));
set1.add(new Student(1,"smh",23,100));
set1.add(new Student(1,"smh",23,100));
System.out.println(set1.size());//set1,set2都4个,set3直接异常无法运行
System.out.println(set1);
}
}
问题1:HashSet、LinkedHashSet :为什么String有重复,会保持唯一;为什么Student有重复,不会保持唯一。
解答1:HashSet、LinkedHashSet 需要Student实现hashCode()和equals()
问题2:TreeSet 为什么String可以添加,而Student就不让添加到TreeSet中呢? 而是抛出异常:
java.lang.ClassCastException: com.bjsxt.entity.Student cannot be cast to java.lang.Comparable
思考:String是系统类,Student是自定义类,应该是String已经做了某些事情,但是Student没有做
解答2:TreeSet 需要Student实现Comparable接口并指定比较的规则
3.重写Student的equals()和hashCode()方法
public class Student implements Comparable<Student>{
private int sno;
private String name;
private int age;
private double score;
@Override
public int compareTo(Student o) {
//return this.sno - o.sno;
//return o.sno - this.sno;
return -(this.sno - o.sno);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (sno != student.sno) return false;
if (age != student.age) return false;
if (Double.compare(student.score, score) != 0) return false;
return name != null ? name.equals(student.name) :
student.name == null;
}
@Override
public int hashCode() {
int result;
long temp;
result = sno;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
temp = Double.doubleToLongBits(score);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
4.比较器Comparator的作用和使用
import java.util.Objects;
public class Student implements Comparable<Student> {
private int age;
private String name;
private String sex;
public Student(int age, String name, String sex) {
this.age = age;
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
@Override
public int compareTo(Student o) {
int a= this.age-o.age;
return a;
}
}
十一. Map接口
**1. **接口
Map是独立的接口。和Collection没有关系。
Map中每个元素都是Entry类型,每个元素都包含Key(键)和Value(值)
2. 继承关系
3. 包含的API
十二. HashMap
**1. **介绍
HashMap是对散列表的具体实现。
里面都包含Key-Value值。
2. 代码演示
public class TestHashMap {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
增
map.put("aa",11);
map.put("bb",22);
map.put("cc",33);
map.put("cc",33);//不会重复增加
map.put(null,44);
删
//根据key 删除键值对 返回被删除的值
int delete = map.remove("cc");
查
//获取存储键值个数
map.size();
//是否包含指定的key
map.containsKey("bb");
//是否包含指定的值
map.containsValue(11);
//根据key获取value
map.get("bb");
///遍历
//键遍历
Set<String> strings = map.keySet();
for (String key : strings) {//快捷键key.for
System.out.println("key->" + key + " value->" + map.get(key));
}
//值遍历
Collection<Integer> values = map.values();
for (Integer value : values) {//快捷键value.for
System.out.println(value);
}
//键值遍历
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
十三. TreeMap
1. 简介
红黑树的具体实现。
2. 代码示例
总体和HashMap使用非常类型
public class TestTreeMap {
public static void main(String[] args) {
Map<Integer, String> treeMap = new TreeMap<>();
treeMap.put(2, "bb");
treeMap.put(1, "aa");
treeMap.put(3, "cc");
for (Map.Entry<Integer, String> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
十四. Iterator
1. 简介
中文名称:迭代器。是一个接口,每个集合中实现类都对Iterator提供了内部类的实现。
通过Iterator可以实现遍历集合的效果。
存在意义:
隐藏集合实现细节,无论是哪种集合都是通过Iterator进行操作,而不是直接操作集合。通过一套API实现所有集合的遍历。
可以在遍历时删除集合中的值。
2. 实例化
每个实现类都提供了.iterator();返回值就是迭代器对象。
public class TestIterator {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//获取集合的迭代器
Iterator<Integer> iterator = list.iterator();
//.hasNext() 判断是否有下一个元素
while (iterator.hasNext()) {
//获取下一个元素
Integer next = iterator.next();
System.out.println(next);
}
}
}
3. ConcurrentModificationException
在循环遍历集合时,向集合中插入值或删除集合中值时会出现这个异常。
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (Integer integer : list) {
list.remove(integer);
}
}
}
异常截图:
为什么使用for(int i =0;i<list.size();i++){}时不出现这个异常?
因为这种循环其实是多次执行get,调用get()方法时,集合其他元素删除或新增是没有要求的。而增强for循环是把集合看做一个整体,在遍历集合时,不允许对整个集合进行操作。
4. 遍历集合时删除元素内容
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//获取集合的迭代器
Iterator<Integer> iterator = list.iterator();
//.hasNext() 判断是否有下一个元素
while (iterator.hasNext()) {
//获取下一个元素
Integer next = iterator.next();
//删除当前元素
iterator.remove();
}
}
}
总结:
- Iterator专门为遍历集合而生,集合并没有提供专门的遍历的方法
Iterator实际上迭代器设计模式的实现
-
哪些集合可以使用Iterator遍历
层次1:Collection、List、Set可以、Map不可以
层次2:提供iterator()方法的就可以将元素交给Iterator;
层次3:实现Iterable接口的集合类都可以使用迭代器遍历
-
for-each循环和Iterator的联系
for-each循环(遍历集合)时,底层使用的是Iterator
-
for-each循环和Iterator的区别
for-each还能遍历数组,Iterator只能遍历集合
使用for-each遍历集合时不能删除元素,会抛出异常ConcurrentModificationException使用Iterator遍历合时能删除元素
5.ListIterator
ListIterator和Iterator的关系
- public interface ListIterator extends Iterator
- 都可以遍历List
ListIterator和Iterator的区别
- 使用范围不同
- Iterator可以应用于更多的集合,Set、List和这些集合的子类型。
- ListIterator只能用于List及其子类型。
public class TestListIterator {
public static void main(String[] args) {
//创建一个集合对象
List<Integer> list = new ArrayList<Integer>();
//向集合中添加分数
list.add(78);
list.add(80);
list.add(89);
ListIterator<Integer> lit = list.listIterator();
while(lit.hasNext()){
lit.next();
}
while(lit.hasPrevious()){
int elem = lit.previous();
System.out.println(elem +" "+lit.nextIndex()+" "+lit.previousIndex());
}
}
}
十五. Collections
1. 介绍
Collections是一个工具类型,一个专门操作集合的工具类。
2. 代码示例
public class TestCollections {
public static void main(String[] args) {
//添加元素
List<Integer> list = new ArrayList();
Collections.addAll(list, 10, 50, 30, 90, 85, 100);//6
System.out.println(list);
//排序
Collections.sort(list);//默认按照内部比较器
System.out.println(list);
//查找元素(元素必须有序)
int index = Collections.binarySearch(list, 500);//不存在返回负数
System.out.println(index);
//获取最大值和最小值
int max = Collections.max(list);
int min = Collections.min(list);
System.out.println(max + " " + min);
//填充集合
Collections.fill(list, null);
System.out.println(list);
//复制集合
List list2 = new ArrayList();
Collections.addAll(list2, 10, 20, 30, 50);
System.out.println(list2);
Collections.copy(list, list2);//dest.size >= src.size 目标列表的长度至少必须等于源列表。
System.out.println(list);
//同步集合
//StringBuffer 线程安全效率低 StringBuilder 线程不安全,效率高
//Vector 线程安全 效率低 ArrayList 线程不安全,效率高
//难道是要性能不要安全吗,肯定不是。
//在没有线程安全要求的情况下可以使用ArrayList
//如果遇到了线程安全的情况怎么办
//方法1:程序员手动的将不安全的变成安全的
//方法2:提供最新的线程安全并且性能高的集合类
List list3 = new ArrayList();
Collections.addAll(list3, 10, 90, 30, 40, 50, 23);
System.out.println(list3);
//将list3转换成线程安全的集合类
list3 = Collections.synchronizedList(list3);
//下面再操作,就线程安全了
}
}
十六. 综合实战案例 - 简易电话本
要求:
1. 所有控制台输入、输出代码只能出现在SystemService的实现类中。
2. 每个人必须输入两个电话号码,并且不可以重复。
3. 查看电话本、录入联系人、删除联系人、修改联系人手机号码等所有功能都是联系人的相关功能。
包含的功能:
1. 查看电话本功能
a) 先查看所有的联系人
b) 选择具体的联系人, 再查看其电话号码
2. 录入联系人功能
3. 删除联系人功能