【Java 15】集合 - List、Set、Map部分详解

25 篇文章 0 订阅
20 篇文章 0 订阅
本文深入探讨了Java集合框架,包括Collection、Map接口及其子接口如List(ArrayList、LinkedList、Vector)、Set(HashSet、LinkedHashSet、TreeSet)和Map(HashMap、LinkedHashMap、TreeMap)。详细讲解了它们的特性和使用场景,以及各实现类的源码分析,特别是ArrayList和HashMap的存储结构。此外,还涵盖了Iterator迭代器、Collection接口方法、以及Java8的新特性。
摘要由CSDN通过智能技术生成

集合 - List、Set、Map

集合

image-20200822092935083

1 Java集合框架概述

1.1 概述

  • Colletion接口:单列集合,存储一个一个的数据
    • List接口:存储有序的、可重复的数据
      • ArrayList
      • LinkedList
      • Vector
    • Set接口:存储无序的、不可重复的数据
      • HashSet
      • LinkedHashSet
      • TreeSet
  • Map接口:双列集合,存储一对一对(Key - Value)的数据
    • HashMap
    • LinkedHashMap
    • TreeMap
    • Hashtable
    • Properties

image-20200822093338769

  1. 集合、数组都是对多个数据进行存储操作的结构,简称Java容器

    此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储

  2. 数组在内存存储方面的特点:

    • 数组初始化以后,长度就确定了
    • 数组声明的类型,就决定了进行元素初始化时的类型
  3. 数组在存储数据方面的弊端:

    • 数组初始化以后,长度就不可变了,不便于扩展
    • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。 同时无法直接获取存储元素的个数
    • 数组存储的数据是有序的、可以重复的。---->存储数据的特点单一

1.2 Colloection、Map

image-20200822094943068

1.3 Collection接口继承树

image-20200822095826444

实线:继承关系

虚线:实现关系

1.4 Map接口继承树

image-20200822095847177

实线:继承关系

虚线:实现关系

2 Collection接口方法

image-20200822100257931

image-20200822100303763

image-20200822100309621

像Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()

@Test
public void test(){
    Collection coll = new ArrayList();
    
    //add(Object e):将元素e添加到集合coll中
    coll.add("AA");
    coll.add("BB");
    coll.add(123);//自动装箱
    coll.add(new Date());
    coll.add(new String("Tome"));
    //Person p = new Person("Jerry", 20);
    //coll.add(p);
    coll.add(new Person("Jerry", 20));
    
    //addAll(Collection coll):将other集合中的元素添加到当前的集合中
    Collection other = new ArrayList();
    other.add("CC");
    other.add(456);
    coll.addAll(other);
    
    //int size():获取添加的元素个数
    coll.size();//4
    
    //clear():清空集合元素
    other.claer();
    
    //boolean isEmpty():判断当前集合是否为空(是否有元素)
    coll.isEmpty();//false
    
    //boolean contains(Object obj):判断当前集合中是否包含obj
    //我们在判断时会调用obj对象所在类的equals()
    coll.contains("AA");//true
    coll.contains(new String("Tome"));//true
    coll.contains(p);//false
    
    //boolean containsAll(Collection coll1):判断coll1中的所有元素是否都存在于当前集合中
    Collection coll1 = Arrays.asList(123,456);
    coll.containsAll(coll1);//true
    Collection coll2 = Arrays.asList(123,456,789);
    coll.containsAll(coll2);//false
    
    //boolean remove(Object obj):从当前集合中移除obj元素,若有重复则移除一个
    coll.remove(123);
    coll.remove(new Person("Jerry", 20));
    
    //boolean removeAll(Collection coll1):差集,从当前集合中移除coll1中所有的元素
    coll.removeAll(coll1);//只会移除123,456
    
    //boolean retainAll(Collection coll2):交集,获取当前集合和coll2集合的交集,并返回给当前集合
    coll.retainAll(coll2);//保留123,456
    
    //boolean equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同,顺序也要相同
    Collection coll3 = new ArrayList();
    coll3.add("AA");
    coll3.add("BB");
    coll3.add(123);//自动装箱
    coll3.add(new Date());
    coll3.add(new String("Tome"));
    coll3.add(new Person("Jerry", 20));
    coll.equals(coll3);//true

    //Object[] toArray():集合 --> 数组
    Object[] arr = coll.toArray;
    for(int i = 0; i < arr.length; i++){
        System.out.println(arr[i]);
    }
    
    //拓展:数组 --> 集合      :调用Arrays类的静态方法asList(T ... t)//
    List<String> list = Arrays.asList(new String[]{"AA","BB"});
    System.out.println(list);//[AA,BB]
    
    List<int[]> arr1 = Arrays.asList(new int[]{123,456});
    //List arr1 = Arrays.asList(new int[]{123,456});
    System.out.println(arr1);//[[I@22927a81]   一个元素
    
    List arr2 = Arrays.asList(123, 456);
    System.out.println(arr2);//123,456   两个元素
    List arr3 = Arrays.asList(new Integer[]{123, 456});
    System.out.println(arr3);//123,456   两个元素
    
    //int hashCode():返回当前对象的哈希值
    System.out.println(coll.hashCode());
    
    //iterator():返回Iterator接口的实例,用于遍历集合元素
    
    
}

3 Iterator迭代器接口

image-20200822141212367

image-20200822141220442

3.1 遍历 - hasNext()、next()

image-20200822141227179

@Test
public void test(){
    Collection coll = new ArrayList();
    
    //add(Object e):将元素e添加到集合coll中
    coll.add(123);
    coll.add(456);
    coll.add(false);
    coll.add(new String("Tome"));
    coll.add(new Person("Jerry", 20));
    
    //Iterator iterator():返回Iterator接口的实例,用于遍历集合元素
    Iterator iterator = coll.iterator();
    
    //方式一
    System.out.println(iterator.next());
    System.out.println(iterator.next());
    System.out.println(iterator.next());
    System.out.println(iterator.next());
    System.out.println(iterator.next());
    //报异常:NoSuchElementException
    System.out.println(iterator.next());
    
    //方式二:不推荐
    for(int i = 0;i < coll.size();i++){
        System.out.println(iterator.next());
    }
    
    //方式三:推荐
    //hasNext():判断是否还有下一个元素
    while(iterator.hasNext()){
        //next():①指针下移 ②将下移以后集合位置上的元素返回
        System.out.println(iterator.next());
    }
    
}

3.2 移除 - remove()

image-20200822141239363

Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Tome"));
coll.add(new Person("Jerry", 20));

//remove()
Iterator iterator = coll.iterator();//回到起点
while(iterator.hasNext()){
	Object obj = iterator.next();
	if(obj.equals("Tom")){
		iterator.remove();
	}
}

3.3 增强for循环

image-20200822141246156

Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Tome"));
coll.add(new Person("Jerry", 20));

//for( 集合中元素的类型 局部变量 : 集合对象 )
//内部仍然调用了迭代器
for(Object obj : coll){
    System.out.println(obj);
}
int[] arr = new int[]{1,2,3,5,6};

//for( 数组中元素的类型 局部变量 : 数组对象 )
for(int i : arr){
    System.out.println(i);
}

3.4 练习

image-20200822141252498

atguigu、atguigu、atguigu、atguigu、atguigu

null、null、null、null、null

4 Collection子接口一:List(“动态”数组)

有序可重复

4.1 概述

image-20200822172524957

4.2 List接口方法

image-20200822172547695

ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tome", 12));
list.add(456);

//void add(int index, Object ele):
//在index位置插入ele元素
list.add(1,"BB");//[123,BB,456,AA,Person{name='Tom', age=12},456]

//boolean addAll(int index, Collection eles)
//从index位置开始将eles中的所有元素添加进来
List list1 = Arrays.asList(1,2,3);
list.addAll(list1);//[123,BB,456,AA,Person{name='Tom', age=12},456,1,2,3]

//Object get(int index)
//获取指定index位置的元素
list.get(0);//123

//int indexOf(Object obj)
//返回obj在集合中首次出现的位置,没有返-1
int index = list.indexOf(456);//2

//int lastIndexOf(Object obj)
//返回obj在当前集合中末次出现的位置,没有返-1
int index = list.indexOf(456);//5

//Object remove(int index)
//移除指定index位置的元素,并返回此元素
Object obj = list.remove(0);//[BB,456,AA,Person{name='Tom', age=12},456,1,2,3]

//Object set(int index, Object ele)
//设置指定index位置的元素为ele
list.set(0,"CC");//[CC,456,AA,Person{name='Tom', age=12},456,1,2,3]
    
//List subList(int fromIndex, int toIndex)
//返回从fromIndex到toIndex位置的子集合,左闭右开
List sublist = list.subList(2,4);//[AA,Person{name='Tom', age=12}]
4.2.1 总结常用方法
  • 增:
    • add(Object obj)
    • addAll(Collection coll)
  • 删:
    • remove(Object obj)
    • remove(int index)
  • 改:
    • set(int index, Object ele)
  • 查:
    • get(int index)
  • 插:
    • add(int index, Object ele)
    • addAll(int index, Collection eles)
  • 长度:
    • size()
  • 遍历:
    • Iterator迭代器方式
    • 增强for循环
    • 普通for循环

4.3 List实现类之一:ArrayList

image-20200822173934859

4.3.1 源码分析
4.3.1.1 JDK7
ArrayList list = new ArrayList();//底层创建了长度为10的Object[]数组elementData
list.add(1);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容,默认情况下,扩容为原来的容量的1.5倍的新数组,同时需要将原有数组中的数据复制到新的数组中

建议开发中使用带参的构造器

ArrayList list = new ArrayList(int capacity);
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    private transient Object[] elementData;
    private int size;
    
    //----------------------------------------------
    
    //构造器
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
    
    //空参构造器,把当前底层的数组给初始化了,容量为10
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this(10);
    }
    
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }
    
    //----------------------------------------------
    
    //扩容
    private void grow(int minCapacity) {//扩容为原来的1.5倍
        // overflow-conscious code
        int oldCapacity = elementData.length;//先记录底层数组的长度
        int newCapacity = oldCapacity + (oldCapacity >> 1);//【oldCapacity >> 1  相当于除以2】扩容为原来的1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;//还不够直接拿你的容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//超过MAX_ARRAY_SIZE的情况,取整形的最大值
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    //超过MAX_ARRAY_SIZE的情况,取整形的最大值
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
    //确定长度是否够
    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//不够则扩容
            grow(minCapacity);//扩容
    }
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!//确定长度是否够
        elementData[size++] = e;
        return true;
    }
    
    
}
4.3.1.2 JDK8
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(1);//第一次调用add()时,底层才创建长度为10的数组,并将数据1添加到elementData[0]中
...
list.add(11);//后续的操作与AJD7无异
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//什么都没有
    transient Object[] elementData;
    private int size;
    
    //----------------------------------------------
    
    //构造器
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    
    //----------------------------------------------
    
    //扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
}
4.3.1.3 小结

内存延迟

JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组

JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个容量为10的数组

4.3.2 面试题

image-20200822173958376

index

包装类

4.4 List实现类之二:LinkedList

image-20200822174010368

image-20200822174019563

4.4.1 源码分析
LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);///将123封装到Node中,创建了Node对象
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    
    //----------------------------------------------
    
    //构造器
    public LinkedList() {
    }
    
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    
    //----------------------------------------------
    
    //add()
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    //----------------------------------------------
    
    //Node内部类
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
}

4.5 List 实现类之三:Vector

image-20200822174026098

默认情况下,扩容为原来的2倍

4.6 面试题

image-20200822172847056

4.6.1 ArrayList、LinkedList、Vector三者的异同
  • 同:三个实现类都是实现了List接口,存储数据的特点相同:有序、可重复

  • 异:

    • ArrayList:

      • 作为List接口的主要实现类

      • 线程不安全的,效率高

      • 底层使用 Object[] 存储

        image-20200822173650950
    • LinkedList:

      • 底层使用 双向链表 存储()【对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高

        image-20200822175855941
    • Vector:

      • 作为List接口的古老实现类(JDK1.0就有了)

      • 线程安全的,效率低

      • 底层使用 Object[] 存储

        image-20200822173816303

5 Collection子接口二:Set

无序不可重复

5.1 概述

  • HashSet:(底层:数组+链表的结构)
    • 作为Set接口的主要实现类
    • 线程不安全的
    • 可以存储null值
  • LinkedHashSet:
    • 作为HashSet的子类,让他看似是有序
    • 遍历其内部数据时,可以按照添加的顺序遍历
    • 对于频繁的遍历操作,效率高于HashSet
  • TreeSet:
    • 可以按照添加对象的指定属性,进行排序

image-20200823120935979

Set set = new HashSet();
set.add(123);
set.add(456);
set.add("AA");
set.add("CC");
set.add(new Person("Tom", 12));

HashSet:(底层:数组+链表的结构)

5.1.1 无序性与不可重复性

HashSet为例说明:

  • 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加

  • 不可重复性:保证添加的元素按照equlas()判断时【既要重写equlas()也要重写Hashcode()】,不能返回true,即:相同的元素只能添加一个

5.1.2 添加元素的过程

HashSet为例说明:

创建一个16长度的数组,我们向HashSet中添加元素a时,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算除HashSet底层数组种的存放位置(即为:索引位置),

再判断数组此位置上是否已经有元素:

  • 如果此位置上没有其他元素,则元素a添加成功 ---->情况1

  • 如果此位置上有其他元素b(或者已经有以链表形式存在的多个元素),则比较元素a于元素b的hash值:

    • 如果hash值不相同,则元素a添加成功 ---->情况2
    • 如果hash值相同,进而需要调用元素a所在类的qeulas()方法:
      • equals()返回true,元素a添加失败
      • equals()返回false,元素a添加成功 ---->情况3

对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。

jdk7 :元素a放到数组中,指向原来的元素

jdk8 :原来的元素在数组中,指向元素a

即七上八下

image-20200823130747444

创建一个16长度的数组,添加先计算哈希值(相同属性则哈希值相同),通过哈希值判断存储在数组的哪里

七上八下

image-20200823125437395

3,4哈希值不一样

image-20200823125526084

1,5哈希值一样

则equals(),返回true,相同,则添加失败

链表长度超过8,则变成红黑树

5.2 要求

  1. 向Set(主要值HashSet、LinkedHashSet)中添加的数组,其所在的类一定要重写hashCode()和equals()
  2. 重写的hashCode()和euals()尽可能保存一致性

5.3 Set实现类之一:HashSet

作为Set接口的主要实现类

image-20200823121004642

image-20200823130834948

image-20200823130849819

image-20200823130856718

image-20200823130901492

image-20200823132716179

image-20200823130910668

5.4 Set实现类之二:LinkedHashSet

还是无序的,在原有的HashSet的基础上添加了一对双向链表,来记录添加的先后顺序

优点:对于频繁的遍历操作,效率高于HashSet

image-20200823141155973

image-20200823141203585

5.5 Set实现类之三:TreeSet

image-20200823141215379

image-20200823142041270

  1. 向TreeSet中添加的数据,要求是相同类的对象

  2. 两种排序方式:自然排序(实现Comparable接口) 和 定制排序

  3. 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()

  4. 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()

TreeSet set = new TreeSet();

//方式一
set.add(11);
set.add(22);
set.add(-11);
set.add(0);
set.add(33);
Iterator iterator = set.iterator();
while(iterator.hashNext()){
    System.out.println(iterator.next());
}//-11、0、11、22、33



//方式二
//自然排序
set.add(new User("Tom", 12));
set.add(new User("Mike", 65));
set.add(new User("Jim", 2));
set.add(new User("Jerry", 30));
set.add(new User("Jerry", 40));
while(iterator.hashNext()){
    System.out.println(iterator.next());
}
//User{name='', age=}

class User implement Comparable{
    String name;
    int age;
    
    //构造器,equals(),hashCode(),
    
    //compareTo():姓名从小到大,年龄从小到大
    @Override
    public int compareTo(Object obj){
        if(o instanceof User){
            User user = (user)o;
//            return this.name.compareTo(user.name);
            int compare = this.name.compareTo(user.name);
            if(compare != 0){
                return compare;
            }else{
                return Integer.compare(this.age, user.age);
            }
        }else{
            throw new RuntimeException("输入类型不匹配")
        }
    }
}


//定制排序
Comparator com = new Comparator(){
    //年龄从小到大
    @Override
    public int compare(Object o1,Object o2){
        if(o1 instancef User && o2 instancef User){
            User u1 = (Goods) o1;
			User u2 = (Goods) o2;
        	return Integer.compare(u1.getAge(), u2.getAge());
        }else{
            throw new RuntimeException("输入的类型不一样");
        }    
    }
};

TreeSet set = new TreeSet(com);
set.add(new User("Tom", 12));
set.add(new User("Mike", 65));
set.add(new User("Jim", 2));
set.add(new User("Jerry", 30));
set.add(new User("Jerry", 40));
5.5.1 自然排序

image-20200823142127544

image-20200823142114380

5.5.2 定制排序

image-20200823142141375

image-20200823225118660

5.5.3 面试题

image-20200823142203412

image-20200829093502564

image-20200829093526239

remove之后,还是两个

remove找的hash值不是之前那个了

image-20200823142212909

public int compareTo(Object o){
    if(o instanceof Employee){
        Employee e = (Employee)o;
        return this.name.compareTo(e.name);
    }
    //return 0;
    throw new RunTimeException("传入的数据类型不一样");
}

TreeSet set = new TreeSet(new Comparator(){
    @Override
    public int compare(Object o1,Object o2){
        if(o1 instanceof Employee && o1 instanceof Employee){
        	Employee e1 = (Employee)o1;
        	Employee e2 = (Employee)o2;
        	
            MyDate b1 = e1.getBirthday();
            MyDate b2 = e2.getBirthday();
            
            int minusYear = b1.getYear() - b2.getYear();
            if(minusYear != 0 ){
                return minusYear;
            }
            int minusMonth = b1.getMonth() - b2.getMonth();
            if(minusMonth != 0 ){
                return minusMonth;
            }
            return b1.getDay() - b2.getDay();
    	}
        throw new RunTimeException("传入的数据类型不一样");
    }
});

6 Map接口

key - value

x - y

  • HashMap

    • 作为主要实现类
    • 线程不安全的,效率高
    • 能存储null的key和value
  • LinkedHashMap

    • 保存在遍历map元素时,可以按照添加的顺序实现遍历

      原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前后元素

      对于频繁的遍历操作,此类执行效率高于HashMap

  • TreeMap

    • 保证按照添加的key-value对进行排序,实现排序遍历。考虑key的自然排序和定制排序
    • 底层使用红黑树
  • Hashtable

    • 作为古老实现类
    • 线程安全的,效率低
    • 不能存储null的key和value
  • Properties

    • 常用来处理配置文件。
    • key和value都是String类型

    HashMap的底层:

    数组+链表(jdk 7及之前)

    数组+链表+红黑树(jdk 8)

6.1 Map接口概述

image-20200829101249232

image-20200829101256006

6.2 Map结构理解

Map中的key:无序的,不可重复的。使用Set存储所有的key —> key所在的类要重写equald()和hashCode()

Map中的value:无序的,可重复的。使用Collection存储所有的value —> value所在的类要重写equald()

一个键值对:key-value构成了一个Entry对象

Map中的entry:无序的,不可重复的。使用Set存储所有的entry

6.3 常用方法

image-20200829102619888

6.3.1 添加、删除、修改操作:
  • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中

    map.put("AA",123);
    map.put("BB",45);
    map.put("CC",78);
    map.put("AA",12);//修改
    
    map1.put("DD",3);
    map1.put("EE",6);
    
  • void putAll(Map m):将m中的所有key-value对存放到当前map中

    map.putAll(map1);

  • Object remove(Object key):移除指定key的key-value对,并返回value

    map.remove("CC")

  • void clear():清空当前map中的所有数据

    map.clear();//{}

6.3.2 元素查询的操作:
  • Object get(Object key):获取指定key对应的value

    map.get("CC");//78

  • boolean containsKey(Object key):是否包含指定的key

    map.containsKey("CC");//true

  • boolean containsValue(Object value):是否包含指定的value

    map.containsValue(12);//true

  • int size():返回map中key-value对的个数

    map.size();

  • boolean isEmpty():判断当前map是否为空

    map.isEmpty()//false

  • boolean equals(Object obj):判断当前map和参数对象obj是否相等

6.3.3 元视图操作的方法:
  • Set keySet():返回所有key构成的Set集合
  • Collection values():返回所有value构成的Collection集合
  • Set entrySet():返回所有key-value对构成的Set集合
Map map = new HashMap();
//map.put(..,..)省略


System.out.println("map的所有key:");
Set keys = map.keySet();// HashSet
for (Object key : keys) {
	System.out.println(key + "->" + map.get(key));
}


System.out.println("map的所有的value:");
Collection values = map.values();
Iterator iter = values.iterator();
while (iter.hasNext()) {
	System.out.println(iter.next());
}


System.out.println("map所有的映射关系:");
// 映射关系的类型是Map.Entry类型,它是Map接口的内部接口
Set mappings = map.entrySet();
for (Object mapping : mappings) {
	Map.Entry entry = (Map.Entry) mapping;
	System.out.println("key是:" + entry.getKey() + ",value是:" + entry.getValue());
}


Set keySet = map.keySet();// HashSet
Iterator iter2 = keySet.iterator();
while (iter2.hasNext()) {
	Object key = iter2.next();
    Object value = map.get(key);
    System.out.println("key是:" + key + ",value是:" + value);
}

6.4 Map实现类之一:HashMap

image-20200829102732972

6.4.1 HashMap的存储结构

image-20200829102759529

image-20200829102808932

6.4.1.1 以jdk 7为例

Map map = new HashMap();

在实例化以后,底层创建了一个长度为16的一维数组Entry[] table。

map.put(key1 , value1);

  • 首先,调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过算法计算之后,得到Entry在数组中的存放位置
    • 如果此位置上的数据为空,此时key1-value1【Entry】添加成功 ---- 情况1
    • 如果此位置上的数据不为空(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值
      • 如果key1的哈希值和已经存在的数据的哈希值都不相同,此时key1-value1【Entry】添加成功 ---- 情况2
      • 如果key1的哈希值和已经存在的某一个数据的哈希值相同,继续比较:调用equals()方法比较
        • 如果equals()返回false:此时key1-value1【Entry】添加成功 ---- 情况3
        • 如果equals()返回true:使用value1替换相同key的value值【修改功能】

补充:

关于情况2和情况3:此时key1-value1【Entry】和原来数据以链表的方式存储

在不断的添加过程中:会涉及扩容问题,当超过临界值(且要存放的位置非空)

默认的扩容方式:扩容为原来容量的2倍,并将原来的数据复制过来

6.4.1.1 以jdk 8为例

jdk8相较于jdk底层实现方面的不同:

  1. Map map = new HashMap();

    底层摸鱼创建一个长度为16的数组

  2. jdk8底层的数组:Node[],而非Entry[]

  3. 首次调用put()方法时,底层创建长度为16的数组

  4. jdk7底层结构只有:数组+链表

    jdk8底层结构:数组+链表+红黑树

    当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8.且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储。

6.4.2 源码

image-20200829114102743

6.4.2.1 jdk7

image-20200829141639857

image-20200829141650732

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{
    //HashMap的默认容量,16
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //HashMap的最大支持容量,2^30
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //HashMap的默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //
    transient Entry<K,V>[] table;
    //HashMap中存储的键值对的数量
    transient int size;
    //扩容的临界值,=容量*填充因子
    int threshold;
    //填充因子
    final float loadFactor;
    //HashMap扩容和结构改变的次数
    transient int modCount;
    //
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
    
    
    //构造器
    //默认:16,0.75
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;//16
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;//16

        this.loadFactor = loadFactor;//0.75
        //临界值
        //capacity * loadFactor = 12;影响扩容
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];//Entry[16]
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init();
    }
    
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }
    
    
    //put
    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);//计算当前key的哈希值
        int i = indexFor(hash, table.length);//获得存放位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;//覆盖
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    
    //计算哈希值
    transient boolean useAltHashing;
    transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
    
    final int hash(Object k) {
        int h = 0;
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }

        h ^= k.hashCode();//异或

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    
    //利用哈希值得来位置
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
    
    //add添加
    //addEntry(hash, key, value, i);
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold/* capacity * loadfactor = 12 */) && (null != table[bucketIndex])) {
            resize(2 * table.length);//扩容为2倍
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);//不需要扩容
    }
    
    //createEntry添加
    //createEntry(hash, key, value, bucketIndex)
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];//取出原有位置上的数组
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
    
    //扩容
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = oldAltHashing ^ useAltHashing;
        transfer(newTable, rehash);
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    
    
    
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }
}
6.4.2.2 jdk8

image-20200829141700915

image-20200829141707071

image-20200829141717634

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    private static final long serialVersionUID = 362498820763181265L;
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    static final int MAXIMUM_CAPACITY = 1 << 30;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    static final int TREEIFY_THRESHOLD = 8;
    static final int UNTREEIFY_THRESHOLD = 6;
    static final int MIN_TREEIFY_CAPACITY = 64;
    
    transient Node<K,V>[] table;
    transient Set<Map.Entry<K,V>> entrySet;
    transient int size;
    transient int modCount;
    int threshold;
    final float loadFactor;
    
    //Node
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
    
    //构造器
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
    
    
    //put
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; 
        Node<K,V> p; 
        int n, i;
        
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    
    
    
    //扩容
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    
    //变红黑树
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
    
    
}
6.4.3 面试题

image-20200829165748007

image-20200829165754109

6.4 Map实现类之二:LinkedHashMap

image-20200829165803928

image-20200829165810710

6.5 Map实现类之三:TreeMap

image-20200829170408836

6.6 Map实现类之四:Hashtable

image-20200829170417529

6.7 Map实现类之五:Properties

image-20200829170424825

  1. 项目新建

    image-20200829204641001

  2. try{
        Properties pros = new Properties();
    
    	FileInputStream fis = new 	FileInputStream("jdbc.properties");
    	pros.load(fis);//加载流对应的文件
    
    	String name = pros.getPtoperty("name");
    	String password = pros.getPtoperty("password");
    }catch(IOException e){
        e.printStackTrace();
    }finally{
        if(fis != null){
            try{
                fis.close();
            }catch(IOException e){
        		e.printStackTrace();
    		}
        }
    }
    
    

6.8 面试题

  1. HahsMap的底层实现原理
  2. HahsMap 和 Hashtable 的异同
  3. CurrentHashMap 与 Hashtable 的异同]
  4. ……………………

7 Collections工具类

7.1 常用方法

image-20200829205710792

image-20200829205717065

List dest = Arrays.asList(new Object[list.size()]);

Collections.copy(dest , list);

7.2 常用方法:同步控制

image-20200829205724642

List list1 = Collections.synchronizedList(list);

8 练习

image-20200829205738341

image-20200829205744168

image-20200829205749329

9 外加:Collection的Java8新特性(遍历):forEach()

默认方法

Collection coll = new ArrayList();
coll.add(1);
coll.add(2);
coll.add(3);
coll.add(4);

coll.forEach(System.out::println);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值