• 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;
        }
    }
    

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

  • 相关阅读:
    ftrace 使用方法
    在Ubuntu custom kernel上裝perf by compile
    [fw]How to use DISM to install a hotfix from within Windows
    Ubuntu下配置了ssh,但是连接很慢
    Fix invisible cursor issue in Ubuntu 13.10
    超強的Linux指令解釋網站《explainshell.com》,學Linux必備!
    python 读取 log日志的编码问题
    随机森林random forest及python实现
    评分卡系列(三):分类学习器的评估
    评分卡系列(二):特征工程
  • 原文地址:https://www.cnblogs.com/yu011/p/12775812.html
Copyright © 2020-2023  润新知