集合
学习过程
概念 使用(熟悉API) 深入底层原理 项目中的应用
初识
集合就是一组数据的容器
存放多个数据–> 数组、集合
有些集合专门用于存放数据
有些集合专门用去排序
有些集合专门去重
集合框架图
标红为已经学习的内容,其他内容待学习补充
含义
- 集合是Java API所提供的一系列类,可以用于动态存放多个对象 (集合只能存对象)
- 集合与数组的不同在于,集合是大小可变的序列,而且元素类型可以不受限定,只要是引用类型。(集合中不能放基本数据类型,但可以放基本数据类型的包装类)
- 集合类全部支持泛型,是一种数据安全的用法。
集合与数组的不同
数组:一旦初始化后长度不可变,元素类型受限定(String类型的数组只能装String的数据),数组可以存储基本数据类型
集合:长度可变的序列,元素类型不受限定(一个集合可以存储多个数据类型的元素),集合只能存储引用数据类型
Collection家族
规范了添加删除
List接口
特点:有序且可重复(因为List接口中添加了许多针对下标操作的方法)
实现类:
- ArrayList
- LinkedList
- Vector
- Stack
1.ArrayList
接触ArrayList
感受一个集合可以存储多个类型的数据
经验:平时不会将多个数据类型的数据存入一个集合中,因为不好管理
ArrayList list = new ArrayList(); list.add(100);//Integer.valueOf(100) list.add(123.123);//Double.valueOf(123.123) list.add("abc");
使用
注意:集合一旦定义了泛型,就规定了集合存储元素的类型,所以说泛型是一种数据安全的做法
public static void main(String[] args) {
//创建ArrayList集合的对象
ArrayList<String> list = new ArrayList<>();
//添加元素
list.add("麻生希");
list.add("椎名空");
list.add("水菜丽");
list.add("朝桐光");
list.add("三上悠亚");
list.add("水野朝阳");
list.add("古川伊织");
//设置指定下标上的元素
list.set(1, "奇男子");
//获取指定下标上的元素
String str = list.get(1);
System.out.println("获取指定下标上的元素:" + str);//奇男子
//获取元素个数
int size = list.size();
System.out.println("获取元素个数:" + size);//7
//将元素添加到指定下标的位置
list.add(4, "北条麻衣");
ArrayList<String> newList1 = new ArrayList<>();
Collections.addAll(newList1, "aaa","bbb","ccc","ccc");//利用Collections工具类给集合做批量添加
list.addAll(newList1);//将newList1中所有的元素添加到list集合的末尾
ArrayList<String> newList2 = new ArrayList<>();
Collections.addAll(newList2, "xxx","yyy","zzz","zzz");//利用Collections工具类给集合做批量添加
list.addAll(4, newList2);//将newList2中所有的元素添加到list集合指定下标的位置
//清空集合中所有的元素
//list.clear();
System.out.println("判断集合中是否包含指定元素:" + list.contains("奇男子"));//true
System.out.println("判断集合中是否包含子集合中所有的元素:" + list.containsAll(newList1));//true
System.out.println("获取集合中第一次出现该元素的下标:" + list.indexOf("ccc"));//13
System.out.println("获取集合中最后一次出现该元素的下标:" + list.lastIndexOf("ccc"));//14
System.out.println("判断集合中是否没有元素:" + list.isEmpty());//false
list.remove(0);//根据下标删除元素
list.remove("朝桐光");//根据元素删除元素
list.removeAll(newList1);//删除list中包含newList1的元素(去除交集)
list.retainAll(newList2);//保留list中包含newList2的元素(保留交集)
List<String> subList = list.subList(1, 3);//截取开始下标(包含)到结束下标(排他)处的元素,返回新的List集合
//将集合转成数组
Object[] objs = subList.toArray();
System.out.println(Arrays.toString(objs));
//将集合转成数组
String[] ss = new String[2];
subList.toArray(ss);
System.out.println(Arrays.toString(ss));
System.out.println("--------------------------------");
//遍历数据 -- for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("--------------------------------");
//遍历数据 -- foreach
for (String element : list) {
System.out.println(element);
}
System.out.println("--------------------------------");
//遍历数据 -- Iterator
Iterator<String> it = list.iterator();
while(it.hasNext()){//判断是否有可迭代的元素
String element = it.next();//返回下一个元素
System.out.println(element);
}
System.out.println("--------------------------------");
//遍历数据 -- ListIterator
ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()){//判断是否有可迭代的元素
String element = listIterator.next();//返回下一个元素
System.out.println(element);
}
day23
2.LinkedList
LinkedList和ArrayList使用是一模一样的,这要归功于Conllection和List接口
因为接口是定义标准的!!!
只不过LinkedList和ArrayList底层实现原理是不一样的,LinkedList底层使用双向链表去实现对于数据的操作(增删改查)
LinkedList独有的方法
LinkedList<String> list = new LinkedList<>();
list.add("麻生希");
list.add("abc");
list.add("椎名空");
list.add("abc");
list.add("水菜丽");
list.add("abc");
list.add("朝桐光");
//将元素添加到首部
list.addFirst("奇男子");
list.offerFirst("小小");
list.push("大大");
//将元素添加到尾部
list.addLast("xxx");
list.offerLast("yyy");
list.offer("zzz");
System.out.println("获取第一个元素:" + list.element());
System.out.println("获取第一个元素:" + list.getFirst());
System.out.println("获取第一个元素:" + list.peek());
System.out.println("获取第一个元素:" + list.peekFirst());
System.out.println("获取最后一个元素:" + list.getLast());
System.out.println("获取最后一个元素:" + list.peekLast());
list.pop();//删除第一个元素
list.poll();//删除第一个元素
list.pollFirst();//删除第一个元素
list.removeFirst();
list.pollLast();//删除最后一个元素
list.removeLast();//删除最后一个元素
list.removeFirstOccurrence("abc");//删除第一次出现的元素
list.removeLastOccurrence("abc");//删除最后一个出现的元素
//倒序遍历
// Iterator<String> descendingIterator = list.descendingIterator();
// while(descendingIterator.hasNext()){//hasNext()底层是判断是否有上一个元素
// String element = descendingIterator.next();//next()底层是获取上一个元素
// System.out.println(element);
// }
System.out.println("----------------------------");
for (String element : list) {
System.out.println(element);
}
LinkedList 的 队列模式(先进先出)
代码添加、删除元素形式呈现模式,体会其特征
LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("eee");
// 遍历
// for (String element : list) {
// System.out.println(element);
// }
//取出来
while(!list.isEmpty()){
String element = list.removeFirst();//删除第一个元素,并返回
System.out.println(element);
}
System.out.println("获取元素个数:" + list.size());//0
}
LinkedList 的 栈模式(先进后出 或 后进先出)
while(!list.isEmpty()){
String element = list.removeLast();//删除最后一个元素,并返回
System.out.println(element);
}
3.Vector
Vector:ArrayList的数据结构一致(一维数组),但是Vector是线程安全的(加锁)
Vector和ArrayList使用是一模一样的,这要归功于Conllection和List接口因为接口是定义标准的!!!
只不过Vector和ArrayList底层实现原理是不一样的,Vector底层使用一维数组+线程安全去实现对于数据的操作(增删改查)
知识点:研究Vector的老方法
Vector的历史:
Vector是元老级别的类(JDK1.0),集合框架是JDK1.2开始才推出的概念,
当时大部分程序员已经习惯Vector去存储数据,为了更好的推广集合框架和保留Vector,Java让Vector又多实现了List接口经验:老的方法大多数使用element这个单词
Vector<String> v = new Vector<>();
v.addElement("aaa");
v.addElement("bbb");
v.addElement("ccc");
v.addElement("ddd");
v.addElement("eee");
//清空
//v.removeAllElements();
v.removeElementAt(0);//根据下标删除元素
v.removeElement("ccc");//根据元素删除元素
v.setElementAt("奇男子", 1);//将元素设置到指定下标的位置
Enumeration<String> elements = v.elements();
while(elements.hasMoreElements()){
String element = elements.nextElement();
System.out.println(element);
}
4.Stack
知识点:研究Stack的特点 – 栈模式(先进后出)
注意:该类继承Vector,所以Stack可以使用父类所有的非私有的方法
Stack特有的方法
Stack<String> stack = new Stack<>();
//将元素添加到栈顶
stack.push("111");
stack.push("222");
stack.push("333");
stack.push("444");
stack.push("555");
System.out.println("获取栈顶元素:" + stack.peek());//555
int search = stack.search("222");
System.out.println("获取元素距离栈顶的个数(基于1):" + search);//4
//遍历取出元素
while(!stack.empty()){
//删除栈顶元素,并返回
String element = stack.pop();
System.out.println(element);
}
Set接口
特点:无序且不可重复(没有下标操作)
对于存取
“aaa”,“bbb”,“ccc” | “ccc”,“aaa”,“bbb” 无序不是随机
实现类:
- HashSet
- LinkedHashSet
- TreeSet
1.HashSet
使用
//创建HashSet集合的对象
HashSet<String> set = new HashSet<>();
//添加元素
set.add("麻生希");
set.add("椎名空");
set.add("水菜丽");
set.add("朝桐光");
set.add("三上悠亚");
set.add("水野朝阳");
set.add("古川伊织");
set.add("xxx");
set.add("yyy");
set.add("zzz");
//获取元素个数
int size = set.size();
System.out.println("获取元素个数:" + size);//10
HashSet<String> newSet1 = new HashSet<>();
Collections.addAll(newSet1, "aaa","bbb","ccc","ccc");//利用Collections工具类给集合做批量添加
set.addAll(newSet1);//将newSet1中所有的元素添加到set集合的末尾
//清空集合中所有的元素
//set.clear();
System.out.println("判断集合中是否包含指定元素:" + set.contains("奇男子"));//true
System.out.println("判断集合中是否包含子集合中所有的元素:" + set.containsAll(newSet1));//true
System.out.println("判断集合中是否没有元素:" + set.isEmpty());//false
set.remove("朝桐光");//根据元素删除元素
set.removeAll(newSet1);//删除set中包含newset1的元素(去除交集)
HashSet<String> newSet2 = new HashSet<>();
Collections.addAll(newSet2, "xxx","yyy","zzz","zzz");//利用Collections工具类给集合做批量添加
set.retainAll(newSet2);//保留set中包含newset2的元素(保留交集)
//将集合转成数组
Object[] objs = set.toArray();
System.out.println(Arrays.toString(objs));
//将集合转成数组
String[] ss = new String[set.size()];
set.toArray(ss);
System.out.println(Arrays.toString(ss));
System.out.println("--------------------------------");
//遍历数据 -- foreach
for (String element : set) {
System.out.println(element);
}
System.out.println("--------------------------------");
//遍历数据 -- Iterator
Iterator<String> it = set.iterator();
while(it.hasNext()){//判断是否有可迭代的元素
String element = it.next();//返回下一个元素
System.out.println(element);
}
特点
数据结构:一维数组+单向链表
特点:无序 + 去重无序的原因:存入顺序的规则和取出顺序的规则不一致
去重的原因:元素的hash值相同,再判断hash&&==||equals,如果相同就不存入到集合中
HashSet<String> set = new HashSet<>();
set.add("aaa");
set.add("bbb");
set.add("ccc");
set.add("ddd");
set.add("ddd");
set.add("Aa");
set.add("BB");
for (String element : set) {
System.out.println(element);
}
HashSet理解图
存在hash值相同的情况:字符串"BB"和"Aa"的哈希值是相同的
2.LinkedHashSet
使用
LinkedHashSet 继承 HashSet
特点
数据结构:一维数组+单向链表+双向链表
特点:有序 + 去重有序的原因:在HashSet的基础上添加了双向链表
去重的原因:元素的hash值相同,再判断hash&&==||equals,如果相同就不存入到集合中
LinkedHashSet理解图
泛型
含义:数据安全的做法
泛型限定:
?表示什么类型都可以
? extends A 表示元素必须是A类或A的子类
? super A 表示元素必须是A类或A的父类
public class Test01 {
public static void main(String[] args) {
}
//?表示任何类型
public ArrayList<?> method01(){
//jdk1.7之后对象的泛型可以不写类型,在引用写了可以代表
//ArrayList<Object> list = new ArrayList<>();
//ArrayList<String> list = new ArrayList<>();
//ArrayList<Integer> list = new ArrayList<>();
//ArrayList<A> list = new ArrayList<>();
ArrayList<B> list = new ArrayList<>();
return list;
}
//? extends A 表示A类或其子类
public ArrayList<? extends A> method02(){
//ArrayList<A> list = new ArrayList<>();
ArrayList<B> list = new ArrayList<>();
return list;
}
//? super A 表示A类或其父类
public ArrayList<? super A> method03(){
//ArrayList<A> list = new ArrayList<>();
ArrayList<Object> list = new ArrayList<>();
return list;
}
}
E - element - 元素
T - type - 类型
K - key - 键
V - value - 值
注意:泛型可以设置多个,使用逗号隔开
ps:public class HashMap<K,V>{}
泛型在项目中的应用
需求:编写通用的管理系统的接口
分析:
学生管理系统 – 管理学生
淘宝后台管理系统 – 管理用户
OA管理系统 – 管理员工
//管理系统的接口
public interface ManagerSystem<T> {
public int add(T t);
public int delete(T t);
public int update(T t,int type,Object val);
public int getElementIndex(T t);
public T getElement(T t);
}
例如Book类的 BookManagerSystem实现ManagerSystem接口
public class BookManagerSystem<T> implements ManagerSystem<T> {//外部new对象传入类型
@Override
public int add(T t) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int delete(T t) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int update(T t, int type, Object val) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getElementIndex(T t) {
// TODO Auto-generated method stub
return 0;
}
@Override
public T getElement(T t) {
// TODO Auto-generated method stub
return null;
}
}
public class Test01 {
public static void main(String[] args) {
//在学生管理系统确定泛型
//学生管理系统 -- 管理的是学生对象
StudentManagerSystem sms = new StudentManagerSystem();
sms.add(new Student());
System.out.println("----------------------");
//用户管理系统 -- 管理的是用户对象
UserManagerSystem ums = new UserManagerSystem();
ums.add(new User());
System.out.println("----------------------");
//不在图书管理系统把泛型确定
//图书管理系统 -- 管理的是书名
BookManagerSystem<String> bms1 = new BookManagerSystem<>();
bms1.add("Java从入门到精通");
bms1.add("MySQL从删库到跑路");
//图书管理系统 -- 管理的是图书类别
BookManagerSystem<String> bms2 = new BookManagerSystem<>();
bms2.add("小说类");
bms2.add("科技类");
bms2.add("历史类");
}
}
迭代器
含义:遍历集合中的数据
分类:Iterator 和 ListIterator
Iterator 和 ListIterator 区别
Iterator :Collection接口下所有的实现类都可以获取的迭代器,可以在遍历时删除元素
ListIterator :List接口下所有的实现类可以获取的迭代器,可以在遍历时删除、替换、添加元素,也可以指定下标开始遍历,还可以倒叙遍历
注意:ListIterator继承Iterator
需求:
Iterator
ps
使用Iterator遍历元素,遍历到“bbb”时删除该元素
不能使用ArrayList删除元素的原因:Iterator遍历不知道ArrayList删除元素,数据不统一;注意区分删除功能到底属于ArrayList还是Iterator
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String element = it.next();
if(element.equals("bbb")){
//注意:在Iterator遍历时,不能使用list.remove(),会报错(内外操作数不一致)
//list.remove(element);
//注意:在Iterator遍历时,如果要删除元素,使用迭代器中的remove()
//思路:1.告诉ArrayList需要删除的元素 2.迭代器内部在更新现有数据
it.remove();
}
}
for (String element : list) {
System.out.println(element);
}
游标依靠顺序开始遍历,删除元素
Iterater内–重写remove(借助ArraList的remove(下标))后,下标赋值给游标,下次遍历从删除下标开始;统一内外操作数,把外部操作数赋值给内部操作数
【Iterater没有删除元素的功能,依赖于ArraList的remove(元素)】
底层实现:见【研究Iterator遍历ArrayList集合的底层原理】
ListIterator
ps
使用ListIterator指定下标遍历元素
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
ListIterator<String> listIterator = list.listIterator(2);
while(listIterator.hasNext()){
String element = listIterator.next();
System.out.println(element);
}
使用ListIterator倒序遍历元素
ListIterator<String> listIterator = list.listIterator(list.size());//把指针移动到最后
while(listIterator.hasPrevious()){//判断是否有上一个元素
String element = listIterator.previous();//获取上一个元素
System.out.println(element);
迭代器底层
学习迭代器底层目的:深入理解面向对象的设计思想,应付面试
1.手撕Iterator底层源码
(1).研究foreach/增强for循环
知识点:研究foreach/增强for循环
注意:foreach底层是由Iterator实现
if、for等的代码块只有一个语句,可省略{},但可读性并不是很好
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
for (String element : list) {
System.out.println(element);
}
//foreach遍历集合的底层实现:
// String element;
// for(Iterator it = list.iterator();it.hasNext();System.out.println(element))
// element = (String)it.next();
//初始化变量,判断条件,执行里面代码块
//注意:
// 1.泛型只在编码阶段有效,不会编译到class文件中
// 2.泛型只支持引用数据类型(基本数据类型只能用包装类)
}
(2).研究Iterator遍历ArrayList集合的底层原理
知识点:研究Iterator遍历ArrayList集合的底层原理
注意:
研究底层原理,就势必会看底层源码
底层源码必须找场景去研究,不能从上往下去理解(会越看越乱码)
源码
//场景
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
Iterator<String> it = list.iterator();//《1》《2》
//(多态)ArrayList里的iterator方法返回接口,接口不能有对象,所有new了一个实现类对象
while (it.hasNext()) {
String element = it.next();
System.out.println(element);
}
查看AbstractList底层,重要属性modCount
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
//外部操作数
//作用:记录修改元素的次数(添加、删除会让该变量++)
protected transient int modCount = 0;//modCount - 4+1=5(删除一个元素+1=5(ArrayList底层remove方法))
}
思考题:为什么添加和删除才会把modCount++ ;为什么修改和查询不会把modCount++?
集合用于存储数据、管理数据 -- 增删改查
进行添加或删除操作时,会改变集合的元素个数
学生管理系统为arilist底层
ArrayList添加元素的过程:
1.判断是否扩容(modCount++)
2.将元素添加到一维数组指定下标上的位置
ArrayList删除元素的过程:
添加元素
添加元素 | 外部操作数 | 指针 |
---|---|---|
modCount | size | |
0 | 0 | |
第一个 | 1 | 1 |
第二个 | 2 | 2 |
第三个 | 3 | 3 |
第四个 | 4 | 4 |
已经添加四个元素
遍历(没有增删) | 游标 | 当前元素的下标 | 外部操作数 | 内部操作数 |
---|---|---|---|---|
cursor | lastRet | modCount | expectedModCount | |
0 | -1 | 4 | 4 | |
第一次 | 1 | 0 | ||
第二次 | 2 | 1 | ||
第三次 | 3 | 2 | ||
第四次 | 4 | 3 |
遍历时删除元素’‘bbb’‘
遍历(没有增删) | 游标 | 当前元素的下标 | 外部操作数 | 内部操作数 |
---|---|---|---|---|
cursor | lastRet | modCount | expectedModCount | |
0 | -1 | 4 | 4 | |
第一次 | 1 | 0 | ||
第二次 | 2 | 1 | ||
删除’‘bbb’‘ | 1 | -1 | 4+1=5 | 4==5 |
第三次 | 2 |
public class ArrayList<E> extends AbstractList<E> implements List<E>{
//添加元素
//元素个数
private int size;//size - 4(添加结束)
//数据容器 - ["aaa","bbb","ccc","ddd",null,null,null,null,null,null]
transient Object[] elementData;
//添加元素,e - ddd(添加结束)
public boolean add(E e) {
ensureCapacityInternal(size + 1);//判断是否扩容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//添加一个元素+1
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//ArrayList底层remove方法
public E remove(int index) {
rangeCheck(index);
//利用ArraList类的remove()去删除元素,外部操作数+1
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public Iterator<E> iterator() {
return new Itr();
}
//ArrayList类的内部类 -- 实现了遍历元素的功能《2》
private class Itr implements Iterator<E> {
int cursor; // 游标 - 0+1+1+1+1=4
int lastRet = -1; // 当前元素的下标 - -1+1+1+1+1=3
int expectedModCount = modCount;//内部操作数 - 4==5(删除一个元素外部操作数+1=5(ArrayList底层remove方法),重写的remove方法重新把外部操作数赋值给内部操作数)
//判断是否有可迭代的元素
public boolean hasNext() {
return cursor != size;//4 - 4(遍历结束)
}
@SuppressWarnings("unchecked")
public E next() {
/**
思考题:为什么在获取元素时,会先判断外部操作数是否等于内部操作数
考虑到遍历元素时,如果添加或删除元素,会导致数据个数变化
遍历时就有可能出现脏数据,如果外部操作数和内部操作数不相同就以为数据不统一,就报错!
*/
checkForComodification();//判断外部操作数是否等于内部操作数,如果不等于就报错
int i = cursor;//i - 0+1+1+1
if (i >= size)
throw new NoSuchElementException();
//获取外部类的成员属性 -- elementData
Object[] elementData = ArrayList.this.elementData;//成员内部类访问外部类属性
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
//elementData -- 获取的是ArrayList(外部类的)elementData -> Object[] elementData;
//Object类型的元素强转为集合中真实类型的元素
// --> elementData[3] --> Object类型的数据需要强转为String类型
return (E) elementData[lastRet = i];//elementData[3]
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
//重写了Iterator接口中的remove()
public void remove() {
//判断当前元素的下标是否小于0
if (lastRet < 0)
throw new IllegalStateException();
//判断外部操作数是否和内部操作数一致,不一致就会报错
checkForComodification();
try {
//利用ArraList类的remove()去删除元素
ArrayList.this.remove(lastRet);
//把当前元素的下标赋值给游标
cursor = lastRet;
//把-1赋值给lastRet
lastRet = -1;
//重新把外部操作数赋值给内部操作数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
查看Iterator底层
/**
思考题:为什么迭代器是一个接口,不是类???
因为Java提供了很多的集合,不同的集合实现增删改查的原理是不一样的,
所以,不同的集合都实现了各自遍历元素的代码(实现了各自的迭代器)
*/
//迭代器的接口《1》
public interface Iterator<E> {
//判断是否有可迭代的元素
boolean hasNext();
//获取下一个元素
E next();
//删除元素的默认方法(作用:给实现类去重写,如果实现类可以选择不重写,意味着遍历时不能删除元素,当然实现类可以选择重写,那么就意味着这个迭代器可以在遍历元素时删除元素)
default void remove() {
//抛出异常 -- 删除异常
throw new UnsupportedOperationException("remove");
}
}
2.手撕ListIterator底层源码
ListIterator继承Iterator,部分底层代码同上略
// ListIterator原理实现
public ListIterator<E> listIterator() {
return new ListItr(0);//0 - 设置游标的位置
}
//指定游标位置
public ListIterator<E> listIterator(int index) {
return new ListItr(index);
}
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
//判断是否有上一个元素
public boolean hasPrevious() {
return cursor != 0;
}
//获取下一个元素的下标
public int nextIndex() {
return cursor;
}
//获取上一个元素的下标
public int previousIndex() {
return cursor - 1;
}
//获取上一个元素
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
//设置元素到当前下标上
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//ListIterator的set功能还是依赖于ArrayList
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//添加元素到当前下标上
public void add(E e) {
checkForComodification();
try {
int i = cursor;
//ListIterator的add功能还是依赖于ArrayList
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
比较器接口
作用:排序时使用
分类:
内置比较器:Comparable - compareTo()
外置比较器:Comparator - compare()
使用场景:
内置比较器:对象要想存入TreeSet、TreeMap中,对象所属的类必须要实现内置比较器
外置比较器:当内置比较的规则不满足现在的需求,但又不能改动内置比较器规则时
优先级别:外置比较器 > 内置比较器
注意
- Collection 与 Map的区别
Collection 存单个值,可以获取迭代器进行遍历
Map存两个值(Key-Value)【键值对】,不可以获取迭代器,不能遍历(Map可以间接遍历)
- 理解Set为什么是无序
无序:存入顺序和取出顺序不一致,无序不等于随机
- ArrayList 与 LinkedList的区别
使用上的区别:
LinkedList添加了
队列模式-先进先出(removeFirst())
栈模式-先进后出(removeLast())
效率上的区别:
ArrayList底层数据结构是一维数组
LinkedList底层数据结构是双向链表
添加 - 不扩容的情况:ArrayList快
添加 - 扩容的情况:LinkedList快
删除:LinkedList快
查询:ArrayList快
修改:ArrayList快
注意:工作中常用ArrayList,因为很多需求都需要使用查询功能,ArrayList查询更快
- 各种集合的应用场景
ArrayList:存数据,线程不安全
LinkedList:队列模式、栈模式,线程不安全
Vector:弃用,线程安全
Stack:弃用,线程安全
HashSet:去重+无序,线程不安全
LinkedHashSet:去重+有序,线程不安全
TreeSet:排序,线程不安全
HashMap:存key+value,key去重,无序,线程不安全
LinkedHashMap:存key+value,key去重,有序,线程不安全
Hashtable:弃用,存key+value,key去重,无序,线程安全,方法加锁-效率低
ConcurrentHashMap:存key+value,key去重,无序,线程安全,局部加锁、CAS-效率高
TreeMap:存key+value,针对于Key排序
Properties:配置文件