java中的集合

1、集合概述
---1.1集合继承结构图
---1.2Map集合继承结构图
---1.3集合的所有实现类
2、关于Collection接口
---2.1常用方法
---2.2Collection集合迭代
---2.3contains方法分析
---2.4remove方法分析
---2.5迭代器的更新
3、关于List接口
---3.1常用方法
---3.2ArrayList集合
---3.3LinkedList集合
---3.4Vector集合
4、关于Set接口
---4.1Set下的HashSet集合和TreeSet集合
5、关于Map接口
---5.1Map概述
---5.2Map接口中的常用方法
---5.3遍历Map集合
6、HashMap集合
---6.1HashMap集合概述
---6.2map.put(k,v)实现原理:
---6.3v = map.get(k)实现原理:
---6.4为什么哈希表的随机增删,以及查询效率都很高?
---6.5图例:
---6.6同时重写hashCode()和equals():
---6.7HashMap和Hashtable的区别
7、Hashtable和Properties属性类对象
---7.1Properties概述
---7.2代码示例
8、SortedMap和TreeMap
---8.1概述
---8.2TreeSet的自定义类型排序
---8.3实现Comparable接口
---8.4自平衡二叉树
---8.5实现比较器接口
9、Collections工具类

集合概述

  • 集合概述
    1、什么是集合?有什么用?
    数组其实就是一个集合,集合实际上就是一个容器。可以来容纳其它类型的数据。
    2、集合为什么说在开发中使用较多?
    集合是一个容器,是一个载体,可以一次容纳多个对象。
    在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在Java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。
    3、集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是Java对象的内存地址。(或者说集合中存储的是引用)
list.add(100);//自动装箱Integer

注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是"引用"。
4、在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。
5、什么是数据结构?
数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:数组、二叉树、链表、哈希表..
以上这些都是常见的数据结构
例如:往集合01中放数据,可能是放到数组上了;
往集合02中放数据,可能是放到二叉树上了。使用不同的集合等同于使用了不同的数据结构。
6、集合的使用和所在包
new ArrayList(); 创建一个集合,底层是数组。
new LinkedList(); 创建一个集合对象,底层是链表。
new Treeset (); 创建一个集合对象,底层是二叉树。
所有的集合类和集合接口都在java.util包下。
7、在java中集合分为两大类:
一类是单个方式存储元素:单个方式存储元素,这一类集合中超级父接口:Java.util.Collection;
一类是以键值对儿的方式存储元素。这一类集合中超级父接口: Java.util.Map;

  • 集合继承结构图
    1、
    集合继承结构图
    2、List集合
    List集合存储元素特点:有序可重复,存储的元素有下标。
    有序实际上是说存进去是这个顺序,取出来还是这个顺序。这里的顺序不是说按照大小排序。
    有序是因为List集合都有下标,下标从0开始,以1递增。
    3、Set集合
    Set集合存储元素特点:无序不可重复。
    无序表示存进去是这个顺序,取出来就不一定是这个顺序了。另外Set集合中元素没有下标,Set集合中的元素还不能重复。
    4、list集合下的
    ArrayList:
    ArrayList集合底层采用了数组这种数据结构,ArrayList集合是非线程安全的。
    LinkedList:
    LinkedList集合底层采用了双句链表数据结构。
    Vector:
    Vector集合底层采用了数组这种数据构,Vector集合是线程安全的。Vector所有的方法都有synchronized关键字修饰。所以线程安全,但是效率较低,现在保证线程安全有别的方案,所以Vector使用较少了。
    5、Set集合下的
    HashSet:
    实际上HashSet集合在new的时候底层实际上new了一个HashMap集合。向HashSet集合中存储元素,实际上是存储到HashMap集合中了。HashMap集合是一个哈希表数据结构。
    SortedSet:
    SortedSet集合存储元素的特点:由于继承了Set集合,所以它的特点也是无序不可重复,但是放在SortedSet集合中的元素可以自动排序成为可排序集合。放到该集合中的元素是自动按照大小顺序排序的。
    SortedSet--->TreeSet:
    TreeSet集合底层实际上是TreeMap new TreeSet集合的时候,底层实际上new 一个TreeMap集合。往TreeSet集合中放数据的时候,实际上是将数据放到TreeMap集合中了。TreeMap集合底层采用了二叉树数据结构。
  • Map集合继承结构图
    在这里插入图片描述
    Map集合的key,就是一个Set集合。
    往Set集合中放数据,实际上放到了Map集合的key部分。
  • 总结所有的实现类
    ArrayList:底层是数组。
    LinkedList:底层是双向链表。
    Vector:底层是数组,线程安全的,效率较低,使用较少。
    HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。
    Treeset:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了。
    HashMap:底层是哈希表。
    Hashtable:底层是哈希表,只不过线程安全,效率较低,使用较少。
    Properties:是线程安全的,并且key和value只能存储字符串String。
    TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序。
  • 各集合特点
    1、List集合存储元素的特点:有序可重复。
    有序:存进去的顺序和取出的顺序相同,每一个元素都有下标。
    可重复:存进去1,可以再存储一个1。
    2、Set (Map)集合存储元素的特点:无序不可重复。
    无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标。
    不可重复:存进去1,不能再存储1了。
    3、Sortedset (SortedMap)集合存储元素特点:首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。
    无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标。
    不可重复:存进去1,不能再存储1了。
    可排序:可以按照大小顺序排列。

关于Collection接口

  • 常用方法
    1、boolean add(E e);(E是Object类型)(向集合中添加元素)
    2、int size();(获取集合中元素的个数)(不是容量)
    3、void clear();(清空集合)
    4、boolean contains (Object o);(判断当前集合中是否包含元素o)
    5、boolean remove(Object o);(删除集合中某个元素)
    6、boolean isEmpty();( 判断该集合中元素的个数是否为0)
    7、Object[] toArray();(将集合转换为数组)
    代码示例:
import java.util.ArrayList;
import java.util.Collection;

public class Demo{
    public static void main(String[] args) {
        Collection c = new ArrayList();
        /*boolean add(E e);
        (E是Object类型)
        (向集合中添加元素)*/
        c.add(1200);//自动装箱
        c.add(1.23);
        c.add(new Object());
        c.add(true);
        /*int size();
        (获取集合中元素的个数)*/
        System.out.println("集合中元素个数是:" + c.size());
        /*void clear();
        (清空集合)*/
        c.clear();
        System.out.println("集合中元素个数是:" + c.size());
        /*boolean contains (Object o);
        (判断当前集合中是否包含元素o)*/
        c.add("钢铁侠");
        c.add("蜘蛛侠");
        c.add(1);
        System.out.println(c.contains("钢铁侠"));
        System.out.println("集合中元素个数是:" + c.size());
        /*boolean remove(Object o);
        (删除集合中某个元素)*/
        c.remove(1);
        System.out.println("集合中元素个数是:" + c.size());
        /*boolean isEmpty();
        判断该集合中元素的个数是否为0
        */
        System.out.println(c.isEmpty());
        /*Object[] toArray();
        将集合转换为数组
        */
        Object[] obj = c.toArray();
        for (int i = 0; i < obj.length; i++) {
            System.out.println(obj[i]);
        }
    }
}

输出:
在这里插入图片描述

  • Collection集合迭代
    1、以下的遍历方式/迭代方式,是所有Collection通用的一种方式。在Map集合中不能用。在所有的Collection以及子类中使用。
    2、图例
    在这里插入图片描述
    3、代码示例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Demo{
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add(11);
        c.add("qwer");
        c.add(new Object());
        //第一步:获取集合对象的迭代器对象Iterator
        Iterator it = c.iterator();
        /*第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
        以下两个方法是迭代器对象Iterator中的方法:
        boolean hasNext() 如果仍有元素可以迭代,则返回true。
        Object next() 返回迭代的下一个元素。*/
        while (it.hasNext()){
            /*
            不管存进去什么类型,取出来还是什么类型。
            但统一都是Object类型。
            */
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

输出:
在这里插入图片描述

  • contains方法分析
    1、contains方法是用来判断集合中是否包含某个元素的方法,它在底层调用equals方法进行比对。equals方法返回true,就表示包含这个元素。
    代码示例:
import java.util.ArrayList;
import java.util.Collection;

public class Demo{
    public static void main(String[] args) {
        Collection c = new ArrayList();
        String s1 = new String("qwe");
        c.add(s1);
        String s2 = new String("asd");
        c.add(s2);
        System.out.println("集合中元素个数是:" + c.size());//2
        String x = new String("qwe");
        System.out.println(c.contains(x));//true
    }
}

2、放在集合里面的元素要重写equals方法
代码示例:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

public class Demo{
    public static void main(String[] args) {
        Collection c = new ArrayList();
        User u1 = new User("qwe");
        c.add(u1);
        User u2 = new User("qwe");
        /*没有重写equals之前,输出:false*/
        //System.out.println(c.contains(u2));
        /*重写equals之后,输出:true*/
        System.out.println(c.contains(u2));
    }
}
class User{
    private String name;
    public User() { }
    public User(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name);
    }
}
  • remove方法分析
    1、同样:放在集合里面的元素要重写equals方法
    代码示例:
public class Demo{
    public static void main(String[] args) {
        Collection c = new ArrayList();
        User u1 = new User("qwe");
        c.add(u1);
        User u2 = new User("qwe");
        c.remove(u2);
        /*没有重写equals之前,输出:1*/
        //System.out.println(c.size());
        /*重写equals之后,输出:0*/
        System.out.println(c.size());
    }
}
  • 集合结构只要发生改变,迭代器必须重新获取。
    1、代码示例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Demo{
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add(1);
        c.add(2);
        c.add(3);
        /*获取的迭代器对象,迭代器用来遍历集合,
        此时相当于对当前集合的状态拍了一个快照。
        迭代器迭代的时候会参照这个快照进行迭代。*/
        Iterator it = c.iterator();
        while (it.hasNext()){
            Object obj = it.next();
            c.remove(obj);
            System.out.println(obj);
        }
    }
}

输出:
在这里插入图片描述
2、出异常的原因是:
这里输出了1之后才出现异常:java.util.ConcurrentModificationException
是因为第二次循环的时候没有重新获取迭代器。
所以出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)。
在删除之后集合结构发生变化,应该重新去获取迭代器,但是这里没有。
c.remove(obj);直接通过集合去删除元素,没有通知迭代器,导致迭代器的快照和原集合状态不同。
3、所以该改为:

while (it.hasNext()){
            Object obj = it.next();
            /*迭代器去删除时,会自动更新送代器。
            并且更新集合(删除集合中的元素)。
            删除的一定是迭代器指向的当前元素。*/
            it.remove();
            System.out.println(obj);
        }

输出:
在这里插入图片描述

关于List接口

  • 常用方法
    1、void add(int index, E element)
    将指定的元素插入此列表中的指定位置(可选操作).
    2、E get(int index)
    返回此列表中指定位置的元素。
    3、int indexOf(Object o)
    返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
    4、int lastIndexOf(Object o)
    返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
    5、E remove(int index)
    删除该列表中指定位置的元素(可选操作)。
    6、E set(int index, E element)
    用指定的元素(可选操作)替换此列表中指定位置的元素。
    代码示例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Demo{
    public static void main(String[] args) {
        List list = new ArrayList();
        /*默认都是向集合末尾添加元素*/
        list.add(11);
        list.add(11);
        list.add("qwe");
        list.add("qwe");
        /*void	add(int index, E element)
        使用不多,对于ArrayList集合来说效率较低*/
        list.add(0,"r");
        /*E	get(int index)
        因为有下标,可以用for循环来遍历*/
        System.out.println("下标对应的元素为:" + list.get(0));
        /*int indexOf(Object o)*/
        System.out.println("第一次出现索引为:" + list.lastIndexOf("qwe"));
        /*int lastIndexOf(Object o)*/
        System.out.println("最后一次出现索引为:" + list.lastIndexOf(11));
        /*E	remove(int index)*/
        list.remove(4);
        /*E	set(int index, E element)*/
        list.set(1,"Thompson");

        System.out.println("=============");
        Iterator it = list.iterator();
        while (it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

输出:
在这里插入图片描述

  • ArrayList集合(最常用的集合)
    1、默认初始化容量10
/*默认初始化容量是10,
        (底层先创建了一个长度为0的教组,
        当添加第一个元素的时候,才初始化容量为10*/
        List list = new ArrayList();
        //指定初始化容量
        List list1 = new ArrayList(20);

2、集合底层是一个Object[]数组。
3、扩容:增长到原容量的1.5倍。Arraylist集合底层是数组。因为数组扩容效率比较低,建议在使用Arraylist集合的时候预估计元素的个数,给定一个初始化容量。
4、补充:二进制位运算
代码示例:

public class DemoTest{
    public static void main(String[] args) {
        /*二进制右移一位(除以2)*/
        System.out.println(10>>1);//5
        /*二进制左移一位(乘以2)*/
        System.out.println(10<<1);//20
    }
}

5、数组优点:检索效率比较高。
数组缺点:随机增删元素效率比较低,向数组末尾添加元素,效率很高。
6、ArrayList集合的另外一种构造方法
ArrayList(Collection<? extends E> c)
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
代码示例:

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

public class Demo{
    public static void main(String[] args) {
        Collection c = new HashSet();
        c.add(11);
        c.add("qwe");
        /*通过这个构造方法就可以
        将HashSet集合转换成list集合*/
        List list = new ArrayList(c);
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

输出:
在这里插入图片描述
7、ArrayList集合是非线程安全的。(不是线程安全的集合)

  • LinkedList集合
    1、优缺点
    链表优点:随机增删元素效率较高。(因为增删元素不涉及到大量元素位移)
    链表缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历。不能通过数学表达式计算被查找元素的内存地址。
    2、单链表中的节点。
    节点是单向链表中基本的单元。
    每一个节点Node都有两个属性:
    属性一:存储的数据。
    属性二:下一个节点的内存地址。
    3、代码理解:
public class Node {
    //存储的数据
    Object element;
    //下一个节点的内存地址
    Node next;

    public Node() {
    }

    public Node(Object element, Node next) {
        this.element = element;
        this.next = next;
    }
}
public class Link {
    //头结点
    Node header;
    int size = 0;
    public int size(){
        return size;
    }

    //增
    public void add(Object data){
        if (header==null){
            /*为空说明没有节点,
            * new一个头结点对象。*/
            header = new Node(data, null);
        }else {
            /*头结点已存在,需找出当前末尾节点,
            * 使末尾节点的next是新节点*/
            Node currentLastNode = findLast(header);
            currentLastNode.next = new Node(data, null);
        }
        size++;
    }

    private Node findLast(Node node) {
        if (node.next==null){
            /*说明是末尾节点*/
            return node;
        }
        /*说明不是末尾节点(递归算法)*/
        return findLast(node.next);
    }
    //删...
    //改...
    //查...
}

4、双向链表图例:
双向链表图例
LinkedList集合是双向链表。

  • Vector集合
    1、底层也是一个数组,初始化容量为10。
    2、扩容之后是原容量的2倍。而ArrayList集合扩容是原容量1.5倍。
    3、Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。但效率较低,使用较少。
    4、代码示例:
import java.util.Iterator;
import java.util.Vector;

public class Demo {
    public static void main(String[] args) {
        Vector vector = new Vector();
        vector.add(11);
        vector.add("qwe");
        Iterator it = vector.iterator();
        while (it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

5、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:java.util. Collections;
java.util.Collection是集合接口。
java.util. Collections是集合工具类。
代码示例:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        //非线程安全
        List list = new ArrayList();
        //变为线程安全
        Collections.synchronizedList(list);
        list.add(1);
        list.add(2);
    }
}

关于Set接口

  • Set下的HashSet集合和TreeSet集合
    1、HashSet集合:
    ①无序不可重复。
    ②底层是HashMap,放到HashSet集合中
    的元素等同于放到HashMap集合key部分了。
    ③HashMap集合是一个哈希表数据结构,HashSet集合初始化容量是16,初始化容里建议是2的倍数。扩容之后是原容量的2倍。
    2、TreeSet集合:
    无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:可排序集合。
    这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。

关于Map接口

  • Map概述
    1、Map和Collection没有继承关系。
    2、Map集合以key和value的方式存储数据:键值对。
    key和value都是引用数据类型。
    key和value都是存储对象的内存地址。
    key起到主导的地位, value是key的一个附属品。
  • Map接口中的常用方法
    1、V put(K key, V value)
    向Map集合中添加键值对。
    2、V get(Object key)
    如果有对应的key,则返回对应的value,没有返回null。
    3、void clear()
    从Map中删除所有的映射(清空Map集合)。
    4、boolean containsKey(Object key)
    判断Map中是否包含某个key,包含则返回true 。
    5、boolean containsValue(Object value)
    判断Map中是否包含某个value,包含则返回true 。
    6、boolean isEmpty()
    如果此Map不包含键值映射(元素个数为0),则返回true 。
    7、Collection values()
    返回Map集合中所有的value,返回一个Collection。Set
    8、V remove(Object key)
    如果存在(从可选的操作),从该Map中删除一个键的映射。(删除键值对)
    9、int size()
    返回此Map中键值对的数量。
    以上9种方法代码示例:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class Demo{
    public static void main(String[] args) {
        //创建Map集合对象
        Map<Integer, String> map = new HashMap<>();
        //向Map集合中添加键值对
        map.put(1, "zhangsan");
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");
        //通过可用获取value
        String value = map.get(1);
        System.out.println(value);
        //获取键值对的数量
        System.out.println("键值对的数量为:" + map.size());
        map.remove(4);
        System.out.println("删除后键值对的数量为:" + map.size());
        //判断是否包含某个key
        System.out.println(map.containsKey(1));//true
        //判断是否包含某个value
        /*contains方法底层调用的都是equals进行比对的,
        所以自定义的类型需要重与equals方法。*/
        System.out.println(map.containsValue("zhangsan"));//true
        //获取所有的value
        Collection<String> values = map.values();
        for (String s : values) {
            System.out.println("获取所有的value:" + s);
        }
        //清空map集合
        map.clear();
        System.out.println("清空后键值对的数量为:" + map.size());
        //判断是否为空
        System.out.println(map.isEmpty());//true
    }
}

输出:
在这里插入图片描述
10、Set keySet()
返回此Map中包含的key。(所有的键是一个set集合)
11、Set<Map.Entry<K,V>> entrySet()
将Map集合转换为Set集合。(Set集合中元素的类型是Map.Entry<K,V>,是一个静态内部类)

  • 遍历Map集合
    第一种方式:获取所有的key,通过遍历key,来遍历value。
    代码示例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class Demo{
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "zhangsan");
        map.put(2, "lisi");
        map.put(3, "wangwu");
        //获取所有的key,所有的key是一个Set集合
        Set<Integer> keys = map.keySet();
        //遍历key,通过key获取value
        //foreach遍历
        /*for (Integer key:keys
             ) {
            System.out.println(key + "=" + map.get(key));
        }*/
        //迭代器遍历
        Iterator<Integer> it = keys.iterator();
        while (it.hasNext()){
            Integer key = it.next();
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }
    }
}

输出:
在这里插入图片描述
第二种方式:Set<Map.Entry<K,V>> entrySet()
这个方法是把Map集合直接全部转换成Set集合。
这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。这种方式比较适合于大数据量。
图例:
在这里插入图片描述
代码示例:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class Demo{
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "zhangsan");
        map.put(2, "lisi");
        map.put(3, "wangwu");
        //Set集合中元素的类型是:Map.Entry
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        //迭代器,遍历Set集合,每次取出一个Node
        /*Iterator<Map.Entry<Integer,String>> it = set.iterator();
        while (it.hasNext()){
            Map.Entry<Integer,String> node = it.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + "=" + value);
        }*/
        //foreach
        for (Map.Entry<Integer,String> node:set
             ) {
            System.out.println(node.getKey() + "=" + node.getValue());
        }
    }
}

输出:
在这里插入图片描述

HashMap集合

  • HashMap集合概述
    1、 HashMap集合底层是哈希表/散列表的数据结构。
    2、哈希表是一个怎样的数据结构呢?
    哈希表是一个数组和单向链表的结合体。
    数组:在查询方面效率很高,随机增删方面效率很低。
    单向链装:在随机增删方面效率较高,在查询方面效率很低。
    哈希表得以上的两种数据结构融合在一起,充分发挥它们各自的优点。
    3、HashMap集合底层的源代码:
//静态内部类HashMap.Node
static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            /*哈希值(哈希值是key的
            hashCode()方法的执行结果)。
            hash值通过哈希函数/算法,
            可以转换存储成数组的下标。*/
            final K key;
            V value;
            HashMap.Node<K,V> next;
            ...
        }
//HashMap底层实际上就是一个数组。(一维数组)
transient Node<K,V>[] table;

4、HashMap集合的默认初始化容量是16,当HashMap集合底层数组的容量达到75%的时(默认加载因子是0.75),数组开始扩容。HashMap集合的初始化容量必须是2的倍数。

  • map.put(k,v)实现原理:
    第一步:先将k,v封装到Node对象当中。
    第二步:底层会调用k的hashCode0方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals,如果所有的equals方法返回都是false,那么这个新节点将会被添加到链表的末尾;如果其中有一个equals返回了true,那么这个节点的value将会被覆盖。
  • v = map.get(k)实现原理:
    1、先调用k的hashCodeo()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,返回null。
    2、如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals,如果所有equals方法返回false,那么get方法返回null;只要其中有一个节点的k和参数k进行equals的时候返回true,那么此时这个节点的value就是我们要找的,get方法最终返回这个value。
  • 为什么哈希表的随机增删,以及查询效率都很高?
    1、增删是在链表上完成。
    2、查询也不需要都扫描,只需要部分扫描。
    3、重点:通过讲解可以得出HashMap集合的key,会先后调用两个方法:一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写。
  • 图例:
    在这里插入图片描述
  • 同时重写hashCode()和equals():
    1、同一个单向链表上所有节点的hash相同,因为他们的数组下标是一样的。但同一个链表上k和k的equals方法肯定返回的是false,都不相等。
    2、hashCode()方法需要重写,在重写时不可以返回一个固定值,会变成一堆数组,没有链表的概念了。(散列分布不均匀)
    3、所以放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。且hashCode()方法先被调用。
    4、equats方法返回如果是true(同一链表),hashCode()方法返回的值必须一样(哈希值要相同)。
    5、在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。这种方式也是为了提高检索效率,因为二叉树的检素会再次缩小扫描范围。扩容之后的容量是原来的2倍。
    6、代码示例:
import java.util.Objects;

public class Demo{
    private String name;

    public Demo() {
    }

    public Demo(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Demo demo = (Demo) o;
        return Objects.equals(name, demo.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
import java.util.HashSet;
import java.util.Set;

public class DemoTest{
    public static void main(String[] args) {
        Demo d1 = new Demo("zhangsan");
        Demo d2 = new Demo("zhangsan");
        /*重写hashCode()前d1、d2的hashCode不同,
        重写后相同。*/
        System.out.println(d1.hashCode());
        System.out.println(d2.hashCode());
        Set<Demo> demos = new HashSet<>();
        demos.add(d1);
        demos.add(d2);
        /*重写hashCode()前size为2,重写后为1*/
        System.out.println(demos.size());
    }
}
  • HashMap和Hashtable的区别
    1、HashMap集合允许key为null值,但只能有一个。key重复的话value就会被覆盖。
    2、Hashtable的key和value都是不能为null的。HashMap集合的key和value都是可以为null的。
    3、Hashtable方法都带有synchronized,是线程安全的。因为线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,所以就使用较少了。Hashtable和HashMap一样,底层都是哈希表数据结构。Hashtable的初化容量是11,默认加载因子是:0.75。Hashtable的扩容是:原容量乘2再加1。

Hashtable和Properties属性类对象

  • Properties概述
    1、Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。Properties被称为属性类对象。
    2、Properties是线程安全的。
    3、Properties的两个方法:一个存,一个取。
  • 代码示例:
import java.util.Properties;

public class Demo{
    public static void main(String[] args) {
        //创建一个Properties对象
        Properties pro = new Properties();
        //需要掌握Properties的两个方法:一个存,一个取。
        pro.setProperty("username","root");
        pro.setProperty("password","123");
        //通过key获取value
        System.out.println(pro.getProperty("username"));
        System.out.println(pro.getProperty("password"));
    }
}

输出:
在这里插入图片描述

SortedMap和TreeMap

  • 概述
    1、TreeSet集合底层实际上是一个TreeMap
    2、TreeMap集合底层是一个二叉树。
    3、放到TreeSet集合中的元素,等同于放到TreeMap集合的key部分了。
    4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动。称为:可排序集合。(对String也是可排序的)
    代码示例:
import java.util.TreeSet;

public class Demo{
    public static void main(String[] args) {
        TreeSet<String> ts = new TreeSet<>();
        ts.add("zhangsan");
        ts.add("lisi");
        ts.add("wangwu");
        for (String s:ts
             ) {
            //按照字典顺序,升序
            System.out.println(s);
        }
        
        TreeSet<Integer> ts2 = new TreeSet<>();
        ts2.add(30);
        ts2.add(35);
        ts2.add(11);
        ts2.add(24);
        for (Integer i:ts2
             ) {
            System.out.println(i);
        }
    }
}

输出:
在这里插入图片描述

  • TreeSet的自定义类型排序
    1、对于自定义类型来说,没有指定对象之间的比较规则的话,是不可以自动排序的。会出现以下异常:
java.lang.ClassCastException
  • 实现Comparable接口
    1、代码示例:
import java.util.TreeSet;

public class Demo{
    public static void main(String[] args) {
       TreeSet<Person> ts = new TreeSet<>();
       ts.add(new Person(11));
       ts.add(new Person(24));
       ts.add(new Person(35));
       for (Person i : ts) {
            System.out.println(i.toString());
       }
    }
}

class Person implements Comparable<Person>{
    int age;
    public Person(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
    @Override
    public int compareTo(Person o) {
        int age1 = this.age;
        int age2 = o.age;
        /*if (age1==age2){
            return 0;
        }else if (age1>age2){
            return 1;
        }else {
            return -1;
        }*/
        return age1-age2;
    }
}

输出:
在这里插入图片描述
2、对于return age1-age2;的解释:
compareTo方法的返回值很重要。返回0表示相同, value会覆盖;返回大于0,会继续在右子树上找;返回小于0,会继续在左子树上找。
(源码)

while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }

3、int和String同时存在时

public int compareTo(Vip v) {
        return this.num - v.num != 0?
                this.num - v.num : this.name.compareTo(v.name);
        /*if (this.num == v.num){
            //年龄相同时按照字母排序
            return this.name.compareTo(v.name);
        }else {
            return this.num - v.num;
        }*/
    }
  • 自平衡二叉树
    1、遵循左小右大原则存放。
    2、TreeSet集合/TreeMap集合采用的是:中序遍历方式。Iterator迭代器采用的是中序遍历方式(左根右)。
  • 实现比较器接口
    1、TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
    2、代码示例:
import java.util.Comparator;
import java.util.TreeSet;

public class Demo{
    public static void main(String[] args) {
        //创建TreeSet集合的时候需要使用比较器
        TreeSet<User> users = new TreeSet<>(new UserComparator());
        //匿名内部类的方式
        /*TreeSet<User> users = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.age - o2.age;
            }
        });*/
        users.add(new User(11));
        users.add(new User(35));
        users.add(new User(24));
        for (User u:users
             ) {
            System.out.println(u);
        }
    }
}
class User{
    int age;
    public User(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                '}';
    }
}
class UserComparator implements Comparator<User> {
    @Override
    public int compare(User o1, User o2) {
        //指定比较规则,按照年龄排序
        return o1.age - o2.age;
    }
}

3、放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.Lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
4、Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。Comparator接口的设计符合OCP原则。

Collections工具类

1、java.util.Collection是集合接口;
java.util.Collections是集合工具类,方便集合的操作。
2、代码示例:

import java.util.*;

public class Demo{
    public static void main(String[] args) {
        //ArrayList集合是线程不安全的
        List<String> list = new ArrayList<>();
        //变成线程安全的
        Collections.synchronizedList(list);
        list.add("zhangsan");
        list.add("lisi");
        list.add("wangwu");
        //排序
        Collections.sort(list);
        for (String s:list
             ) {
            System.out.println(s);
        }

        List<User> users = new ArrayList<>();
        users.add(new User(11));
        users.add(new User(24));
        /*注意:List集合中元素排序,
        需要保证List集合中的元素实现了Comparable接口。*/
        Collections.sort(users);
        for (User i:users
             ) {
            System.out.println(i);
        }

        Set<String> set = new HashSet<>();
        set.add("curry");
        set.add("curry1");
        set.add("Thompson");
        List<String> list2 = new ArrayList<>(set);
        Collections.sort(list2);
        /*也可以用这种方式排序:
        Collections.sort(list集合,new 比较器对象);*/
        for (String l:list2
             ) {
            System.out.println(l);
        }
    }
}
class User implements Comparable<User>{
    int age;
    public User(int age) {
        this.age = age;
    }
    @Override
    public String
    toString() {
        return "User{" +
                "age=" + age +
                '}';
    }
    @Override
    public int compareTo(User u) {
        return this.age - u.age;
    }
}

输出:
在这里插入图片描述

原文地址:https://www.cnblogs.com/yu011/p/12775812.html