Java的集合类(collection接口和Map)

一、集合概述

  • 集合:集合是java中提供的一种容器,可以用来存储多个数据。

集合和数组既然都是容器,它们有啥区别呢?

  • 数组的长度是固定的。集合的长度是可变的。
  • 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

Java的集合类是一些非常实用的工具类,主要用于存储和装载数据 (包括对象),因此,Java的集合类也被成为容器。在Java中,所有的集合类都位于java.util包下,这些集合类主要是基于两个根接口派生而来,它们就是 CollectionMap

首先我要说明的是Map不属于Collection接口。

List、Set和Map区别:
List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;List 中存储的数据是有顺序,并
且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,Set 中存储的数据是无
序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的);

二、Collection接口

Collection集合框架图如下:

(图片摘自:https://www.cnblogs.com/janson071/p/9663569.html)

Collection派生出三个子接口,Set代表不可重复的无序集合、List代表可重复的有序集合、Queue是java提供的队列实现,通过它们不断的扩展出很多的集合类。

2.1、Collection 常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e): 把给定的对象添加到当前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。
  • public Iterator<E> iterator() 获取迭代器

还有一些操作整个集合的方法:

boolean containsAll(Collection<?> c)是否包含指定集合 c 的全部元素

boolean addAll(Collection<? extends E> c)添加集合 c 中所有的元素到本集合中,如果集合有改变就返回 true

boolean removeAll(Collection<?> c)删除本集合中和 c 集合中一致的元素,如果集合有改变就返回 true

boolean retainAll(Collection<?> c)保留本集合中 c 集合中两者共有的,如果集合有改变就返回 true

void clear()删除所有元素

方法演示:

import java.util.ArrayList;
import java.util.Collection;public class Demo1Collection {public static void main(String[] args) {// 创建集合对象 // 使用多态形式Collection<String> coll = new ArrayList<String>();// 使用方法// 添加功能  boolean  add(String s)coll.add("小李广");coll.add("扫地僧");coll.add("石破天");System.out.println(coll);// boolean contains(E e) 判断o是否在集合中存在System.out.println("判断  扫地僧 是否在集合中"+coll.contains("扫地僧"));//boolean remove(E e) 删除在集合中的o元素System.out.println("删除石破天:"+coll.remove("石破天"));System.out.println("操作之后集合中元素:"+coll);// size() 集合中有几个元素System.out.println("集合中有"+coll.size()+"个元素");// Object[] toArray()转换成一个Object数组Object[] objects = coll.toArray();// 遍历数组for (int i = 0; i < objects.length; i++) {System.out.println(objects[i]);}// void  clear() 清空集合coll.clear();System.out.println("集合中内容为:"+coll);// boolean  isEmpty()  判断是否为空System.out.println(coll.isEmpty());   }
}

2.2、List接口

2.2.1、概述

List是单列集合的一个重要f分支,习惯性地会将实现了List接口的对象称为List集合。

2.2.1.1、List集合特点:

  • 它是一个元素存取有序的集合,即元素的存入顺序和取出顺序一致。
  • 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
  • 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

2.2.1.2、List接口方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index):返回集合中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
public class ListDemo {public static void main(String[] args) {// 创建List集合对象List<String> list = new ArrayList<String>();// 往 尾部添加 指定元素list.add("图图");list.add("小美");list.add("不高兴");System.out.println(list);// add(int index,String s) 往指定位置添加list.add(1,"没头脑");System.out.println(list);// String remove(int index) 删除指定位置元素  返回被删除元素// 删除索引位置为2的元素 System.out.println("删除索引位置为2的元素");System.out.println(list.remove(2));System.out.println(list);// String set(int index,String s)// 在指定位置 进行 元素替代(改) // 修改指定位置元素list.set(0, "三毛");System.out.println(list);// String get(int index)  获取指定位置元素// 跟size() 方法一起用  来 遍历的 for(int i = 0;i<list.size();i++){System.out.println(list.get(i));}//还可以使用增强forfor (String string : list) {System.out.println(string);}     }
}

2.2.1.3、List的三个子类的特点

  • ArrayList 底层结构是数组,底层查询快,增删慢。
  • LinkedList 底层结构是链表型的,增删快,查询慢。
  • voctor 底层结构是数组 线程安全的,增删慢,查询慢。

2.2.2、ArrayList集合类

ArrayList 底层结构是数组,底层查询快,增删慢。

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

ArrayList是List接口的实现类,一种大小可变数组,随着元素的增多,容量会自动扩充,默认初始容量值是10,也可以自己指定初始容量

(1)采用的数据结构:数组

(线性表:数组、链表、队列、栈
非线性表:二叉树、堆、图等)

(2)ArrayList特点:

  • ArrayList底层使用了Object的数组作为容器去存储数据
  • ArrayList 提供了使用索引的随意访问数据
  • ArrayList 是线程非安全的,效率较高,查询速度高

(3)ArrayList优点:

查询速度快
ArrayList缺点:
新增和删除元素比较慢

(4)查询速度快的原因:

ArrayList底层是数组实现的,根据下标查询,不需要比较,查询方式为,首地址+(元素长度*下标),基于这个位置读取相应的字节数,所以非常快;

(5)新增和删除慢的原因:

增删会带来元素的移动,增加数据会向后移动,删除数据会向前移动,所以影响效率

(6)适用场景:

如果应用程序对数据有较多的随机访问使用ArrayList较好

(7)左父右子

List a=new ArrayList()和 ArrayList a =new ArrayList()的区别?

List list = new ArrayList();这句创建了一个 ArrayList 的对象后把上溯到了 List。此时它是一个 List 对象了,有些
ArrayList 有但是 List 没有的属性和方法,它就不能再用了。而 ArrayList list=new ArrayList();创建一对象则保留了
ArrayList 的所有属性。 所以需要用到 ArrayList 独有的方法的时候不能用前者。实例代码如下:

List list = new ArrayList();
ArrayList arrayList = new ArrayList();
list.trimToSize(); //错误,没有该方法。
arrayList.trimToSize(); //ArrayList 里有该方法。

(8)数组扩容

这是对ArrayList效率影响比较大的一个因素。
每当执行Add、AddRange、Insert、InsertRange等添加元素的方法,都会检查内部数组的容量是否不够了,如果是,它就会以当前容量的两倍来重新构建一个数组,将旧元素Copy到新数组中,然后丢弃旧数组,在这个临界点的扩容操作,应该来说是比较影响效率的。

例1:比如,一个可能有200个元素的数据动态添加到一个以默认16个元素大小创建的ArrayList中,将会经过:
162222 = 256
四次的扩容才会满足最终的要求,那么如果一开始就以:
ArrayList List = new ArrayList( 210 );
的方式创建ArrayList,不仅会减少4次数组创建和Copy的操作,还会减少内存使用。

例2:预计有30个元素而创建了一个ArrayList:
ArrayList List = new ArrayList(30);
在执行过程中,加入了31个元素,那么数组会扩充到60个元素的大小,而这时候不会有新的元素再增加进来,而且有没有调用TrimSize方法,那么就有1次扩容的操作,并且浪费了29个元素大小的空间。如果这时候,用:
ArrayList List = new ArrayList(40);
那么一切都解决了。
所以说,正确的预估可能的元素,并且在适当的时候调用TrimSize方法是提高ArrayList使用效率的重要途径。

(9)为什么说ArryList是线性不安全的

我们首先看看这类所有的部分属性字段:

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{/*** 列表元素集合数组* 如果新建ArrayList对象时没有指定大小,那么会将EMPTY_ELEMENTDATA赋值给elementData,* 并在第一次添加元素时,将列表容量设置为DEFAULT_CAPACITY */transient Object[] elementData; /*** 列表大小,elementData中存储的元素个数*/private int size;
}

通过这两个字段我们可以看出,ArrayList的实现主要就是用了一个Object的数组,用来保存所有的元素,以及一个size变量用来保存当前数组中已经添加了多少元素。

接着我们看下最重要的add操作时的源代码:

public boolean add(E e) {/*** 添加一个元素时,做了如下两步操作* 1.判断列表的capacity容量是否足够,是否需要扩容* 2.真正将元素放在列表的元素数组里面*/ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;
}

ensureCapacityInternal()这个方法的详细代码我们可以暂时不看,它的作用就是判断如果将当前的新元素加到列表后面,列表的elementData数组的大小是否满足,如果size + 1的这个需求长度大于了elementData这个数组的长度,那么就要对这个数组进行扩容。

由此看到add元素时,实际做了两个大的步骤:

  1. 判断elementData数组容量是否满足需求
  2. 在elementData对应位置上设置值

这样也就出现了第一个导致线程不安全的隐患,在多个线程进行add操作时可能会导致elementData数组越界。具体逻辑如下:

  1. 列表大小为9,即size=9
  2. 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。
  3. 线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。
  4. 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
  5. 线程B也发现需求大小为10,也可以容纳,返回。
  6. 线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。
  7. 线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException.

另外第二步 elementData[size++] = e 设置值的操作同样会导致线程不安全。从这儿可以看出,这步操作也不是一个原子操作,它由如下两步操作构成:

  1. elementData[size] = e;
  2. size = size + 1;

在单线程执行这两条代码时没有任何问题,但是当多线程环境下执行时,可能就会发生一个线程的值覆盖另一个线程添加的值,具体逻辑如下:

  1. 列表大小为0,即size=0
  2. 线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
  3. 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
  4. 线程A开始将size的值增加为1
  5. 线程B开始将size的值增加为2

这样线程AB执行完毕后,理想中情况为size为2,elementData下标0的位置为A,下标1的位置为B。而实际情况变成了size为2,elementData下标为0的位置变成了B,下标1的位置上什么都没有。并且后续除非使用set方法修改此位置的值,否则将一直为null,因为size为2,添加元素时会从下标为2的位置上开始。

2.2.3、LinkedList集合类

LinkedList 底层结构是链表型的,增删快,查询慢。

java.util.LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:

  • public void addFirst(E e):将指定元素插入此列表的开头。
  • public void addLast(E e):将指定元素添加到此列表的结尾。
  • public E getFirst():返回此列表的第一个元素。
  • public E getLast():返回此列表的最后一个元素。
  • public E removeFirst():移除并返回此列表的第一个元素。
  • public E removeLast():移除并返回此列表的最后一个元素。
  • public E pop():从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e):将元素推入此列表所表示的堆栈。
  • public boolean isEmpty():如果列表不包含元素,则返回true。

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)

方法演示:

public class LinkedListDemo {public static void main(String[] args) {LinkedList<String> link = new LinkedList<String>();//添加元素link.addFirst("abc1");link.addFirst("abc2");link.addFirst("abc3");System.out.println(link);// 获取元素System.out.println(link.getFirst());System.out.println(link.getLast());// 删除元素System.out.println(link.removeFirst());System.out.println(link.removeLast());while (!link.isEmpty()) { //判断集合是否为空System.out.println(link.pop()); //弹出集合中的栈顶元素}System.out.println(link);}
}

其他1、ArrayList 和 Linkedlist 区别:
ArrayList 和 Vector 使用了数组的实现,可以认为 ArrayList 或者 Vector 封装了对内部数组的操作,比如向数组
中添加,删除,插入新的元素或者数据的扩展和重定向。
LinkedList 使用了循环双向链表数据结构。与基于数组的 ArrayList 相比,这是两种截然不同的实现技术,这也决
定了它们将适用于完全不同的工作场景。
LinkedList 链表由一系列表项连接而成。一个表项总是包含 3 个部分:元素内容,前驱表和后驱表,如图所示:

在下图展示了一个包含 3 个元素的 LinkedList 的各个表项间的连接关系。在 JDK 的实现中,无论 LikedList 是否
为空,链表内部都有一个 header 表项,它既表示链表的开始,也表示链表的结尾。表项 header 的后驱表项便是链表
中第一个元素,表项 header 的前驱表项便是链表中最后一个元素。

其他2、要对集合更新操作时,ArrayList 和 LinkedList 哪个更适合

ArrayList 和 LinkedList 在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下:
1.对 ArrayList 和 LinkedList 而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList 而言,主要是在内部数组中增加一项,指向所添加的元素,偶 尔可能会导致对数组重新进行分配;而对 LinkedList 而言,这个开销是统一的,分配一个内部 Entry 对象。
2.在 ArrayList 的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在 LinkedList 的中间插入或删除一个元素的开销是固定的。
3.LinkedList 不支持高效的随机元素访问。
4.ArrayList 的空间浪费主要体现在在 list 列表的结尾预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList 会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元 素时,就应该使用 LinkedList 了。

2.2.4、Vector

voctor 底层结构是数组 线程安全的,增删慢,查询慢。

Vector非常类似ArrayList,但是Vector是同步的,效率相对比较低

Vector实现了Serializable接口,支持序列化,实现了Cloneable接口,能被克隆,实现了RandomAccess接口,支持快速随机访问。

Vector的底层结构也是数组,但是它们对数组的扩容方式不同

当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样ArrayList就有利于节约内存空间。即Vector增长原来的一倍,ArrayList增加原来的0.5倍。

Stack栈继承于Vector,栈的存储特点是后进先出,它基于动态数组实现的一个线程安全的栈,所以栈是线程安全的

java.util.vector提供了向量类(vector)以实现类似动态数组的功能。在Java语言中没有指针的概念,但如果正确灵活地使用指针又确实可以大大提高程序的质量。比如在c,c++中所谓的“动态数组”一般都由指针来实现。为了弥补这个缺点,Java提供了丰富的类库来方便编程者使用,vector类便是其中之一。事实上,灵活使用数组也可以完成向量类的功能,但向量类中提供大量的方法大大方便了用户的使用。
创建了一个向量类的对象后,可以往其中随意插入不同类的对象,即不需顾及类型也不需预先选定向量的容量,并可以方便地进行查找。对于预先不知或者不愿预先定义数组大小,并且需要频繁地进行查找,插入,删除工作的情况。可以考虑使用向量类

Vector与ArrayList的区别:

  • Vector是线程安全的, ArrayList不是线程安全的, 这是最主要的
  • Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector效率低,不能与ArrayList相比。
  • 两者都是采用线性连续空间存储元素,当空间不足时,两者的扩容方式不同。ArrayList不可以设置扩展的容量, 默认1.5倍; Vector可以设置, 默认2倍
  • ArrayList无参构造函数中初始量为0; Vector的无参构造函数初始容量为10

向量类提供了三种构造方法:

public vector()
public vector(int initialcapacity,int capacityIncrement)
public vector(int initialcapacity)

使用第一种方法系统会自动对向量进行管理,若使用后两种方法。则系统将根据参数,initialcapacity设定向量对象的容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时。系统会扩充向量对象存储容量。
参数capacityincrement给定了每次扩充的扩充值。当capacityincrement为0的时候,则没次扩充一倍,利用这个功能可以优化存储。

在Vector类中提供了各种方法方便用户的使用:
很多方法都加入了synchronized同步语句,来保证线程安全。

插入功能:

(1)public final synchronized void adddElement(Object obj)
将obj插入向量的尾部。obj可以是任何类型的对象。对同一个向量对象,亦可以在其中插入不同类的对象。但插入的应是对象而不是数值,所以插入数值时要注意将数组转换成相应的对象。
例如:要插入整数1时,不要直接调用v1.addElement(1),正确的方法为: Vector v1 = new Vector();
Integer integer1 = new Integer(1); v1.addElement(integer1);

(2)public final synchronized void setElementAt(Object obj,int index)
将index处的对象设置成obj,原来的对象将被覆盖。

(3)public final synchronized void insertElementAt(Object obj,int
index) 在index指定的位置插入obj,原来对象以及此后的对象依次往后顺延。

删除功能:

(1)public final synchronized void removeElement(Object obj)
从向量中删除obj,若有多个存在,则从向量头开始试,删除找到的第一个与obj相同的向量成员。 (2)public final
synchronized void removeAllElement(); 删除向量所有的对象 (3)public fianl
synchronized void removeElementAt(int index) 删除index所指的地方的对象
查询搜索功能:

(1)public final int indexOf(Object obj)
从向量头开始搜索obj,返回所遇到的第一个obj对应的下标,若不存在此obj,返回-1.

(2)public final synchronized int indexOf(Object obj,int index)
从index所表示的下标处开始搜索obj.

(3)public final int lastindexOf(Object obj) 从向量尾部开始逆向搜索obj.

(4)public final synchornized int lastIndex(Object obj,int index)
从index所表示的下标处由尾至头逆向搜索obj.

(5)public final synchornized firstElement() 获取向量对象中的首个obj

(6)public final synchornized Object lastElement() 获取向量对象的最后一个obj

2.3、Set接口

2.3.1、概述

存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的);

它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性(== 或 eqauls)的方式依赖于:hashCodeequals方法。

Set集合取出元素的方式可以采用:迭代器、增强for。

Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法;LinkedHashSet:继承于 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMap)。

2.3.2、List和Set的区别

List , Set 都是继承自 Collection 接口
List 特点:元素有放入顺序,元素可重复
Set 特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(元素虽然无放入顺序,但是元素在set中的位置是由该元素HashCode 决定的,其位置其实是固定的,加入Set 的 Object 必须定义 equals ()方法 ,另外list 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想 要的值。) Set和List对比 Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变

补充:关于迭代器
Java集合框架的集合类,我们有时候称之为容器。容器的种类有很多种,比如ArrayList、LinkedList、HashSet…,每种容器都有自己的特点,ArrayList底层维护的是一个数组;LinkedList是链表结构的;HashSet依赖的是哈希表,每种容器都有自己特有的数据结构。
因为容器的内部结构不同,很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单,Java引入了迭代器模式!
把访问逻辑从不同类型的集合类中抽取出来,从而避免向外部暴露集合的内部结构。
对于数组:

//for循环
int array[] = new int[3];    for (int i = 0; i < array.length; i++) {System.out.println(array[i]);}

对于ArrayList:

//for循环
List<String> list = new ArrayList<String>();for(int i = 0 ; i < list.size() ;  i++){String string = list.get(i);}

对于这两种方式,我们总是都知道它的内部结构,访问代码和集合本身是紧密耦合的,无法将访问逻辑从集合类和客户端代码中分离出来。不同的集合会对应不同的遍历方法,客户端代码无法复用。在实际应用中如何将上面两个集合整合是相当麻烦的。所以才有Iterator,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由Iterator来维护。客户端不用直接和集合进行打交道,而是控制Iterator向它发送向前向后的指令,就可以遍历集合。

对于Set:

package sun.rain.amazing.traversal;import java.util.*;public class TraversalSet {public static void main(String args[]){List<String> list = new ArrayList<>(Arrays.asList("tom","cat","Jane","jerry"));Set<String> set = new HashSet<>();set.addAll(list);//方法1 集合类的通用遍历方式, 从很早的版本就有, 用迭代器迭代Iterator it1 = set.iterator();while(it1.hasNext()){System.out.println(it1.next());}//方法2 集合类的通用遍历方式, 从很早的版本就有, 用迭代器迭代for(Iterator it2 = set.iterator();it2.hasNext();){System.out.println(it2.next());}//方法3 增强型for循环遍历for(String value: set){System.out.println(value);}}}

2.3.3、HashSet类

HashSet数据结构:哈希表(数组+链表)

什么是哈希表呢?
哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。

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

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

看到这张图就有人要问了,这个是怎么存储的呢?

为了方便大家的理解我们结合一个存储流程图来说明一下:

即哈希表存放的是哈希值,HashSet存取元素的顺序并不是按照存入时的顺序(和List显然不同),而是按照哈希值来存取的。元素的哈希值通过元素的hashcode方法来获取。获取hashcode之后,接下来就有三种情况:1、如果哈希值不一样,直接插入。2、hashcode一样,就进一步用equals方法比较其中的值,如果为false,则在同样的哈希值下顺延,进行链表插入(如果链表长度大于阈值,就转化为红黑树)(可以认为哈希值相同的元素放在一个哈希桶中)。3、equals方法也是true,则判断为同一个元素,不插入。

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

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

创建自定义Student类

public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic boolean equals(Object o) {if (this == o)return true;if (o == null || getClass() != o.getClass())return false;Student student = (Student) o;return age == student.age &&Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}
public class HashSetDemo2 {public static void main(String[] args) {//创建集合对象   该集合中存储 Student类型对象HashSet<Student> stuSet = new HashSet<Student>();//存储 Student stu = new Student("于谦", 43);stuSet.add(stu);stuSet.add(new Student("郭德纲", 44));stuSet.add(new Student("于谦", 43));stuSet.add(new Student("郭麒麟", 23));stuSet.add(stu);for (Student stu2 : stuSet) {System.out.println(stu2);}}
}
执行结果:
Student [name=郭德纲, age=44]
Student [name=于谦, age=43]
Student [name=郭麒麟, age=23]

2.3.4、LinkedHashSet类

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?

HashSet下面有一个子类java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。

演示代码如下:

public class LinkedHashSetDemo {public static void main(String[] args) {Set<String> set = new LinkedHashSet<String>();set.add("bbb");set.add("aaa");set.add("abc");set.add("bbc");Iterator<String> it = set.iterator();while (it.hasNext()) {System.out.println(it.next());}}
}
结果:bbbaaaabcbbc

特点:

  • 具有set集合不重复的特点,同时具有可预测的迭代顺序,也就是我们插入的顺序。
  • 是一个非线程安全的集合。如果有多个线程同时访问当前linkedhashset集合容器,并且有一个线程对当前容器中的元素做了修改,那么必须要在外部实现同步保证数据的冥等性。
  • LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素
  • LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

2.3.5、TreeSet类

底层是用二叉树来实现

TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。

TreeSet并不是根据元素的插入顺序进行排序,而是根据元素实际值来进行排序.(可以确保元素唯一并且元素排序) TreeSet采用红黑树的数据结构对元素进行排序.

TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式(Integer类型元素自然升序)。向TreeSet中加入的应该是同一个类的对象。

TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法

2.4、Queue

本节转自:https://www.cnblogs.com/lemon-flm/p/7877898.html

Queue: 基本上,一个队列就是一个先入先出(FIFO)的数据结构

Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接 口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue 继承了Queue接口。

队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向一个 已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞.在多线程进行合作时,阻塞队列是很有用的工具。工作者线程可 以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则第二个 线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。

Queue的实现

1、没有实现的阻塞接口的LinkedList: 实现了java.util.Queue接口和java.util.AbstractQueue接口
  内置的不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue
  PriorityQueue 和 ConcurrentLinkedQueue 类在 Collection Framework 中加入两个具体集合实现。
  PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。
  ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大 小,       ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。

2)实现阻塞接口的:
  java.util.concurrent 中加入了 BlockingQueue 接口和五个阻塞队列类。它实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。
五个队列所提供的各有不同:
  * ArrayBlockingQueue :一个由数组支持的有界队列。
  * LinkedBlockingQueue :一个由链接节点支持的可选有界队列。
  * PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
  * DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
  * SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。

下表显示了jdk1.5中的阻塞队列的操作:

add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
  remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
  element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
  offer 添加一个元素并返回true 如果队列已满,则返回false
  poll 移除并返问队列头部的元素 如果队列为空,则返回null
  peek 返回队列头部的元素 如果队列为空,则返回null
  put 添加一个元素 如果队列满,则阻塞
  take 移除并返回队列头部的元素 如果队列为空,则阻塞

remove、element、offer 、poll、peek 其实是属于Queue接口。

阻塞队列的操作可以根据它们的响应方式分为以下三类:aad、removee和element操作在你试图为一个已满的队列增加元素或从空队列取得元素时 抛出异常。当然,在多线程程序中,队列在任何时间都可能变成满的或空的,所以你可能想使用offer、poll、peek方法。这些方法在无法完成任务时 只是给出一个出错示而不会抛出异常。

注意:poll和peek方法出错进返回null。因此,向队列中插入null值是不合法的

最后,我们有阻塞操作put和take。put方法在队列满时阻塞,take方法在队列空时阻塞。

ArrayBlockingQueue在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。

PriorityBlockingQueue是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。

DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。

三、Map

3.1、关于Map

3.1.1、概述

Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的

现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map接口。

我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图。

  • Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
  • Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
  • Collection中的集合称为单列集合,Map中的集合称为双列集合。
  • 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值

3.1.2、常用子类


HashMap
存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复需要重写键的hashCode()方法、equals()方法。它是最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为Null(多条会覆盖);允许多条记录的值为 Null。非同步的。

LinkedHashMap
HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢。key和value均允许为空,非同步的。

TreeMap
能够把它保存的记录根据键(key)排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。

Hashtable
与 HashMap类似,不同的是:key和value的值均不允许为null;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。

3.1.3、常用方法

Map接口中定义了很多方法,常用的如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • boolean containsKey(Object key) 判断集合中是否包含指定的键。
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

Map接口的方法演示

public class MapDemo {public static void main(String[] args) {//创建 map对象HashMap<String, String>  map = new HashMap<String, String>();//添加元素到集合map.put("黄晓明", "杨颖");map.put("文章", "马伊琍");map.put("邓超", "孙俪");System.out.println(map);//String remove(String key)System.out.println(map.remove("邓超"));System.out.println(map);// 想要查看 黄晓明的媳妇 是谁System.out.println(map.get("黄晓明"));System.out.println(map.get("邓超"));    }
}

tips:

使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;

若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。

3.1.4、Map遍历

3.1.4.1、初始化数据

Map<String, String> map = ``new` `HashMap<String, String>();
map.put(``"key1"``, ``"value1"``);
map.put(``"key2"``, ``"value2"``);

3.1.4.2、增强for循环遍历

(1)使用keySet()遍历

for (String key : map.keySet()) {System.out.println(key + " :" + map.get(key));
}

也叫遍历键找值方式:即通过元素中的键,获取键所对应的值

分析步骤:

  1. 获取Map中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键。方法提示:keyset()
  2. 遍历键的Set集合,得到每一个键。
  3. 根据键,获取键所对应的值。方法提示:get(K key)

代码演示:

public class MapDemo01 {public static void main(String[] args) {//创建Map集合对象 HashMap<String, String> map = new HashMap<String,String>();//添加元素到集合 map.put("胡歌", "霍建华");map.put("郭德纲", "于谦");map.put("薛之谦", "大张伟");//获取所有的键  获取键集Set<String> keys = map.keySet();// 遍历键集 得到 每一个键for (String key : keys) {//key  就是键//获取对应值String value = map.get(key);System.out.println(key+"的CP是:"+value);}  }
}

遍历图解:

(2)使用entrySet()遍历

for (Map.Entry<String, String> entry : map.entrySet()) {System.out.println(entry.getKey() + " :" + entry.getValue());
}

1)Entry键值对对象

我们已经知道,Map中存放的是两种对象,一种称为key(键),一种称为value(值),它们在Map中是一一对应关系,这一对对象又称做Map中的一个Entry(项)Entry将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。

既然Entry表示了一对键和值,那么也同样提供了获取对应键和对应值得方法:

  • public K getKey():获取Entry对象中的键。
  • public V getValue():获取Entry对象中的值。

在Map集合中也提供了获取所有Entry对象的方法:

  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

2)遍历键值对方式

键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。

操作步骤与图解:

  1. 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回。方法提示:entrySet()

  2. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象。

  3. 通过键值对(Entry)对象,获取Entry对象中的键与值。 方法提示:getkey() getValue()

public class MapDemo02 {public static void main(String[] args) {// 创建Map集合对象 HashMap<String, String> map = new HashMap<String,String>();// 添加元素到集合 map.put("胡歌", "霍建华");map.put("郭德纲", "于谦");map.put("薛之谦", "大张伟");// 获取 所有的 entry对象  entrySetSet<Entry<String,String>> entrySet = map.entrySet();// 遍历得到每一个entry对象for (Entry<String, String> entry : entrySet) {// 解析 String key = entry.getKey();String value = entry.getValue();  System.out.println(key+"的CP是:"+value);}}
}

遍历图解:

tips:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。

3.1.4.3、迭代器遍历

使用keySet()遍历

Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {String key = iterator.next();System.out.println(key + " :" + map.get(key));
}

使用entrySet()遍历

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {Map.Entry<String, String> entry = iterator.next();System.out.println(entry.getKey() + " :" + entry.getValue());
}

3.1.4.4、HashMap遍历方式对比:

  1. 增强for循环使用方便,但性能较差,不适合处理超大量级的数据。
  2. 迭代器的遍历速度要比增强for循环快很多,是增强for循环的2倍左右。
  3. 使用entrySet遍历的速度要比keySet快很多,是keySet的1.5倍左右。

3.2、HashMap

3.2.1、HashMap的数据结构

数组+单链表(数组中的每个元素都是单链表的头节点)
使用链表的原因是解决哈希冲突的(即不同的key映射到了数组的同一位置处,就将其放入单链表中)

哈希冲突
如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。

HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。

HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。

HashMap中的key-value都是存储在Entry数组中的。
Entry 实际上就是一个单向链表。这也是为什么我们说HashMap是通过拉链法解决哈希冲突的。
Entry 实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数。这些都是基本的读取/修改key、value值的函数。

简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

在Java8中,如果链表中的元素个数超过了8,则会将链表转化为红黑树

3.2.2、HashMap的构造函数

// 默认构造函数。
HashMap()// 指定“容量大小”的构造函数
HashMap(int capacity)// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor)// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)

3.2.3、主要方法

clear():清空HashMap

containsKey() :判断HashMap是否包含key

containsValue() 的作用是判断HashMap是否包含“值为value”的元素

entrySet()的作用是返回“HashMap中所有Entry的集合”,它是一个集合

get() 的作用是获取key对应的value

put() 的作用是对外提供接口,让HashMap对象可以通过put()将“key-value”添加到HashMap中

putAll() 的作用是将"m"的全部元素都添加到HashMap中

remove() 的作用是删除“键为key”元素

3.2.4、线程安全问题

不是线程安全的;
如果有两个线程A和B,都进行插入数据,刚好这两条不同的数据经过哈希计算后得到的哈希码是一样的,且该位 置还没有其他的数据。所以这两个线程都会进入我在上面标记为1的代码中。假设一种情况,线程A通过if判断,该 位置没有哈希冲突,进入了if语句,还没有进行数据插入,这时候 CPU 就把资源让给了线程B,线程A停在了if语句 里面,线程B判断该位置没有哈希冲突(线程A的数据还没插入),也进入了if语句,线程B执行完后,轮到线程A执 行,现在线程A直接在该位置插入而不用再判断。这时候,你会发现线程A把线程B插入的数据给覆盖了。发生了线 程不安全情况。本来在 HashMap 中,发生哈希冲突是可以用链表法或者红黑树来解决的,但是在多线程中,可能 就直接给覆盖了。
上面所说的是一个图来解释可能更加直观。如下面所示,两个线程在同一个位置添加数据,后面添加的数据就覆盖 住了前面添加的。

如果上述插入是插入到链表上,如两个线程都在遍历到最后一个节点,都要在最后添加一个数据,那么后面添加数 据的线程就会把前面添加的数据给覆盖住。则

3.2.5、HashMap 的扩容过程

当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值—即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。HashMap的数组长度一定是2的次幂
扩容( resize )就是重新计算容量,向 HashMap 对象里不停的添加元素,而 HashMap 对象内部的数组无法装载更 多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然 Java 里的数组是无法自动扩容的,方法 是使用一个新的数组代替已有的容量小的数组,就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。

HashMap hashMap=new HashMap(cap);

cap =3, hashMap 的容量为4;
cap =4, hashMap 的容量为4;
cap =5, hashMap 的容量为8;
cap =9, hashMap 的容量为16;
如果 cap 是2的n次方,则容量为 cap ,否则为大于 cap 的第一个2的n次方的数。

3.2.6、HashMap1.7和1.8的区别

在 JDK1.7 及之前的版本中, HashMap 又叫散列链表:基于一个数组以及多个链表的实现,hash值冲突的时候, 就将对应节点以链表的形式存储。
JDK1.8 中,当同一个hash值( Table 上元素)的链表节点数不小于8时,将不再以单链表的形式存储了,会被 调整成一颗红黑树。这就是 JDK7 与 JDK8 中 HashMap 实现的最大区别。

其下基于 JDK1.7.0_80 与 JDK1.8.0_66 做的分析

JDK1.7中
使用一个 Entry 数组来存储数据,用key的 hashcode 取模来决定key会被放到数组里的位置,如果 hashcode 相 同,或者 hashcode 取模后的结果相同( hash collision ),那么这些 key 会被定位到 Entry 数组的同一个 格子里,这些 key 会形成一个链表。
在 hashcode 特别差的情况下,比方说所有key的 hashcode 都相同,这个链表可能会很长,那么 put/get 操作 都可能需要遍历这个链表,也就是说时间复杂度在最差情况下会退化到 O(n)

JDK1.8中
使用一个 Node 数组来存储数据,但这个 Node 可能是链表结构,也可能是红黑树结构
如果插入的 key 的 hashcode 相同,那么这些key也会被定位到 Node 数组的同一个格子里。
如果同一个格子里的key不超过8个,使用链表结构存储。
如果超过了8个,那么会调用 treeifyBin 函数,将链表转换为红黑树。
那么即使 hashcode 完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销
也就是说put/get的操作的时间复杂度最差只有 O(log n)
听起来挺不错,但是真正想要利用 JDK1.8 的好处,有一个限制:
key的对象,必须正确的实现了 Compare 接口
如果没有实现 Compare 接口,或者实现得不正确(比方说所有 Compare 方法都返回0)
那 JDK1.8 的 HashMap 其实还是慢于 JDK1.7 的

3.3、HashTable

HashTable和HashMap功能类似,都是用来保存键值对,但两者之间又有区别
区别:
1.HashTable中不允许保存null的,而HashMap可以保存空的key和value
2…HashTable是同步的,因此它是线程安全的
HashMap线程不安全
3.Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口

使用举例:

package ThreeWeek;import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;public class HashTableTest {public static void main(String args[]){Hashtable<String, Integer> table = new Hashtable<String, Integer>();//[1]添加元素table.put("zhangsan", 22);table.put("lisi", 33);table.put("wangwu", 44);//[2]toString()方式打印System.out.println(table.toString());//[3]Iterator遍历方式1--键值对遍历entrySet()Iterator<Entry<String, Integer>> iter = table.entrySet().iterator();while(iter.hasNext()){Map.Entry<String, Integer> entry = (Map.Entry<String, Integer>)iter.next();String key = entry.getKey();int value = entry.getValue();System.out.println("entrySet:"+key+" "+value);}System.out.println("====================================");//[4]Iterator遍历方式2--key键的遍历Iterator<String> iterator = table.keySet().iterator();while(iterator.hasNext()){String key = (String)iterator.next();int value = table.get(key);System.out.println("keySet:"+key+" "+value);}System.out.println("====================================");//[5]通过Enumeration来遍历HashtableEnumeration<String> enu = table.keys();while(enu.hasMoreElements()) {System.out.println("Enumeration:"+table.keys()+" "+enu.nextElement());} }
}
{zhangsan=22, lisi=33, wangwu=44}
entrySet:zhangsan 22
entrySet:lisi 33
entrySet:wangwu 44
====================================
keySet:zhangsan 22
keySet:lisi 33
keySet:wangwu 44
====================================
Enumeration:java.util.Hashtable$Enumerator@139a55 zhangsan
Enumeration:java.util.Hashtable$Enumerator@1db9742 lisi
Enumeration:java.util.Hashtable$Enumerator@106d69c wangwu

Hashtable 是遗留类,很多映射的常用功能与HashMap 类似,不同的是它承自Dictionary 类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用HashMap 替换,需要线程安全的场合可以用ConcurrentHashMap 替换。

3.4、LinkedHashMap

多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰,因为有些场景,我们期待一个有序的Map。

这个时候,LinkedHashMap就闪亮登场了,它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序该迭代顺序可以是插入顺序或者是访问顺序。

关 注 点 结 论
LinkedHashMap是否允许空 Key和Value都允许空
LinkedHashMap是否允许重复数据 Key重复会覆盖、Value允许重复
LinkedHashMap是否有序 有序
LinkedHashMap是否线程安全 非线程安全

关于LinkedHashMap:
1、LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。
2、LinkedHashMap的基本实现思想就是----多态。可以说,理解多态,再去理解LinkedHashMap原理会事半功倍;反之也是,对于LinkedHashMap原理的学习,也可以促进和加深对于多态的理解。

3.5、TreeMap

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap 实现了Cloneable接口,意味着它能被克隆。
TreeMap 实现了java.io.Serializable接口,意味着它支持序列化

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

与Map关系:

从图中可以看出:
(01) TreeMap实现继承于AbstractMap,并且实现了NavigableMap接口。
(02) TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量: root, size, comparator。
  root 是红黑数的根节点。它是Entry类型,Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。Entry节点根据key进行排序,Entry节点包含的内容为value。
  红黑数排序时,根据Entry中的key进行排序;Entry中的key比较大小是根据比较器comparator来进行判断的。
  size是红黑数中节点的个数。

3.6、区别比较

3.6.1、HashMap和HashSet区别

3.6.2、HashMap 和 HashTable 区别?

(1)基类不同:HashTable基于Dictionary类,而HashMap是基于AbstractMap。Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。

(2)null不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。

(3)线程安全:HashMap时单线程安全的,Hashtable是多线程安全的。由于非线程安全,HashMap 的效率要较 HashTable 的效率高一些.

(4)遍历不同:HashMap仅支持Iterator的遍历方式,Hashtable支持Iterator和Enumeration两种遍历方式。

(5)实现同步:HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时
候需要自己为它的方法实现同步;

3.6.3、Linkedhashmap 与 hashmap 的区别

  1. LinkedHashMap 是 HashMap 的子类
  2. LinkedHashMap 中的 Entry 增加了两个指针 before 和 after,它们分别用于维护
    双向链接列表。
  3. 在 put 操作上,虽然 LinkedHashMap 完全继承了 HashMap 的 put 操作,但是在细
    节上还是做了一定的调整,比如,在 LinkedHashMap 中向哈希表中插入新 Entry 的同时,还会通过 Entry 的 addBefore 方法将其链入到双向链表中。
  4. 在扩容操作上,虽然 LinkedHashMap 完全继承了 HashMap 的 resize 操作,但是 鉴于性能和 LinkedHashMap 自身特点的考量,LinkedHashMap 对其中的重哈希过 程(transfer 方法)进行了重写
  5. 在读取操作上,LinkedHashMap 中重写了 HashMap 中的 get 方法,通过 HashMap 中的 getEntry 方法获取 Entry 对象。在此基础上,进一步获取指定键对应的值。

四、Collections

4.1 常用功能

  • java.utils.Collections是集合工具类,用来对集合进行操作。部分方法如下:
  • public static <T> boolean addAll(Collection<T> c, T... elements):往集合中添加一些元素。
  • public static void shuffle(List<?> list) 打乱顺序:打乱集合顺序。
  • public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
  • public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。

代码演示:

public class CollectionsDemo {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<Integer>();//原来写法//list.add(12);//list.add(14);//list.add(15);//list.add(1000);//采用工具类 完成 往集合中添加元素  Collections.addAll(list, 5, 222, 1,2);System.out.println(list);//排序方法 Collections.sort(list);System.out.println(list);}
}
结果:
[5, 222, 1, 2]
[1, 2, 5, 222]

代码演示之后 ,发现我们的集合按照顺序进行了排列,可是这样的顺序是采用默认的顺序,如果想要指定顺序那该怎么办呢?

我们发现还有个方法没有讲,public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。接下来讲解一下指定规则的排列。

4.2 Comparator比较器

我们还是先研究这个方法

public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。

不过这次存储的是字符串类型。

public class CollectionsDemo2 {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);System.out.println(list);}
}

结果:

[aba, cba, nba, sba]

我们使用的是默认的规则完成字符串的排序,那么默认规则是怎么定义出来的呢?

说到排序了,简单的说就是两个对象之间比较大小,那么在JAVA中提供了两种比较实现的方式,一种是比较死板的采用java.lang.Comparable接口去实现,一种是灵活的当我需要做排序的时候在去选择的java.util.Comparator接口完成。

那么我们采用的public static <T> void sort(List<T> list)这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能,在String类型上如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使用

public static <T> void sort(List<T> list,Comparator<? super T> )方法灵活的完成,这个里面就涉及到了Comparator这个接口,位于位于java.util包下,排序是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>() {@Overridepublic int compare(String o1, String o2) {return o2.charAt(0) - o1.charAt(0);}});System.out.println(list);}
}

结果如下:

[sba, nba, cba, aba]

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

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

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

4.4 练习

创建一个学生类,存储到ArrayList集合中完成指定排序操作。

Student 初始类

public class Student{private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}

测试类:

public class Demo {public static void main(String[] args) {// 创建四个学生对象 存储到集合中ArrayList<Student> list = new ArrayList<Student>();list.add(new Student("rose",18));list.add(new Student("jack",16));list.add(new Student("abc",16));list.add(new Student("ace",17));list.add(new Student("mark",16));/*让学生 按照年龄排序 升序*/
//        Collections.sort(list);//要求 该list中元素类型  必须实现比较器Comparable接口for (Student student : list) {System.out.println(student);}}
}

发现,当我们调用Collections.sort()方法的时候 程序报错了。

原因:如果想要集合中的元素完成排序,那么必须要实现比较器Comparable接口。

于是我们就完成了Student类的一个实现,如下:

public class Student implements Comparable<Student>{....@Overridepublic int compareTo(Student o) {return this.age-o.age;//升序}
}

再次测试,代码就OK 了效果如下:

Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}
Student{name='ace', age=17}
Student{name='rose', age=18}

4.5 扩展

如果在使用的时候,想要独立的定义规则去使用 可以采用Collections.sort(List list,Comparetor c)方式,自己定义规则:

Collections.sort(list, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o2.getAge()-o1.getAge();//以学生的年龄降序}
});

效果:

Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}

如果想要规则更多一些,可以参考下面代码:

Collections.sort(list, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {// 年龄降序int result = o2.getAge()-o1.getAge();//年龄降序if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序result = o1.getName().charAt(0)-o2.getName().charAt(0);}return result;}});

效果如下:

Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='abc', age=16}
Student{name='jack', age=16}
Student{name='mark', age=16}

五、常考点

1、Collection

(1)List

  1. 存储单列数据
  2. 有序、允许重复
  3. 带有索引,可以通过索引精确操作元素

1)ArrayList

  • 有序、允许重复
  • 底层结构是Object数组,查询快,增删慢,getter和setter方法快;
  • 线程不安全;
  • 当容量不够时,ArrayList是当前容量x1.5+1
  • 无参构造中初始量为0

2)LinkedList

  • 有序,允许重复
  • 底层结构是双向循环链表型,增删快,查询慢,add和remove速度快
  • 线程不安全

3)Vector

  • 有序、允许重复
  • 底层结构是数组,增删慢、查询慢
  • 线程安全,效率低
  • 当容量不够时,默认*2扩展为两倍
  • 无参构造函数初始容量为10(ArrayList初始量为0)

(2)Set

  1. 存储单列数据
  2. 无序、不允许重复(List是有序且允许重复)
  3. Set遍历只能通过迭代器,因为他是无序的,不能用下标来获取想要的值(List还可以用for循环来遍历)

1)HashSet

  • 无序、不允许重复
  • 底层结构是哈希表(数组+链表 ),JDK1.8增加了红黑树部分,当链表长度超过阈值(8)时,将链表变为红黑树
  • 内部是HashMap
  • 存取速度快

2)TreeSet

  • 无序、不允许重复
  • 底层采用二叉树实现
  • 排序存储,不是根据插入顺序,而是根据元素实际值来进行排序(所以说是无序的)
  • 内部是TreeMap的SortSet

3)LinkedHashSet

  • 不允许重复,具有可预测的迭代顺序,也就是我们插入的顺序
  • 根据元素的HashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序,这样使得元素看起来像是以插入顺序保存到,也就是说,当遍历该集合的时候会以元素添加的顺序访问集合的元素
  • 采用哈希表存储,并用双向链表记录插入顺序
  • 内部是LinkedHashMap
  • LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

(3)Queue

在两端出入的List,所以也可以用数组或链表来实现

2、Map

  1. 存储双列数据
  2. 数据没有顺序
  3. 键不能重复,其值可以重复

(1)HashMap

  • 键不可重复,值可重复
  • 底层哈希表
  • 线程不安全
  • 允许key值为null(最多一条为null,多条会覆盖),value也可以为null
  • 由于要保证键的唯一、不重复,需要重写键的hashcode()方法、equals()方法

(2)HashTable

  • 键不可重复,值可重复
  • 底层哈希表
  • 线程安全,因此写入较慢
  • key和value都不允许为null

(3)TreeMap

  • 键不可重复,值可重复
  • 底层二叉树
  • 记录根据键排序,默认升序排序

(4)LinkedHashMap

多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰,因为有些场景,我们期待一个有序的Map。

这个时候,LinkedHashMap就闪亮登场了,它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序该迭代顺序可以是插入顺序或者是访问顺序。

  • 键不可重复,值可重复
  • key和value都允许为null
  • 有序,迭代顺序可以是插入顺序或者是访问顺序
  • 非线程安全
  • 可以看成是HashMap+LinkedList,既可以使用HashMap操作数据结果,又可以使用LinkedList维护元素的小先后顺序

(5)TreeMap

  • 有序
  • 底层是红黑树
  • 非线性安全

3、其他

(1)线程安全的类

  • vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。
  • statck:堆栈类,先进后出
  • hashtable:就比hashmap多了个线程安全
  • enumeration:枚举,相当于迭代器

除了这些之外,其他的都是非线程安全的类和接口。

记忆:线程同步:喂,SHE

喂(Vector)S(Stack)H(hashtable)E(enumeration)

(2)Lish、Set、Map

  1. 单列数据or双列数据:List和Set存储的是单列数据,Map存储的是双列数据
  2. 是否有序:List是有序的,Set和Map是无序的(LinkedHashSet、LinkedHashMap、TreeMap的迭代顺序会按照插入的顺序,TreeSet插入时按照元素大小通过二叉树排序插入)
  3. 是否允许重复:List允许重复,Set不允许重复,Map键不允许重复、值可以重复

(3)Vector与ArrayList的区别:

  • Vector是线程安全的, ArrayList不是线程安全的, 这是最主要的
  • Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector效率低,不能与ArrayList相比。
  • 两者都是采用线性连续空间存储元素,当空间不足时,两者的扩容方式不同。ArrayList不可以设置扩展的容量, 默认1.5倍; Vector可以设置, 默认2倍
  • ArrayList无参构造函数中初始量为0; Vector的无参构造函数初始容量为10

(4)哈希冲突

如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。

(5)HashMap 和 HashTable 区别

(1)基类不同:HashTable基于Dictionary类,而HashMap是基于AbstractMap。Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。

(2)null不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。

(3)线程安全:HashMap时单线程安全的,Hashtable是多线程安全的。由于非线程安全,HashMap 的效率要较 HashTable 的效率高一些.

(4)遍历不同:HashMap仅支持Iterator的遍历方式,Hashtable支持Iterator和Enumeration两种遍历方式。

(5)实现同步:HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步;
补充:

(6)Linkedhashmap 与 hashmap 的区别

  1. LinkedHashMap 是 HashMap 的子类
  2. LinkedHashMap 中的 Entry 增加了两个指针 before 和 after,它们分别用于维护
    双向链接列表。
  3. 在 put 操作上,虽然 LinkedHashMap 完全继承了 HashMap 的 put 操作,但是在细
    节上还是做了一定的调整,比如,在 LinkedHashMap 中向哈希表中插入新 Entry 的同时,还会通过 Entry 的 addBefore 方法将其链入到双向链表中。
  4. 在扩容操作上,虽然 LinkedHashMap 完全继承了 HashMap 的 resize 操作,但是 鉴于性能和 LinkedHashMap 自身特点的考量,LinkedHashMap 对其中的重哈希过 程(transfer 方法)进行了重写
  5. 在读取操作上,LinkedHashMap 中重写了 HashMap 中的 get 方法,通过 HashMap 中的 getEntry 方法获取 Entry 对象。在此基础上,进一步获取指定键对应的值。

(7)数组和链表

内存中的存储形式可以分为连续存储和离散存储两种。因此,数据的物理存储结构就有连续存储和离
散存储两种,它们对应了我们通常所说的数组和链表。

1)数组:

是将元素在内存中连续存储的,所以在查找数据的时候效率比较高。但缺点是在存储之前,就需要申请一块连续的内存空间,并且编译时就必须确定他的空间大小,在运行的时候空间的大小无法随着需要变化。当数据量大的时候会越界,小的时候会导致浪费内存空间,增删改的效率比较低。适合数据量比较少,经常查找的应用场景。

2)链表:

是动态申请内存空间,不需要像数组那样提前申请内存大小,只需在用的时候申请,根据需要动态申请和删除内存空间,增删比较灵活,修改、查询效率比较低。适用于对线性表的长度或者规模难以估摸;频繁做插入删除操作。构建动态性比较强的线性表。
参考:

(8)hashcode()和equals()方法

hashcode()和equals()方法详解

Collection集合类和Map接口各实现类详解相关推荐

  1. java 抽象类,接口,object类详解

    抽象类: 如果一个类没有足够的信息去描绘一个具体的对象,那么这个类就可以叫做抽象类. 也可以叫做:可声明抽象方法的类==>抽象类 注意: <1>抽象类不可以实例化对象,所以它只能被继 ...

  2. Collection 和 Map接口及其实现类总结

    Collection 和 Map接口及其实现类总结 Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Ele ...

  3. Java集合类之Map接口之学生花名册

    Java集合类之Map接口之学生花名册 任务描述 把给定的学生花名册数据添加到 Map 集合中. 相关知识 在 Java 的集合体系中,主要包含 Collection 接口以及 Map 接口,将介绍 ...

  4. Java集合篇:Map接口、Map接口的实现类、Collections集合工具类

    目录 一.Map接口 1.1 Map接口概述 1.2 Map接口常用功能 二.Map接口的实现类 2.1 Map实现类之一:HashMap 2.1.1 HashMap概述 2.1.2 HashMap的 ...

  5. Java讲课笔记23:Map接口及其实现类

    文章目录 零.本讲学习目标 1.了解Map集合的常用方法 2.掌握HashMap和TreeMap的使用 3.掌握Properties集合的使用 4.掌握Map集合遍历 一.Map接口 1.Map接口概 ...

  6. JDBC学习笔记02【ResultSet类详解、JDBC登录案例练习、PreparedStatement类详解】

    黑马程序员-JDBC文档(腾讯微云)JDBC笔记.pdf:https://share.weiyun.com/Kxy7LmRm JDBC学习笔记01[JDBC快速入门.JDBC各个类详解.JDBC之CR ...

  7. [NewLife.XCode]实体类详解

    NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...

  8. basicdatasourcefactory mysql_Java基础-DBCP连接池(BasicDataSource类)详解

    Java基础-DBCP连接池(BasicDataSource类)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 实际开发中"获得连接"或"释放资源 ...

  9. 集合框架之ArrayDeque类详解

    Java集合框架分析(Deque)---ArrayDeque类详解 目录 一.数据结构 二.类标题 三.字段 四.构造函数 五.方法分析 类型 方法 作用 添加元素 public void addFi ...

最新文章

  1. maya如何查看资源大纲_3DMaya大纲视图在哪查看?
  2. python能做什么游戏ll-Python 应该怎么学?
  3. Mouse Without Borders 一套鼠标键盘控制多台电脑
  4. 【codevs30123037】线段覆盖4、5,姗姗来迟
  5. app中的h5游戏分享到微信等第三方
  6. 云计算是商业模式创新而非技术创新
  7. ES6JS笔记以及部分运行问题记录
  8. Richer Convolutional Features for Edge Detection(RCF-PYTorch)部署
  9. 计算机怎黑夜模式么启动,Win10系统电脑夜间模式怎么开启/关闭的方法
  10. 宝宝 天天加班, 有意义吗
  11. 控制windows 8,win8.1 win10 虚拟键盘
  12. TOOD: Task-aligned One-stage Object Detection 原理与代码解析
  13. SQL建表后设定唯一性
  14. cache策略实现的原理(200、304返回码含义)
  15. IOS编译报错:objc-class-ref........
  16. 数字IC后端实现问答(摘选自知识星球本周问答)
  17. 当新到达一个城市的时候,会收到一条该城市欢迎你的短信的原因和原理
  18. 一个用Dijkstra算法实现的路由算法的java程序——9 图信息文件示例
  19. 2020年高级工程师职称评审必备条件!
  20. PC争霸史:崛起、帝国时代与落幕

热门文章

  1. php7 phalcon,GitHub - shyn0121/cphalcon7: Phalcon7 - Web framework for PHP7.x 高性能PHP7框架
  2. MPS和MRP的区别
  3. SCU(System Control Units)
  4. VUE DIFF算法之快速DIFF
  5. 研发组织该如何设计绩效体系?
  6. rpath添加依赖库搜索路径
  7. iOS开发:利用SDWebImage实现图片加载与缓存
  8. 白话数字签名(3)——Web程序中的数字签名
  9. UNIX编程学习——more指令实现
  10. 每日一题python90:找到小镇的法官