zoukankan      html  css  js  c++  java
  • JAVA笔记整理-集合框架-ArrayList类-LinkedList集合-Set集合-Map集合--HashTable

    一、集合框架

    1、为什么会有集合?

    存储多个元素我们以前学过数组类型, 由于数组类型特点是 相同类型且长度固定 ,如果需要存储某一天的新闻数据,用数组不合理 ,无法确定当天数量。 Java中提供可变长度的存储多个元素的数据类型,还可以存储不同数据结构的数据。这样的类型 就是“集合类型”

     数组和集合的区别?
    
     a、数组的长度固定,集合的长度可自动扩容
    
     b、数组的数据类型固定,集合可以存储任意类型 ,集合可以支持泛型
    
     c、数组没有方法,而集合提供大量的方法
    
     d、Java中提供一个动态数组 集合类型,或其他结合类型
    

    2、集合的分布图

    2.1 集合的顶级接口: Collection

       Collection属于单列集合的根接口,它扩展的主要子接口包括 java.util.List  和 java.util.Set接口, 
    

    List接口特点存储有序 且 可重复的元素而Set接口特点存储无序且不可重复的元素,其中List下扩展常用的实现类包括 java.util.ArrayList 和java.util.LinkedList 和Vector , 其中Set接口下扩展的实现类包括 java.util.HashSet 和 java.util.TreeSet .

    集合接口的常用方法:
    
    • public void add(E) : 把给定的元素添加到集合中

    • public void clear():清空集合中的所有元素

    • public boolean remove(E):删除集合中指定的元素,删除成功返回true

    • public boolean contains(E):判断该元素是否存在集合中

    • public boolean isEmpty():判断是否为空集合对象 null会报异常

    • public int size():获取集合元素的个数

    • publict Object toArray() : 将集合元素转成对象数组

        public static void main(String[] args) {
               //通过接口创建实现类 , 可指定存储的泛型
      
              Collection<String> collection = new ArrayList<String>();
              // 集合中指定了元素的类型  String
              collection.add("hello");  // 默认添加到末尾
              collection.add("hi");
              collection.add("哈哈");
              System.out.println("元素大小:"+ collection.size());
              // 删除集合元素  (后面的原始往前 移动)
              collection.remove("hi");
              System.out.println("元素大小:"+collection.size());
              System.out.println("第一个元素:"+((ArrayList<String>) collection).get(0));
              System.out.println("第二个元素:"+((ArrayList<String>) collection).get(1));
      
              // 判断元素是否存在
              System.out.println("是否存在哈哈:"+collection.contains("哈哈"));
              // 转成数组对象
              Object [] objs = collection.toArray();
              //遍历元素
              for(Object obj : objs){
                  System.out.println("数组的元素:"+obj);
              }
              //清空元素  clear
              collection.clear();
              // 大小
              System.out.println("清空后元素的大小(对象依然存在,只能内容为空)"
                      +collection.size());
              // 判断对象中是否是空集合
              System.out.println(collection.isEmpty());
      
            
          }
    

    Iterator 集合遍历接口

        // 直接对集合元素遍历   泛型只能是包装类
              Collection<Integer> scores = new ArrayList<>();
              scores.add(90);
              scores.add(88);
              scores.add(92);
              scores.add(91);
              //遍历集合  使用   ,
              // 再遍历集合时 不能一边遍历集合一边删除集合元素,这样会改变它的遍历的模式
              Iterator<Integer> is = scores.iterator();
              //判断是否有下一个元素   ,如果true ,则可以通过next获取值 
               while(is.hasNext()){
                   Integer score = is.next();
                   System.out.println(score);
               }
      
    

    2.2 ArrayList类:

    java.util.ArrayList是一个数组结构的集合,实现动态数组的功能,扩展所有Collection的方法

    数组结构的本质: 线性结构的顺序结构,ArrayList中使用连续的内存空间存储, 访问时通过下标(元素所在的位置)访问

    ArrayList的数据结构

      分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。

      ArrayList的数据结构是:

        img

      说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。

       源码分析参考:https://www.cnblogs.com/zhangyinhua/p/7687377.html#_lab2_0_1
    
    public static void main(String[] args) {
             //  通过ArrayList 创建集合对象
            // 还可以存储自定义对象  默认容量是 10
            ArrayList<String> list = new ArrayList<String>();
            // 存储有序集合
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");
            // 将元素插入到指定位置
            list.add(1,"ddd");
            //直接 遍历元素  get(Index) 通过下标访问元素
            for(int i = 0 ;i<list.size();i++){
                System.out.println("集合元素:"+ list.get(i));
            }
            // 设置集合的最小容量
            list.ensureCapacity(20);
    
        }
    

    二、LinkedList集合

    java.util.LinkedList集合是java.util.List的实现类,实现List接口的所有方法(添加,删除,查找,判断是空等) ,它添加,删除元素较快,查询相对慢,但是查询头尾元素较快

    LinkedList集合实现双向链表接口,实现从头元素到尾元素的链表和从尾到头元素的链表,目标为了增加元素的检索效率 ,如下图

    关于LinkedList实现大量操作头元素和尾元素的方法。 其中必须通过LinkedList的引用创建该对象

    public void addFirst(E e) :将指定元素插入此列表的开头。

    public void addLast(E e) :将指定元素添加到此列表的结尾。

    public E getFirst() :返回此列表的第一个元素。

    public E getLast() :返回此列表的后一个元素。

    public E removeFirst() :移除并返回此列表的第一个元素。

    public E removeLast() :移除并返回此列表的后一个元素。

    public E pop() :从此列表所表示的堆栈处弹出一个元素。

    public void push(E e) :将元素推入此列表所表示的堆栈。

    public boolean isEmpty() :如果列表不包含元素,则返回true

    练习方法:

    package LinkedList;
    
    import java.util.Arrays;
    import java.util.LinkedList;
    
    /**
     * @ClassName:JAVA
     * @Author:ZhouHongTing
     * @pate:2020/10/28 10:44
     * @Description;
     */
    public class Test {
        public static void main(String[] args) {
            //创建Linkedlist对象
            LinkedList<String> list=new LinkedList<>();
            list.add("苹果");
            list.add("香蕉");
            list.add("草莓");
            list.add("西瓜");
            //遍历元素
            for(String a:list){
                System.out.println(a);
            }
            //删除元素
            list.remove("香蕉");
            list.remove(2);
            System.out.println("删除之后:");
            for(String a:list){
                System.out.println(a);
            }
            //获取指定下标的元素
            System.out.println("指定下标一的元素:"+list.get(1));
            //查询是否存在指定元素
            System.out.println(list.contains("西瓜"));
            System.out.println(list.contains("草莓"));
    
            //创建一个新的集合
            LinkedList<String>list2=new LinkedList<>();
            list2.add("橙子");
            list2.add("哈密瓜");
    
            //将集合list2添加到集合list中
            list.addAll(list2);
            //再次输出
            for(String s:list){
                System.out.println("添加集合list2后:"+s);
                //判断删除指定元素
                if(s.equals("哈密瓜")){
                    System.out.println("删除该元素:");
                    //System.out.println(s);
                    list.remove(s);         
                    //这里只能删除末尾元素,若删除其他位置元素,则会使遍历出现错误
                    //解决办法:应遍历完后再进行添加或删除的操作
                    System.out.println(s);
                }
            }
            System.out.println(Arrays.toString(list.toArray()));
    
            for(int i=0;i<list.size();i++){
                if(list.get(i).equals("草莓")){
                    list.remove(i);
                }
                System.out.println(list.get(i));
            }
            Test.linkedListMethod();
    
    
        }
        public static void  linkedListMethod(){
            //  由于LinkedList实现多个接口 (List 、Deque,Queue)
            LinkedList<String> linkedList = new LinkedList<>();
            // 测试 Deque的方法
            linkedList.add("武汉");
            // 添加到列表的前面
            linkedList.addFirst("长沙");
            //添加到列表的最后
            linkedList.addLast("南京");
            // 类似方法 addFirst
            linkedList.push("上海");
            //  类似方法 addFirst
            linkedList.offerFirst("北京");
            //遍历元素
            for(String s : linkedList){
                System.out.println(s);
            }
            System.out.println("获取元素");
            System.out.println("获取第一个:"+linkedList.getFirst());
            System.out.println("获取最后一个:"+linkedList.getLast());
    
            System.out.println("获取第一个(删除)"+linkedList.pollFirst());//获取并删除
            System.out.println("测试删除第一个后,第一个是否存在"+linkedList.contains("北京"));
        //    System.out.println("测试删除第一个后,第一个元素:"+linkedList.getFirst());
            System.out.println("获取最后一个(删除)"+linkedList.pollLast());//获取并删除
            System.out.println("测试删除第一个后,第一个元素:"+linkedList.poll());
            //遍历元素
            for(String s : linkedList){
                System.out.println(s);
            }
            //获取不删除的
            System.out.println(linkedList.peek());
            System.out.println(linkedList.peekFirst());
            System.out.println(linkedList.peekLast());
            // 弹出第一个元素(删除)
            System.out.println(linkedList.pop());
            System.out.println("最后"+linkedList.get(0));
    
    
        }
    
    }
    

    三、 Set集合

    ​ java.util.Set 接口 继承自Collection接口,实现对元素的基本操作 ,与java.util.List区别于 Set集合存储无序,且存储唯一的元素List存储有序,且可存储重复的元素

    ​ Set接口的实现类 HashSet 、 LinekedHashSet 、TreeSet

    1、HashSet

    ​ HashSet集合依据元素的哈希值确定在内存中的存储位置, 所谓Hash值是内存中哈希表的唯一标志,通过哈希值可快速检索到元素所在的位置 , 所以它查询效率高 ,与HashSet类似结构的包括HashMap 等

    ​ 创建一个HashSet时,就是创建一个HasMap( 关于HashMap结构后面讲)

    ​ 什么是哈希表?

    在Java1.8以前,哈希表的底层实现采用数组+链表结构,但是这样对于“Hash冲突” (两个对象生成的哈希值一样),即多个元素存储在一个“数据桶”中, 这样查找该元素时,依然效率低下, 为了解决由于哈希冲突导致的数据查询效率低下,JDK8以后将哈希表实现采用 数组+链表+红黑树结构

    1.2.HashSet存储自定义对象类型

    ​ HashSet对于对象是否相同的依据,判断对象的hashCode值和equals是否相等,如果它们相等则判断元素一致,不能重复添加

    public class People {
        private int pid; //id 号
        private String pname;
        private int age;
    
        public People(int pid, String pname, int age) {
            this.pid = pid;
            this.pname = pname;
            this.age = age;
        }
    
        public int getPid() {
            return pid;
        }
    
        public void setPid(int pid) {
            this.pid = pid;
        }
    
        public String getPname() {
            return pname;
        }
    
        public void setPname(String pname) {
            this.pname = pname;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        //重写hasCode 与equals的方法,
        //使其以pid(id号)来判断是否重复,若重复则用新数据直接替换掉旧数据
        
        @Override
        public int hashCode() {
            return this.pid;
        }
    
        @Override
        public boolean equals(Object obj) {
             if(this == obj){  //如果相同,则直接返回true
                 return true;
             }
             if(obj instanceof  People){  //若不同,需判断是否属于Ppeople类,在判断是否相等
                 People p = (People) obj;
                 if(p.pid == this.pid && p.getPname().equals(p.getPname())){
                     return true;
                 }
             }
             return false;
        }
    }
    
       //存储 对象类型
            Set<People> sets = new HashSet<>();
            People p = new People(1001,"关羽",100);
            People p2 = new People(1002,"张飞",100);
            People p3 = p2;
            System.out.println("p的hashcode:"+p.hashCode());
            sets.add(p);
            // 检查是否为同一个地址
            sets.add(p);
    
            sets.add(p2);
            sets.add(p3);
    
    
            // 插入一个重新new的张飞对象  HashSet以 equals和hashcode的结果作为是否重复对象的依据
            People p4 = new People(1002,"张飞",90);
            sets.add(p4);  // 由重写的方法以pid 是否相同判断,这里pid相同
    //会当做是重复的对象 ,不能添加成功。
            System.out.println("sets的长度:"+sets.size());
            for(People obj : sets){
                System.out.println(obj.getPid()+"---"+obj.getPname()+"---"+obj.getAge());
            }
    

    1.3 LinkedHashSet

    ​ 在HashSet中存储的数据是唯一且无序,如何保证数据的有序型,可通过扩展HashSet的子类完成,

    java.util.LinkedHashSet ,它实现有序的Hash结构, 它的底层实现使用链表+哈希结构

    创建LinkedHashSet时,就是创建一个LinkedHashMap结构 ,linkeHashSet中如何保证顺序一致性

    ​ accessOrder = false; 按照插入的顺序存储 accessOrder = true: 按照访问的顺序存储。

            // 创建LinkedHashSet对象
            LinkedHashSet<String> set = new LinkedHashSet();
            set.add("aaa");
            set.add("bbb");
            set.add("ccc");
            set.add("ddd");
            //遍历元素
            for(String s : set){
                System.out.println(s);
            }
    

    1.4 TreeSet

    TreeSet实现对Set元素的排序功能, 也包含基础的Set集合功能。 存放在TreeSet中的元素是有序的,默认升序,也可以自定义排序规则。

    两种方式实现自定义排序规则

    1、对元素(自定义类)实现 java.lang.Comparable 接口,重写 compareTo方法

    public class Fruit  implements  Comparable<Fruit>{
        private  int id;
        private String name;
        public int compareTo(Fruit o) {
                // return this.id-o.id; //这里this代表(当前)元素;o 表示下一个元素 
                升序 (前一个元素减后一个元素)
                降序(后一个减前一个元素)
            return  o.id - this.id;
                // 正数: 前一个大于后一个
                // 负数: 前一个小于后一个
        }
     }
    
            // 实现自定义排序规则的方式一 :  对象实现Comparable接口 (java.lang)
            // 重写compareTo 方法。
            TreeSet<Fruit> fruitSet = new TreeSet<>();
            Fruit f1 = new Fruit(100,"苹果");
            Fruit f2 = new Fruit(101,"香蕉");
            fruitSet.add(f1);
            fruitSet.add(f2);
            System.out.println(fruitSet.size());
            for(Fruit f : fruitSet){
                System.out.println(f.getId()+"---"+f.getName());
            }
    
    

    2、通过匿名内部类的方式 在创建TreeSet时,创建自定义排序规则 ,new Comparator的接口

      // 自定义排序规则的方式二: 对treeSet实现匿名内部类  new Comparator(java.util)
            TreeSet<Integer> scores  = new TreeSet (new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o2-o1;  //降序
                }
            });
            //  添加元素
            scores.add(80);
            scores.add(87);
            scores.add(90);
            scores.add(78);
            for(Integer score :  scores){
                System.out.println(score);
            }
    
    
      // 按照对象的某一属性降序
            TreeSet<People> peopleSet = new TreeSet<>(new Comparator<People>() {
                @Override
                public int compare(People o1, People o2) {
                    return o2.getAge()- o1.getAge(); // 根据age降序排列
                }
            });
            peopleSet.add(new People(1001,"张飞",100));
            peopleSet.add(new People(1002,"刘备",102));
            peopleSet.add(new People(1003,"关羽",108));
    
            for(People p : peopleSet){
                System.out.println(p.getPid()+"--"+p.getAge());
            }
            
    

    例题:

    1、创建一个学生类,存储到ArrayList集合中完成指定排序操作
            1.1 按照学生分数降序排列
    

    测试类:

    /*
    1、创建一个学生类,存储到ArrayList集合中完成指定排序操作
            1.1 按照学生分数降序排列
    */
    
    public class Test1 {
        public static void main(String[] args) {
    
            ArrayList<Student> list=new ArrayList<>();
            Student st1=new Student(101,"鲁班",20,60);
            Student st2=new Student(102,"公孙离",18,80);
            Student st3=new Student(103,"虞姬",19,90);
            Student st4=new Student(104,"黄忠",20,70);
            list.add(st1);
            list.add(st2);
            list.add(st3);
            list.add(st4);
            list.sort(new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o1.getScore()-o2.getScore();
                }
            });
            //输出学生成绩
            for(Student s:list){
                System.out.println(s.getName()+"成绩:"+s.getScore());
            }
    
        }
    
    }
    

    四、Map集合

    java.util.Map集合用于存储key-value的数据结构 ,一个键对应一个值,其中键在集合中是唯一的, Value可以重复, 例如 学号与学生的关系,省份编号对应省份信息, 对于Map集合的常用实现类包括 HashMap 、LinkedHashMap、HashTable 、TreeMap 等 。

    1、HashMap(重点)

    ​ java.util.HashMap 存储无序的,键值对数据,HashMap的实现原理在JDK1.8以前使用 链表+数组结构,1.8以后使用链表+数组+红黑树结构, 使用Hash表的存储方式其检索效果高

    ​ 特点:

    ​ a、HashMap的key唯一,且无序,value不唯一

    ​ b、HashMap的key和value 都可以为null

    ​ c、对于相同key 元素,它的value会覆盖原始value

    ​ HashMap的常用方法

    HashMap() 
    构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。  
    HashMap(int initialCapacity) 
    构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。  
    HashMap(int initialCapacity, float loadFactor) 
    构造一个空的 HashMap具有指定的初始容量和负载因子。  
    HashMap(Map<? extends K,? extends V> m) 
    构造一个新的 HashMap与指定的相同的映射 Map 。  
    
    

    ​ a、put(K key,V value) : 存储key-value 到容器中

    ​ b、V get(K key): 根据key 获取对应的value

    ​ c、Set keySet(): 返回所有的key,Set集合

    ​ d、boolean containsKey(K key): 判断key是否存在

    ​ e、clear():清空容器的原始

    ​ f、boolean containsValue(V value):判断value是否存在

    ​ g、Collection values() : 返回所有的value集合

    ​ h、isEmpty(): 判断是否为空集合

    ​ i、remove(Object key) : 根据key删除这个key-value

    ​ j、size():返回元素的大小

    ​ k、Set<Map.Entry<Key,Value>> entrySet(): 返回容器的key-value的实体类的集合,方便遍历元素

    HashMap的源码分析

       // 创建HashMap对象
            Map<String , Integer> cards = new HashMap();
            //存储
            cards.put("红桃",3);
            cards.put("黑桃",3);
            cards.put("方片",2);
            cards.put("梅花",8);
            cards.put("红桃",2); // 会覆盖原始的value
             //获取指定key的value元素
            System.out.println(cards.get("红桃"));
            // 获取所有的key
            Set<String> keys= cards.keySet();
            //通过遍历所有的key 访问对应的value
            for(String k : keys){
                System.out.println(k+"-----"+cards.get(k));
            }
            // 判断key 是否存在, 判断value是否存在
            System.out.println("是否有红桃:"+cards.containsKey("红桃"));
            System.out.println("判断是否有value:"+cards.containsValue(2));
            // 获取所有的value
            Collection<Integer> values =  cards.values();
            Iterator its= values.iterator();
            while(its.hasNext()){
                System.out.println("values ----"+its.next());
            }
    
            //获取所有的元素
            System.out.println(cards.size());
    
            // 遍历map集合元素  entrySet
            Set<Map.Entry<String, Integer>> entrys =   cards.entrySet();
            for(Map.Entry<String, Integer> en  :  entrys ){
                System.out.println("entry遍历方式:"+en.getKey()+"-----"+en.getValue());
            }
            // remove
            System.out.println("删除元素:"+cards.remove("红桃"));
            //清空元素
            cards.clear();
            System.out.println("元素的大小:"+cards.size());
        }
    

    2、HashMap的原理以及源码分析

    ​ HashMap基于键值对存储,这里讲解的jdk8的源码

    ​ HashMap实现步骤:数据结构(数组+链表+红核数)

    1、根据key生成对应是hash值(采用Hash函数生成) ,根据hash值找到该元素所在的数组结构中的位置,如果该位置中存在元素,说明产生了哈希冲突,此时JDK8采用元素尾插入法 (JDK7采用头插法) ,将元素放入链表的尾部,这样可能会形成一条长长的链表。

    ​ 2、当链表长度达到8时,此时会转成 红黑树结构(树形结构的检索效率较高) ,为了提高查询效率。

    HashMap中的一些关键属性和方法

    /**
     * 扩容的临界值,通过capacity * load factor可以计算出来。超过这个值HashMap将进行扩容
     * @serial
     */
    
    int threshold; 
    /**
     * 存储键值对的数组,一般是2的幂
     */
    transient Node<K,V>[] table;
    
    /**
     * 键值对的实际个数
     */
    transient int size;
    
    
    /**
     * 记录HashMap被修改结构的次数。
     * 修改包括改变键值对的个数或者修改内部结构,比如rehash
     * 这个域被用作HashMap的迭代器的fail-fast机制中(参考ConcurrentModificationException)
     */
    transient int modCount;
    
    /**
     * HashMap的节点类型。既是HashMap底层数组的组成元素,又是每个单向链表的组成元素
     */
    static class Node<K,V> implements Map.Entry<K,V> {
            //key的哈希值
        final int hash;
        final K key;
        V value;
            //指向下个节点的引用
        Node<K,V> next;
        
    }
            //增长因子  0.75 
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
       
    
    

    几个常用方法分析:

    1、get(Object)

     public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        
    

    返回目标Node

    final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            // 整个数组长度不为空, 且第一个Node不为空   说明已找到对一个的hash位置
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
                // 表示当前“桶”的第一个元素  的hash值相同,且 key也相同,说明value 是目标查找对象
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                // 说明一个“桶”中有多个元素,  继续找
                if ((e = first.next) != null) {
                    // 多个元素中 需要先判断是否为 “树”结构,因为超过8个长度就转成了数
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    // 一定是单链表结构  ,依次从头找到尾,看有没有对应的 key 
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    

    2、put(K,V)

        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;
           // 判断如果当前集合中没有对应的 “桶”,说明没有出现 “Hash碰撞”
            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) {
                            // 创建一个新节点 创建最后节点 next中
                            p.next = newNode(hash, key, value, null);
                            // 如果长度大于 8 
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                 // 将tab 的桶的所有元素 转成  树结构 
                                treeifyBin(tab, hash);
                            break;
                        }
                        // 如果还没有到达尾部 就找到元素了, 直接返回 
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
               // 如果找到该元素 ,需要替换它的  value  
                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;
        }
    
    

    put插入的流程

    img

    步骤一: 先判断容器是否为空,为空需要扩容,

    步骤二: 根据key 生成hash值,根据hash值找到对应的数组的位置,如果数组位置为空,说明没有hash冲突,直接插入,并长度+1 。

    步骤三:如果数组位置的内容不为空,说明产生hash冲突 ,继续通过key查找元素,如果第一个元素存在(说明还没有生产链表)直接返回value 并覆盖value的值。

    步骤四: 如果key对应的第一个元素不存在,则此时可能出现链表或红黑树, 如果是红黑树采用树结构的插入法(省略分析过程)。 否则一定是链表结构

    步骤五: 如果是链表,将该元素插入到末尾,之后验证整个链表的长度是否大于8 ,如果大于8,将链表转成红黑树结构。

    步骤六:插入成功之后,判断整个容器的元素个数是否超出 扩容的临界值(threshold = capacity* 增长因子)

    关于JDK7.0和JDK8.0的HashMap有什么区别

    1、结构不同

    ​ JDK7采用数组+链表 JDK8采用数组+链表+红黑树

    2、hash值的计算方式不同

    ​ JDK7 table在创建hashmap时分配空间

    ​ JDK8 通过key的hashcode计算,在put时分配空间

    3、发生hash冲突插入方式不同

    ​ JDK7采用头插法,JDK8采用尾插法

    4、resize操作方式不同

    ​ JDK7重写计算index 的值,JDK8通过判断相应的位是0还是1,要么依旧是原index,要么是oldCap + 原index

    3、LinkedHashMap

    由于HashMap存储的key是无序,如果需要存储有序的key可使用LinkedHashMap 它依然满足HashMap的所有特点 ,并在此基础上有序

      
    

    4、TreeMap

    TreeMap实现一个可排序的Map集合 ,默认对key升序排列,也可以降序排序,

    ​ 如果添加元素的key为自定义类,需要实现Comparable接口或Comparator接口。 TreeMap的底层实现是二叉树结构 (有关二叉树的特点 ) ,实现有序的key的分布

     // TreeMap 用于存储可排序的Key -Value集合  ,
        // 其中key必须实现了排序规则的对象 (包装类,String,自定类)
        public static void main(String[] args) {
            // 存储学号和分数   默认对key 进行升序
          //  TreeMap<Integer ,Integer> maps = new TreeMap<>();
    
            TreeMap<Integer,Integer> maps = new TreeMap<>(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o2-o1;   // 降序
                }
            });
    
            maps.put(1003,88);
            maps.put(1002,90);
            maps.put(1004,80);
            maps.put(1001,85);
            //输出
            Set<Integer> keys =  maps.keySet();
            for(Integer k : keys){
                System.out.println(k+"----"+maps.get(k));
            }
    
            // 自定义规则
            // 注意 ,如果key不是包装类而是自定义,必须要求该类实现Comparable或Comparator接口
            TreeMap<Student ,Integer> stuMap = new TreeMap<>();
            stuMap.put(new Student(1001,"张飞"),90);
            stuMap.put(new Student(1003,"刘备"),87);
            stuMap.put(new Student(1002,"关羽"),96);
            // 这里降序排列
            for(Map.Entry<Student,Integer> en : stuMap.entrySet()){
                System.out.println(en.getKey().getSid() + "---"+ en.getValue());
            }
        }
    
    
    class  Student implements  Comparable<Student>{
        private int sid;
        private String sname;
    
        public Student(int sid, String sname){
            this.sid = sid;
            this.sname = sname;
        }
    
        public int getSid() {
            return sid;
        }
    
        public void setSid(int sid) {
            this.sid = sid;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        @Override
        public int compareTo(Student o) {
            return o.sid-this.sid; // this表示前一个对象,  o表示后一个对象
        }
    }
    

    5、HashTable

    ​ HashTable 实现 hash结构的key-value集合, 与HashMap很相似, HashTable 是线程安全(它的很多方法是同步操作),它不需要存储null的 key 和value

    ​ 扩展自 Dictionary类 和 实现Map接口

    常用方法 :

    ​ put ()

    ​ get()

    ​ clear()

    ​ containsKey()

    ​ containsValue()

    ​ 它 有一个子类 是 Properties类,用于存储属性文件的 key- value

     public static void main(String[] args) {
             //创建HashTable   无序
            Hashtable<String,String> tables = new Hashtable<>();
            // 存储
            tables.put("王宝强","马蓉");
            tables.put("贾乃亮","李小璐");
            tables.put("文章","马伊琍");
            //获取   使用所有key遍历  返回枚举类型
            Enumeration<String> keys = tables.keys();
            while(keys.hasMoreElements()){
                String s = keys.nextElement();
                System.out.println(s + "---"+ tables.get(s));
            }
    
            // 有一个HashTable的子类  Properties
            Properties prop  = new Properties();
            prop.setProperty("username","张三");
            prop.setProperty("password","123456");
            //获取对应属性名的值
            System.out.println("根据属性名获取值:"+prop.getProperty("username"));
            System.out.println("根据属性名获取值:"+prop.getProperty("password"));
    
        }
    

    五、集合常见面试题

    1、Collection 和 Collections 的区别? Array 和Arrays的区别?

    ​ Collection是集合的顶级接口 ,Collections是一个集合工具类 ,它提供大量的操作集合方法,例如排序, 打乱顺序,添加元素等。

    ​ Array 表示一个数组对象 , Arrays是一个数组工具类 ,提供大量的数组操作方法。

    2、List 和 Set 的区别? ArrayList 和 LinkedList的区别

    ​ 相同点:List、Set 都继承Collection接口

    ​ 不同点: List 存储不唯一,有序集合元素 , Set 存储唯一,无序的集合元素

    ​ ArrayList 实现数组结构集合,查询比较快,添加,删除效率低

    ​ LinkedList实现双向链表结构集合,添加,删除效率高, 查询效率低

    3、HashSet 和 TreeSet的区别?

    它们都存储唯一集合,都实现Set接口

    HashSet无序,底层实现Hash结构的存储, TreeSet(默认升序)有序,可实现自定义排序,存储树形结构

    4、HashMap 和 HashSet的区别?

    它们都属于Hash结构的集合,存储效率较高 , HashSet是存储单个元素 ,HashMap存储键值对元素

    HashMap实现Map接口,HashSet实现Set接口 ,

    5、HashMap 和HashTable的区别

    它们都来自Map 的实现类, HashTable 还继承一个父类 Dictionary

    • HashMap 的key和value 可以为空,HashTable 的key value 不能为空;
    • HashTable 的子类包含key value 的方法
    • HashMap线程不安全效率高 ,HashTable线程安全效率低

    6、HashMap 和 TreeMap的区别?

    ​ 它们都实现Map接口 ,TreeMap 有序(默认升序),HashMap无序, HashMap实现哈希结构 集合,TreeMap实现二叉树集合。

  • 相关阅读:
    试试用有限状态机的思路来定义javascript组件
    利用grunt-contrib-connect和grunt-connect-proxy搭建前后端分离的开发环境
    利用bootstrap的modal组件自定义alert,confirm和modal对话框
    利用轮播原理结合hammer.js实现简洁的滑屏功能
    等高分栏布局小结
    圣杯布局小结
    浏览器缓存知识小结及应用
    基于淘宝弹性布局方案lib-flexible的问题研究
    淘宝弹性布局方案lib-flexible实践
    Js位置与大小(1)——正确理解和运用与尺寸大小相关的DOM属性
  • 原文地址:https://www.cnblogs.com/z5452830/p/13903332.html
Copyright © 2011-2022 走看看