【4-3】《Java中所有集合》——链表和二叉树、Collection、List、Set、Map、Iterator迭代器、集合在JDK9中的新特性

    科技2022-07-21  132

    此文包含Java中所有集合知识点:使用方式和代码,还有各种注意事项

    集合一、类集概述二、链表和二叉树1、链表2、二叉树 三、常见数据结构1、栈2、队列3、数组4、链表5、红黑树 四、Collection接口(重点)1、List接口(重点)1.ArrayList集合(重点)2.Vector集合(重点)3.ArrayList类与Vector类的区别(重点)4.LinkedList集合(理解) 2、Set接口(重点)1.HashSet集合(重点)2.TreeSet集合(重点)3.TreeSet与Comparable(重点) 其他 五、集合输出(重点)1、Iterator迭代器(重点)2、forEach(理解) 六、Map接口(重点)1、HashMap集合(重点)2、Hashtable集合(重点)3、Map集合各个子类区别分析(重点)4、TreeMap集合(理解)5、存储自定义对象6、Map集合的输出 七、集合在JDK9中的新特性 总结

    集合

    一、类集概述

    集合是数据的容器。 java 对数据结构成熟的实现。

    Collection和Map是同一级的,一个是单值存储,一个是双值存储,接下来的内容中会重点学习。

    链表和二叉树都属于数据结构。

    前期Java程序员不需要过于关注数据结构,因为Java内置了一套成熟的数据结构

    二、链表和二叉树

    1、链表

    链表节点

    class Node { Object data; Node next; }

    链表,linked list:按特定的顺序链接在一起的抽象数据类型。

    数组的优点

    存取速度快

    数组的缺点:

    事先必须知道数组的长度插入删除元素很慢空间通常是有限制的需要大块连续的内存块插入删除元素的效率很低

    链表的优点

    空间没有限制插入删除元素很快

    链表的缺点

    存取速度很慢

    可以通过练习编写链表的增删遍历代码对链表进行深入了解。

    2、二叉树

    链表只有下一个,二叉树有左下一个和右下一个。

    二叉树节点

    class Node { Object data; Node left; Node right; }

    通常二叉树都是有序的,根节点向下分叉,存储的数据与根节点作比较,比根节点小存到左下,反之存到右下。之后每存储一个数据就按照这个存储顺序依次向下分叉。

    二叉树的遍历方式

    先序遍历

    先访问根节点,然后访问左节点,最后访问右节点,顺序:中左右

    中序遍历

    先访问左节点,然后访问根节点,最后访问右节点,顺序:左中右

    后序遍历

    先访问左节点,然后访问右节点,最后访问根节点,顺序:左右中

    可以通过练习编写二叉树的遍历代码对二叉树进行深入了解。

    三、常见数据结构

    数据存储常用结构有:栈、队列、数组、链表和红黑树。

    1、栈

    stack,又称堆栈,是一种限定存储的结构。限定仅在整个结构的尾部进行添加、删除操作的线性表。

    特点:

    先进后出:例如将子弹压入弹夹之中,先压进去的子弹在下面,后压进去的子弹在上面,开枪时先弹出上面的子弹,后弹出下面的子弹。栈的入口和出口都是栈的顶端位置

    此处有两个名词:

    压栈:存元素

    弹栈:取元素

    栈在Java类集中用得不多。

    2、队列

    queue,就像排队,先排的先走。队列是一种特殊的线性表,只允许在表的一端插入,另一端删除。

    特点:

    先进先出:例如火车过山洞,车头先进去、车尾后进去;车头先出来,车尾后出来。队列的入口、出口各占一侧。有单端队列和双端队列。

    3、数组

    Array,有序的元素序列。

    特点:

    查找元素通过索引找,特别快。增删元素慢:需要创建新的数组,根据下标进行增加和删除,进行数据的移动

    4、链表

    linked list,由一系列节点node组成,每一个节点除了要存储数据,还要存储下一个节点的位置,像一个链链接起来。分为单向、双向链表和单向、双向循环链表。

    特点:

    查找元素慢:依次查找增删元素快:只需要修改连接下个元素的地址

    5、红黑树

    二叉树:binary tree,每个节点不超过2的有序树。红黑树又称平衡二叉树。

    特点:

    尽量保持平衡,不会将数据只存在一端

    速度特别快,趋近于平衡,查找叶子元素最少和最多次数不多于二倍

    四、Collection接口(重点

    单值存储从此处开始。

    在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。此接口定义在 java.util 包中。单值存储。用得最多的是List和Set接口。

    Collection接口的常用方法:

    No.方法名称类型描述1public booleanadd(Ee)普通向集合中插入一个元素2public boolean addAll(Collection<?extends E> c)普通向集合中插入一组元素3public void clear()普通清空集合中的元素4public boolean contains(Object o)普通查找一个元素是否存在5public boolean containsAll(Collection<?> c)普通查找一组元素是否存在6public boolean isEmpty()普通判断集合是否为空7public Iterator<E> iterator()普通为 Iterator 接口实例化8public boolean remove(Object o)普通从集合中删除一个对象9boolean removeAll(Collection<?> c)普通从集合中删除一组对象10boolean retainAll(Collection<?> c)普通判断是否没有指定的集合 11public intsize()普通求出集合中元素的个数12public Object[] toArray()普通以对象数组的形式返回集合中的全部内容13<T>T[]toArray(T[] a)普通指定操作的泛型类型,并把内容返回14public boolean equals(Object o)普通从 Object 类中覆写而来15public int hashCode()普通从 Object 类中覆写而来

    为什么要学习集合?

    因为集合是Java中成熟的数据结构的实现,使用集合可以更合理地存储数据。便于对数据更好地进行管理。

    1、List接口(重点

    List中所有的内容都允许重复。

    对接口Collection的扩充方法:

    No.方法名称类型描述1public void add(int index,E element)普通在指定位置处增加元素2boolean addAll(int index,Collection<?extends E>c)普通在指定位置处增加一组元素3 public E get(int index)普通根据索引位置取出每一个元素4public int indexOf(Object o)普通根据对象查找指定的位置,找不到返回-15public int lastIndexOf(Object o)普通从后面向前查找位置,找不到返回-16public List Iterator<E> list Iterator()普通返回 List Iterator7public List Iterator<E> listIterator(int index)普通返回从指定位置的 ListIterator 接口的实例8public E remove(int index)普通删除指定位置的内容9public E set(int index,E element)普通修改指定位置的内容10List<E> subList(int fromIndex,int toIndex)普通返回子集合

    List中有许多实现类:ArrayList、Vector和LinkedList(双向链表)。

    Vector是ArrayList早期实现,ArrayList线程不安全,Vector线程安全。(线程后续学习)

    1.ArrayList集合(重点

    ArrayList是List接口的子类:有序、可重复。使用数组结构,增删慢、查找快。

    创建数组:

    ArrayList<Integer> data = new ArrayList<>();

    接下来查看ArrayList的源码,我们看到ArrayList创建的数组,初始值为一个常量。

    /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

    再点进去这个常量,我们看到它是一个长度为0的数组。

    /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    **添加数据:**用上面的data调用add方法

    data.add(100); 存储时需要存储Integer类型数据,传入的int型会自动装箱为Integer类型数据。此时数组长度为0,若想存储数据,需要进行扩容,而add就对数据进行扩容操作。

    点开add源码,结果return的是true。

    /** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { modCount++; add(e, elementData, size); return true; } 只要调用add方法,永远返回true。

    再点开add中传入3个参数的add方法:

    /** * This helper method split out from add(E) to keep method * bytecode size under 35 (the -XX:MaxInlineSize default value), * which helps when add(E) is called in a C1-compiled loop. */ private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; } 如果目前寸的数据和数组长度一致,说明存满了,此时用grow方法对其进行扩容,重新赋值给elementData。

    最后点开grow方法,看到底是如何对数组进行扩容的。

    /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero */ private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); } private Object[] grow() { return grow(size + 1); } 如果数组满了,最少给数组长度+1。定义一个新的数组长度,再使用Arrays.copyOf将旧数组的值,通过新的数组长度,赋值给旧数组。

    查看newCapacity方法,用来计算新的长度:

    /** * Returns a capacity at least as large as the given minimum capacity. * Returns the current capacity increased by 50% if that suffices. * Will not return a capacity greater than MAX_ARRAY_SIZE unless * the given minimum capacity is greater than MAX_ARRAY_SIZE. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero */ private int newCapacity(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity <= 0) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }

    新数组长度为: oldCapacity + oldCapacity >> 1

    >> 1指二进制右移一位,相当于除2,0.5倍。

    这里的默认长度DEFAULT_CAPACITY为10:

    /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;

    获取集合中的数据:使用get

    data.get(0);

    打印集合:调用的是toString方法

    System.out.println(data);

    熟悉ArrayList的每个方法,并整理笔记。

    2.Vector集合(重点

    不是vector冲锋枪,是可增长的对象数组。也是List接口的子类,从Java 1.2开始改进,可以实现List接口。

    与ArrayList很像,存取数据方式一样,都通过size获取总长度。

    使用:

    Vector<Integer> vector = new Vector(); vector.add(1); vector.add(2); vector.add(3); System.out.println(vector); 运行结果:

    3.ArrayList类与Vector类的区别(重点

    Vector是线程安全的,ArrayList是线程不安全的。

    No.区别点ArrayListVector1时间是新的类,是在 JDK1.2 之后推出的是旧的类是在 JDK1.0 的时候就定义的2性能性能较高,是采用了异步处理性能较低,是采用了同步处理3输出支持 Iterator、ListIterator 输出除了支持 Iterator、ListIterator 输出,还支持 Enumeration 输出

    4.LinkedList集合(理解)

    使用的是双向链表结构,增删快,查找慢。使用几率很低。

    除了add、remove、get等,LinkedList还有特殊方法,可以当作栈、队列结构进行使用。

    addFirst(E e) 在此列表的开头插入指定的元素getFirst() 返回此列表中的第一个元素removeFirst() 从此列表删除并返回第一个元素

    使用:

    LinkedList<Integer> linkedList = new LinkedList<>(); linkedList.addFirst(222); linkedList.addFirst(111); linkedList.addFirst(333); linkedList.addFirst(666); Integer i = linkedList.removeFirst(); System.out.println(i); System.out.println(linkedList);

    可以用addFirst进行压栈,实际更像队列

    运行结果:

    真正的压栈、弹栈方法:

    //压栈 linkedList.push(2); linkedList.push(8); System.out.println(linkedList); //弹栈 Integer p = linkedList.pop(); System.out.println(p); 运行结果:

    看需求选择使用双端、单端、栈、队列等方式,但在实际应用中较少使用到。

    2、Set接口(重点

    继承自Collection接口,本身方法与Collection接口中方法基本一致,仅增加一点使用不太多的方法。是单值存储结构的顶级父接口。

    Set特性:无序,不可重复

    Set在存储数据时不包含重复元素,null也只能放一个,模拟了数学中的集。

    可变对象用作set元素可能会导致一些问题。

    Set接口的两个常用子类:HashSet、TreeSet

    1.HashSet集合(重点

    无get方法,想要获取数据要用toArray将其转为数组再遍历,还有就是用迭代器对其进行迭代。内部使用了哈希表(学习Map集合时了解)的存储方式,HashSet 属于散列的存放类集,里面的内容是无序存放的。

    查看HashSet源码:

    private transient HashMap<E,Object> map;

    看到其中96行有一个HashMap

    创建一个HashSet对象:调用add方法

    HashSet<String> set = new HashSet<>(); set.add("12315");

    点进add方法查看源码:

    /** * Adds the specified element to this set if it is not already present. * More formally, adds the specified element {@code e} to this set if * this set contains no element {@code e2} such that * {@code Objects.equals(e, e2)}. * If this set already contains the element, the call leaves the set * unchanged and returns {@code false}. * * @param e element to be added to this set * @return {@code true} if this set did not already contain the specified * element */ public boolean add(E e) { return map.put(e, PRESENT)==null; } 看到是使用map对数据进行添加,重新利用了双值存储的数据结构

    HashSet存储自定义类型元素 给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方 式,才能保证HashSet集合中的对象唯一

    2.TreeSet集合(重点

    采用有序的二叉树进行存储。

    TreeSet<String> data = new TreeSet(); //基于ASCII码 //A : 65 data.add("A"); data.add("bb"); data.add("C"); data.add("dd"); for (String i : data) { System.out.println(i); } 运行结果:按照ASCII码进行排序

    3.TreeSet与Comparable(重点

    测试TreeSet是否可以对类中的对象进行排序:假设已经创建了Person类,其中有in age和String name两个属性

    TreeSet<Person> set = new TreeSet<>(); Person p1 = new Person(18, "小木"); Person p2 = new Person(15, "小帆"); set.add(p1); set.add(p2); for (Person p:set) { System.out.println(p); }

    创建两个Person对象,使用forEach对其遍历输出:

    运行时出错,原因:“class wct.day12.Person cannot be cast to class java.lang.Comparable”,没有实现Comparable方法

    什么是Comparable?

    就是可以对对象比较大小的一种方法。

    想要改正上述错误,需要对Person类实现Comparable接口:要加一个比较的泛型 ,并实现抽象方法CompareTo

    public class Person implements Comparable<Person>{ private int age; private String name; @Override public int compareTo(Person o) { //返回的数据:可以是负数(this小)、0(相等)、正数(this大) if(this.age > o.age) { return 1; } else if (this.age == o.age) { return 0; } return -1; } //get、set方法 } 此时再对Person类对象进行遍历输出,运行结果:

    注意:如果发现两个内容一样大,就不会进行存储

    所有单值存储结束。

    其他

    Comparator比较器:指定规则的排列 public int compare(String o1, String o2) :比较其两个参数的顺序。

    两个对象比较的结果有三种:大于,等于,小于。

    如果要按照升序排序

    则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)

    如果要按照降序排序

    则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

    public class CollectionsDemo3 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); list.add("cba"); list.add("aba"); list.add("sba"); list.add("nba"); //排序方法 按照第一个单词的降序 Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.charAt(0) - o1.charAt(0); } }); System.out.println(list); } }

    运行结果:

    [sba, nba, cba, aba]

    简述Comparable和Comparator两个接口的区别

    Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码 实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进 行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

    Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构 (如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

    五、集合输出(重点

    1、Iterator迭代器(重点

    现在需要遍历一个集合,使用for循环,如果遍历一个ArrayList,效率可观;但如果是一个LinkedList集合,那效率就不是最优的,此时可以使用迭代器来循环。

    迭代器(2种)

    Iterator:迭代Collection下的所有集合(如List、Set)

    ListIterator:只能迭代List集合

    Iterator(绝对重点

    通过集合对象调用iterator方法:拿到Iterator对象

    Iterator<Integer> iterator = data.iterator();

    iterator有许多方法:

    变量和类型方法描述default voidforEachRemaining(Consumer<? super E> action)对每个剩余元素执行给定操作,直到处理完所有元素或操作引发异常。booleanhasNext()如果迭代具有更多元素,则返回 true 。Enext()返回迭代中的下一个元素。default voidremove()从底层集合中移除此迭代器返回的最后一个元素(可选操作)。 //使用迭代器对集合中的数据进行遍历 Iterator<Integer> iterator = data.iterator(); while (iterator.hasNext()) { Integer i = iterator.next(); System.out.println(i); } 运行结果

    特殊的remove:必须用next获取数据,才能remove,否则会报错

    iterator.next(); iterator.remove();

    ListIterator(理解)

    是Iterator的一个子接口。

    变量和类型方法描述voidadd(E e)将指定的元素插入列表(可选操作)。booleanhasNext()如果此列表迭代器在向前遍历列表时具有更多元素,则返回 true 。booleanhasPrevious()如果此列表迭代器在反向遍历列表时具有更多元素,则返回 true 。Enext()返回列表中的下一个元素并前进光标位置。intnextIndex()返回后续调用 next()将返回的元素的索引。Eprevious()返回列表中的上一个元素并向后移动光标位置。intpreviousIndex()返回后续调用 previous()将返回的元素的索引。voidremove()从列表中删除 next()或 previous() (可选操作)返回的最后一个元素。voidset(E e)用指定的元素替换 next()或 previous()返回的最后一个元素(可选操作)。

    要想使用迭代器遍历数据必须将指针归0:

    ListIterator<Integer> listIterator = data.listIterator(); listIterator.add(666); listIterator.next(); listIterator.next(); listIterator.set(888); listIterator.previous(); listIterator.previous(); listIterator.previous(); while (listIterator.hasNext()) { System.out.println(listIterator.next()); } 运行结果:

    2、forEach(理解)

    增强for循环,最早出现在C#中。在Java中用于迭代数组或集合。

    tips: 新for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。不能在遍历过程中进行增删等操作。

    语法格式:

    for(元素的数据类型 变量 : Collection集合or数组) { //写操作代码 }

    遍历数组:

    public static void main(String[] args) { int arr[] = {1,2,5,6,8,9}; for (int a : arr) {//a表示数组中的每个元素 System.out.println(a); } } 运行结果:

    遍历集合:

    ArrayList<Integer> arrayList = new ArrayList<Integer>(); arrayList.add(1); arrayList.add(2); arrayList.add(3); arrayList.add(4); arrayList.add(5); for (Integer a : arrayList) { System.out.println(a); } 运行结果:

    六、Map接口(重点

    Map集合存储的是一个个的键值对数据。里面的所有内容都按照 key->value 的形式保存,也称为二元偶对象。

    特点:key不可重复,value可重复

    No.方法名称类型描述1void clear()普通清空Map集合中的内容2boolean containsKey(Objectkey)普通判断集合中是否存在指定的 key3boolean containsValue(Objectvalue)普通判断集合中是否存在指定的 value4Set<Map.Entry<K,V>> entrySet()普通将 Map 接口变为 Set 集合5V get(Objectkey)普通根据 key 找到其对应的 value6boolean isEmpty()普通判断是否为空7Set<K> keySet()普通将全部的 key 变为 Set 集合8Collection<V> values()普通将全部的 value 变为 Collection 集合9V put(Kkey,Vvalue)普通向集合中增加内容10void putAll(Map<? extends K,? extends V>m)普通增加一组集合11V remove(Object key)普通根据 key 删除内容

    我们学到的set集合的内部使用了map集合,存储数据时只使用了map集合的key来存储数据,做到内容不重复。

    将键映射到值的对象。 map不能包含重复的键;每个键最多可以映射一个值。

    常用方法:

    clear:清空集合,删除所有映射

    keySet:想要遍历map集合,把key单独作为set集合返回,先取出键,再使用get方法

    get:返回键对应的值

    put:存储一个键或一个值

    putAll:存储一组键值

    remove:可以传入键和值来删除,若是键值不匹配无法删除;也可以只传key来删除,此时值是多少没有关系,返回一个与键关联的v

    具体方法查看API了解

    1、HashMap集合(重点

    HashMap集合是Map集合的子类,基于哈希表map接口的实现,数据结构是哈希表。

    什么是哈希表?

    在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一 个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率 较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

    简单来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,红黑树更利于查找。

    哈希桶中的数据量大于8时,从链表转换为红黑树;从大于8减少到6时,又会从红黑树转换为链表。

    初始数量:16;散列因子:0.75(意思:当16个桶有75%的存数据了,就对桶扩容一倍)

    注意:为提高性能,初始容量要提供合理,避免频繁散列。

    查看HashMap源码:

    能看到第236行有一个默认值1 << 4,其结果就是16。第248行有一个默认值0.75f。

    查看put方法:

    /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with {@code key}, or * {@code null} if there was no mapping for {@code key}. * (A {@code null} return can also indicate that the map * previously associated {@code null} with {@code key}.) */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } 传入键值,先计算哈希值,将值放进putVal中进行运算。数据存放在哪由键来决定。

    查看putVal方法:

    逻辑为下图:

    1.HashMap的使用: 在存储时不保证存储顺序

    HashMap<String, String> map = new HashMap<>(); map.put("key","233"); String value = map.get("key"); System.out.println(value); 运行结果:

    2.ConcurrentHashMap的使用: 与HashMap一样。

    ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(); map.put("key1","22"); map.put("key2","33"); map.put("key3","233"); /* String value = map.get("key3"); System.out.println(value);*/ Set<String> val = map.keySet(); for (String i:val) { System.out.println(i + ":" + map.get(i)); } 运行结果:

    3.LinkedHashMap的使用: 使用方式与HashMap一样,但能保证存储顺序。

    存储的数据既在哈希表中又在一个双向链表中。

    2、Hashtable集合(重点

    旧的子类。Hashtable 是一个最早的 key->value 的操作类,本身是在 JDK1.0的时候推出的。其基本操作与 HashMap 是类似的。

    Hashtable的使用:

    Hashtable<String, String> map = new Hashtable<>(); map.put("key1","22"); map.put("key2","33"); map.put("key3","233"); Set<String> val = map.keySet(); for (String i:val) { System.out.println(i + ":" + map.get(i)); } 运行结果:顺序不一样。

    3、Map集合各个子类区别分析(重点

    都是存储数据的容器

    No.区别点HashMapHastableConcurrentHashMap1推出时间JDK1.2之后推出的,新的操作类JDK1.0时推出的,旧的操作类-2性能异步处理,性能较高同步处理,性能较低-3null允许设置为 null不允许设置,否则将出现空指向异常-4线程不安全,效率高安全,效率低采用分段锁机制,保证线程安全,效率比较高

    4、TreeMap集合(理解)

    排序的子类,和TreeList类似,如果使用自定义类,需要支持排序接口Comparable。自动进行排序。

    使用方式与HashMap一样。

    5、存储自定义对象

    自定义Book类:注意重写hashCode方法

    package xxx; import java.util.Objects; /** * @author Elvira * @date 2020/10/4 21:35 * @description */ public class Book { private String name; private String info; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Book book = (Book) o; return Objects.equals(name, book.name) && Objects.equals(info, book.info); } @Override public int hashCode() { return Objects.hash(name, info); } public Book() { } public Book(String name, String info) { this.name = name; this.info = info; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", info='" + info + '\'' + '}'; } }

    程序入口:

    package xxx; import java.util.HashMap; /** * @author Elvira * @date 2020/10/4 21:35 * @description */ public class BookTest { public static void main(String[] args) { HashMap<Book, String> map = new HashMap<>(); Book book = new Book("小苹果","苹果种植"); map.put(book, "第一本书"); System.out.println(map.get(book)); } } 运行结果:

    当对象作为hash表的键进行存储时,就不要随意改变,否则HashCode值会改变。特别是自定义对象。 当一个对象被存入HashSet中,若是由于改变而找不到对象,也无法从HashSet集合中删除对象,容易造成内存泄漏。

    java.lnag.Object 中对 hashCode 的约定(很重要):

    在一个应用程序执行期间,如果一个对象的 equals 方法做比较所用到的信息没有被修改的话,则对该对象调用 hashCode 方法多次,它必须始终如一地返回同一个整数。如果两个对象根据 equals(Objecto)方法是相等的,则调用这两个对象中任一对象的 hashCode 方法必须产生相同的整 数结果。如果两个对象根据 equals(Objecto)方法是不相等的,则调用这两个对象中任一个对象的 hashCode 方法,不要求产生 不同的整数结果。但如果能不同,则可能提高散列表的性能。

    在 java 的集合中,判断两个对象是否相等的规则是:

    (1)判断两个对象的 hashCode 是否相等

    如果不相等,认为两个对象也不相等,完毕

    如果相等,转入 2

    (为提高存储效率,理论上没有也可以,如果没有,实际使用时效率会大大降低)

    (2)判断两个对象用 equals 运算是否相等

    如果不相等,认为两个对象也不相等

    如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

    6、Map集合的输出

    使用Map.Entry 接口,Map 的内部接口。此内部接口使用 static 进行定义, 所以此接口将成为外部接口。

    No.方法名称类型描述1K getKey()普通得到 key2V getValue()普通得到 value

    使用方式:

    Map<String, String> map = new HashMap<String, String>(); map.put("ZS", "张三"); map.put("LS", "李四"); map.put("WW", "王五"); map.put("ZL", "赵六"); map.put("SQ", "孙七"); // 变为Set实例 Set<Map.Entry<String, String>> set = map.entrySet(); Iterator<Map.Entry<String, String>> iter = set.iterator(); while (iter.hasNext()) { Map.Entry<String, String> me = iter.next(); System.out.println(me.getKey() + " --> " + me.getValue()); } Map 集合中每一个元素都是 Map.Entry 的实例,只有通过 Map.Entry 才能进行 key 和 value 的分离操作。

    七、集合在JDK9中的新特性

    创建固定长度(不可变)的集合:List、Set、Map,子类没有,只有这三个接口有新特性。

    of方法:List、Set、Map中都有相应的of方法

    具体内容可打开API进行查看

    总结

    类集就是一个动态的对象数组,可以向集合中加入任意多的内容。List 接口中是允许有重复元素的,Set 接口中是不允许有重复元素。map存放一组数据,通过键值对key->value进行存储所有的重复元素依靠 hashCode()和 equals 进行区分Map 使用 Iterator 输出,必须通过 Map.Entry 进行 key 和 value 的分离操作
    Processed: 0.011, SQL: 8