目录
集合是什么
集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用。
单列集合Collection
单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。
List<E>
特点
子接口添加的元素是(存取)有序、可重复、有索引。
ArrayList<E>
底层原理
1.利用空参构造器创建的集合,会在底层创建一个默认长度为0的数组,名字叫elementdata
2.用add方法添加第一个元素时,底层会将数组扩容为10(底层通过grow()方法会自动为elementdata数组扩容)
3.当数组存满(再存第11个数据)时,底层会再次通过grow方法完成elementdata数组的扩容,将数组改为之前的1.5倍
特点
数组实现,查询快,增删慢
应用场景
适合根据索引查询数据,比如根据随机索引取数据,或者数据量不是很大时
不适合数据量大的同时频繁的进行增删操作
LinkedList<E>
底层原理
链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
特点
1.基于双链表实现的。
2.查询慢,无论查询哪个数据都要从头开始找。
3.链表增删相对快
常用方法
public void addFirst(E e)//在该列表开头插入指定的元素
public void addLast(E e)//将指定的元素追加到此列表的末尾
public E getFirst()//返回此列表中的第一个元素
public E getLast()//返回此列表中的最后一个元素
public E removeFirst()//从此列表中删除并返回第一个元素
public E removeLast()//从此列表中删除并返回最后一个元素
应用场景
队列特点
先进先出,后进后出
栈特点
后进先出,先进后出
数据进入栈模型的过程称为:压/进栈(push)
数据离开栈模型的过程称为:弹/出栈(pop)
Set<E>
特点
子接口添加的元素是添加数据的顺序和获取出的数据顺序不一致(存取)无序、不重复、无索引。
注意
Set要用到的常用方法,基本上就是Collection提供的!!自己几乎没有额外新增一些常用功能!
实际上:Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
前置知识
哈希值
方法:public int hashCode():返回对象的哈希码值。
特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的。
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
含义
是一个int类型的数值,Java中每个对象都有一个哈希值。Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
哈希表
JDK8之前,哈希表 = 数组+链表 新元素存入数组,占老元素位置,老元素挂下面
JDK8开始,哈希表 = 数组+链表+红黑树 新元素直接挂在老元素下面
JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树进一步提高了操作数据的性能,如果数组块占满了,会出现链表过长,导致查询性能降低,就得扩容。
详细流程
1.创建一个默认长度16,默认加载因为0.75的数组,数组名table,
2.根据元素的哈希值跟数组的长度计算出应存入的位置
3.判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素, 则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
4.当数组存满到16*0.75=12时,就自动扩容,每次扩容原先数组的两倍
树特点
度:每个节点的子节点数量
树高:树的总层数
二叉树中,任意节点的度<=2
根节点:最顶层的节点
二叉排序树
特点:
小的存左边,大的存右边,一样的不存
存在的问题:
当数据已经是排好序的,导致查询的性能与单链表一样,查询速度变慢!
平衡二叉树
在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。
红黑树
就是可以自平衡的二叉树
一种增删改查数据性能相对都较好的结构
HashSet<E>
特点
(存取)无序、不重复、无索引;
经典代码
Set<Integer> set=new HashSet<>();
底层原理
基于哈希表实现,JDK8之前,哈希表 = 数组+链表,JDK8开始,哈希表 = 数组+链表+红黑树
哈希表是一种增删改查数据,性能都较好的数据结构
深入理解HashSet集合去重复的机制
HashSet集合默认不能对内容一样的两个不同对象去重复!
结论:
由于Object中的方法都用的地址值,如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法比较属性值
LinkedHashSet<E>
特点:
(存取)有序、不重复、无索引。
经典代码:
Set<Integer> set=new LinkedHashSet<>();
底层原理:
基于哈希表(数组、链表、红黑树)实现的。
它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。
TreeSet<E>
特点:
按照大小默认升序(强制)排序、不重复、无索引
经典代码:
Set<Integer> set=new TreeSet<>();
底层原理:
底层是基于红黑树实现的排序。增删改查性能较好
注意:
对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet默认是无法直接排序的
两种方式
方式一:自然排序让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。
方式二:通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则。
public TreeSet(Comparator<? super E> comparator)
两种方式中,关于返回值的规则:
如果认为第一个元素 > 第二个元素 返回正整数即可。
如果认为第一个元素 < 第二个元素返回负整数即可。
如果认为第一个元素 = 第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。
常用方法
public boolean add(E e)//把给定的对象添加到当前集合中
public void clear() //清空集合中所有的元素
public boolean remove(E e)//把给定的对象在当前集合中删除
public boolean contains(Object obj)//判断当前集合中是否包含给定的对象
public boolean isEmpty()//判断当前集合是否为空
public int size()//返回集合中元素的个数。
public Object[] toArray()//把集合中的元素,存储到数组中
遍历方式
迭代器
迭代器是用来遍历单列集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。谁继承Iterator接口,谁就是迭代器对象
Collection集合获取迭代器的方法
Iterator<E> iterator()该迭代器对象默认指向当前集合的第一个元素(当前集合的索引0)
Iterator迭代器中的常用方法
1.boolean hasNext()一般写在while判断中.询问当前位置是否有元素存在,存在返回true ,不存在返回false
2.E next()获取当前位置的元素,并同时将迭代器对象指向下一个元素处。
通过迭代器获取集合的元素,如果取元素越界会出现什么异常
出现NoSuchElementException异常
自带一个remove()方法,指向谁删除谁,不需要我们手动修改索引
迭代器不能共用,自己用自己生成的迭代器,因为相当于自己的索引,被别人用,会改变索引,故不能给别人用
案例
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it,next());}
增强for
格式
for (元素的数据类型 变量名 : 数组或者集合) {
//在此处使用变量即可,该变量就是元素
}
作用
增强for可以用来遍历集合或者数组。
增强for遍历集合,本质就是迭代器遍历集合的简化写法。
修改增强for中的变量值不会影响到集合中的元素,因为增强for中会创建一个临时变量接收集合中的取出来的元素,即第三方变量。
案例
list.forEach(s -> System.out.println(s);
lambda表达式forEach
JDK 8开始的新技术Lambda表达式,是一种更简单、更直接的方式来遍历集合。
需要使用Collection的如下方法来完成(结合lambda遍历集合)
default void forEach(Consumer<? super T> action)
集合中存储的是元素对象的地址
总结
如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)
如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。
如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
用TreeSet集合,基于红黑树实现。
集合的并发修改异常
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
可用迭代器自带一个remove()方法,指向谁删除谁,不需要我们手动修改索引
增强for循环遍历集合是迭代器遍历集合的简化(增强for封装了迭代器,故不能调用迭代器的方法),则使用增强for循环遍历集合,又删除集合中的数据时,程序也会出现并发修改异常的错误,且无法解决
forEach中封装了增强for,增强for封装了迭代器,故也会报同样错误
怎么保证遍历集合同时删除数据时不出bug?
1.使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
2.如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。
Collection的其他相关知识
可变参数
就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型...参数名称;
特点和好处
特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
好处:常常用来灵活的接收数据。
注意事项
可变参数在方法内部就是一个数组。
一个形参列表中可变参数只能有一个
可变参数必须放在形参列表的最后面
Collections
是一个用来操作集合的工具类,只能支持对List集合进行排序
提供的常用静态方法
public static <T> boolean addAll(Collection<? super T> c, T... elements)
//给集合批量添加元素
public static void shuffle(List<?> list)
//打乱List集合中的元素顺序
public static <T> void sort(List<T> list)
//对List集合中的元素进行升序排序
public static <T> void sort(List<T> list,Comparator<? super T> c)
//对List集合中元素,按照比较器对象指定的规则进行排序
排序方式
public static <T> void sort(List<T> list)
//对List集合中元素按照默认规则排序本方法可以直接对自定义类型的List集合排序,
//但自定义类型必须实现了Comparable接口,指定了比较规则才可以。
public static <T> void sort(List<T> list,Comparator<? super T> c)
//对List集合中元素,按照比较器对象指定的规则进行排序
双列集合Map
认识Map集合
1.Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , ...}, 一次需要存一对数据做为一个元素.
2.Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对集合”
3.Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
体系的特点
Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引。
常用方法
Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的。Map<K , V>
public V put(K key,V value) 添加元素
public int size() 获取集合的大小
public void clear() 清空集合
public boolean isEmpty() 判断集合是否为空,为空返回true , 反之
public V get(Object key) 根据键获取对应值
public V remove(Object key) 根据键删除整个元素
public boolean containsKey(Object key) 判断是否包含某个键
public boolean containsValue(Object value) 判断是否包含某个值
public Set<K> keySet() 获取全部键的集合
public Collection<V> values() 获取Map集合的全部值
遍历方式
键找值
先获取Map集合全部的键,再通过遍历键来找值
public Set<K> keySet() 获取所有键的集合
public V get(Object key) 根据键获取其对应的值
键值对
把“键值对“看成一个整体进行遍历(难度较大)
Map提供的方法
Set<Map.Entry<K, V>> entrySet() 获取所有“键值对”的集合Set< Map.Entry<String, Double> >
Map.Entry提供的方法
K getKey() 获取键
V getValue() 获取值
Lambda
JDK 1.8开始之后的新技术(非常的简单)
default void forEach(BiConsumer<? super K, ? super V> action)
结合lambda遍历Map集合
HashMap
特点
由键决定特点): 无序、不重复、无索引 (用的最多)
底层原理
1.基于哈希表(是一种增删改查数据,性能都较好的集)实现的
public HashSet() { map = new HashMap<>();}
2.利用键计算哈希值,跟值无关
3.实际上:Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
实现键的唯一性的
1.HashMap的键依赖hashCode方法和equals方法保证键的唯一
2.如果键存储的是自定义类型的对象,可以通过重写hashCode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。
LinkedHashMap
特点
由键决定特点):由键决定的特点:有序、不重复、无索引
底层原理
1.基于哈希表实现
2.只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)
3.实际上:原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap。
TreeMap
特点
(由键决定特点):按照大小默认升序排序、不重复、无索引。
底层原理
TreeMap跟TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序
两种方式来指定排序规则
1.让类实现Comparable接口,重写比较规则。
2.TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。
补充知识:集合的嵌套
指的是集合中的元素又是一个集合
Map集合实现类特点
HashMap:元素按照键是无序,不重复,无索引,值不做要求,基于哈希表(与Map体系一致)
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求,基于哈希表
TreeMap:元素只能按照键排序,不重复,无索引的,值不做要求,可以做排序