zoukankan      html  css  js  c++  java
  • 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;
        }
    }
    

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

  • 相关阅读:
    SpringMVC中静态获取request对象 Spring中获取 HttpServletRequest对象【转载】
    springcloud 的loadbalancer 轮询算法切换方法 2021.4.3
    springboot项目启动增加图标
    rabbitmq 端口作用以及修改方法
    centos8 安装rabbitmq
    springcloud config client Value获取不到信息的问题的处理方法
    springcloud config配置git作为数据源然后启动报错 If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    Sublime Text的列模式如何操作
    centos8 安装redis
    jQuery简单的Ajax调用
  • 原文地址:https://www.cnblogs.com/yu011/p/12775812.html
Copyright © 2011-2022 走看看