Java集合框架

一、集合框架的概述

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

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

  2、数组优缺点

    2.1、数组在存储多个数据方面的特点:

      > 一单初始化后,其长度就确定了。

      > 数组一旦定义好,其元素的类型也就确定了。只能操作指定类型的数据。

        比如:String[ ]  arr ;int[  ]   arr  ;  可利用多态性,定义Object[  ]   arr  变相操作多类型数据,但Object类中的方法过少,使用不便 ;

    2.2、数组在存储多个数据方面的缺点:

      >  一旦初始化后,其长度就不可修改。

      >  数组中提供的方法非常有限,对于添加删除插入数据等操作,非常不便,同时效率不高。

      >  获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。

      >  数组存储数据的特点:有序、可重复。对无需不可重复的需求不能满足。

二、集合框架

    |---------Collection接口:单列集合,用来存储一个一个的对象。

      |--------List接口:存储有序的、可重复的数据。

        |--------ArrayList

        |--------LinkedList

        |--------Victor

      |--------Set接口:存储无序的不可重复的数据。

        |--------HashSet

        |--------LinkedSet

        |--------TreeSet  

    |--------Map接口:双列集合,用来存储多对(key  -  value)形式的数据。

      |--------HashMap

      |--------LinkedHashMap

      |--------TreeMap

      |--------HashTable

      |--------Properties  

三、Collection中的方法

 @Test
    public void test1(){
        Collection coll = new ArrayList();

        // add(Object e):将任意类型的元素e 添加到coll中
        coll.add("AA");
        coll.add(123);
        coll.add(new Date());

        //int size():返回coll中元素个数
        System.out.println(coll.size());//3

        //addAll(Collection c):将另一个对象中的全部元素添加到调用该方法的对象中。
        Collection coll1 = new ArrayList();
        coll1.add("CC");
        coll1.add(456);
        coll1.add(0.23456789D);
        coll.addAll(coll1);
        System.out.println(coll.size());//6
        System.out.println(coll);//[AA, 123, Sun May 23 21:28:40 CST 2021, CC, 456, 0.23456789]

        //clear():清空当前集合中的所有元素。
        coll.clear();
        System.out.println(coll.size());//0

        //boolean isEmpty():判断当前集合是否为空(size = 0)。
        System.out.println(coll.isEmpty());//true


        //contains(Object o):判断对象o是否在当前集合中
        //需要调用要判断的对象所在类被重写的equals()方法。
        coll.add(new Person("Tom",20));
        System.out.println(coll.contains(new Person("Tom",20)));//true

        //containsAll(Collection c):判断集合o中的元素是否都在调用该方法的集合中。
        coll.add(123);
        coll.add(456);
        List<Integer> list = Arrays.asList(123, 456);
        System.out.println(coll.containsAll(list));//true

        //remove(Object o):删除集合中的某个元素
        System.out.println(coll);//[Person{name='Tom', age=20}, 123, 456]
        coll.remove(123);
        System.out.println(coll);//[Person{name='Tom', age=20}, 456]

        //removeAll(Collection c):从当前集合中删除集合c中所有的元素。
        coll.add(789);
        coll.add(77);
        coll.add(88);
        System.out.println(coll);//[Person{name='Tom', age=20}, 456, 789, 77, 88]
        List<Integer> list1 = Arrays.asList(77, 88);
        coll.removeAll(list1);
        System.out.println(coll);//[Person{name='Tom', age=20}, 456, 789]


        //retainAll(Collection c):将当前集合的元素更改为当前集合与集合c的公共元素。
        coll.add(88888);
        coll.add(99999);
        coll.add(111111);
        coll.add("hello");
        System.out.println(coll);//[Person{name='Tom', age=20}, 456, 789, 88888, 99999, 111111, hello]
        List hello = Arrays.asList(88888, 99999, "hello");
        coll.retainAll(hello);
        System.out.println(coll);//[88888, 99999, hello]


        //equals(Object o):要想返回true,需要当前集合和集合o的元素都相同,如果是List接口下的集合还需要顺序相同。
        Collection collection = new ArrayList();
        collection.add(88888);
        collection.add(99999);
        collection.add(111111);
        collection.add("hello");

        Collection collection2 = new ArrayList();
        collection2.add(88888);
        collection2.add(99999);
        collection2.add(111111);
        collection2.add("hello");

        Collection collection3 = new ArrayList();
        collection3.add(99999);
        collection3.add(88888);
        collection3.add(111111);
        collection3.add("hello");

        System.out.println(collection.equals(collection2));//true
        System.out.println(collection.equals(collection3));//false


        //hashCode():返回当前对象的哈希值
        System.out.println(coll.hashCode());//187713450

        //toArray():集合转换成数组
        System.out.println( Arrays.toString(coll.toArray()));//[88888, 99999, hello]
    }

   集合元素遍历操作,使用迭代器Iterator接口

@Test
    public void test2() {
        Collection collection = new ArrayList();
        collection.add(88888);
        collection.add(99999);
        collection.add(111111);
        collection.add("hello");

        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

  Iterator中的remove方法

@Test
    public void test3() {
        Collection collection = new ArrayList();
        collection.add(88888);
        collection.add(99999);
        collection.add(111111);
        collection.add("hello");

        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            //java.lang.IllegalStateException  如果没有调用next()就直接remove  会报出该异常
            //iterator.remove();
            Object next = iterator.next();
            if ("hello".equals(next)) {
                //通过迭代器的remove方法,而不是集合的remove方法。
                iterator.remove();
                //java.lang.IllegalStateException  如果调用next()方法后 多次调用remove  会报出该异常
                //iterator.remove();
            }
        }
    }

四、List接口

    |---------Collection接口:单列集合,用来存储一个一个的对象。

      |--------List接口:存储有序的、可重复的数据。

        |--------ArrayList:作为List接口的主要实现类,线程不安全,但效率高。底层使用Object类型的数组Object[  ]。

        |--------LinkedList:对于频繁的添加、删除操作,使用此类效率比ArrayList要高。底层使用双向链表存储。

        |--------Victor:作为List接口的古老实现类,线程安全,效率低。底层使用Object类型的数组 Object[  ]  elementData。

  1、ArrayList,LinkedList,Victor三者异同

    同:三个累都是实现了List接口,都存储有序的、可重复的数据。

    不同:

        ArrayList:作为List接口的主要实现类,线程不安全,但效率高。底层使用Object类型的数组Object[  ]。

        LinkedList:对于频繁的添加、删除操作,使用此类效率比ArrayList要高。底层使用双向链表存储。

        Victor:作为List接口的古老实现类,线程安全,效率低。底层使用Object类型的数组 Object[  ]  elementData。

  2、ArrayList源码分析

    2.1、Jdk7

  @Test
    public void test() {
        ArrayList list = new ArrayList();//此时底层创建了长度是10的Object[  ] 数组elementData
        list.add(123);//elementData[ 0 ] = new Integer( 123 )
        //```````此处省略九次add( )调用
        list.add("第11次调用");//如果此次的添加导致elementData数组的容量不足,则扩容。
        //默认情况下,扩容为原来容量的倍。同时将原数组中的数据复制到新的数组中。
        //建议在开发中使用 ArrayList list = new ArrayList( int  capacity)。
    }

 

    2.2、Jdk8中ArrayList的变化  

 @Test
    public void test() {
        ArrayList list = new ArrayList();//此时底层Object[] elementData初始化为{} 并没有创建长度为10的数组。
        list.add(123);//第一次调用add()方法时才创建长度为10的数组。并将数据添加elementData中
        //·········
        //后续的添加和扩容与Jdk7无异。
    }

    2.3、小结

      jdk7中的ArrayList对象创建类似于单利模式的饿汉式,而Jdk8中的ArrayList对象创建类似于单例模式的懒汉式,延迟了数组的创建,节省内存。

  3、LinkedList源码分析  

@Test
    public void test() {
        LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null。
        list.add(123);//将123封装到Node中,创建了Node对象。
        /*其中Node体现了双向链表的特点,其定义方式为:
        private static class Node<E> {
            E item;
            LinkedList.Node<E> next;
            LinkedList.Node<E> prev;

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

   4、List接口中的常用方法(除Collection接口中的方法外)

    

 @Test
    public void test() {
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add(789);
        list.add("aaa");
        list.add("bbb");
        System.out.println(list);//[123, 456, 789, aaa, bbb]

        //插入操作add(int index,Object el);
        list.add(0, "AAAAAA");
        System.out.println(list);//[AAAAAA, 123, 456, 789, aaa, bbb]

        //addAll(int index,Collection els);向集合中index位置插入els的所有元素。
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
        list.addAll(2,list1);
        System.out.println(list);//[AAAAAA, 123, 1, 2, 3, 4, 456, 789, aaa, bbb]

        //get(int index)获取指定位置的元素。
        Object o = list.get(8);
        System.out.println(o);//aaa

        //indexOf(Object obj)返回obj在集合中首次出现的索引。
        System.out.println(list.indexOf(1));//2
        System.out.println(list.indexOf(10));//如果不存在返回-1

        //lastIndexOf(Object obj)返回obj在集合中末次出现的索引。
        list.add(1);
        System.out.println(list.lastIndexOf(1));//10
        System.out.println(list.lastIndexOf(10));//如果不存在返回-1

        //remove(int index)移除指定位置的元素,并返回该元素。
        System.out.println(list);//[AAAAAA, 123, 1, 2, 3, 4, 456, 789, aaa, bbb, 1]
        Object removed = list.remove(10);
        System.out.println(removed);//1
        System.out.println(list);//[AAAAAA, 123, 1, 2, 3, 4, 456, 789, aaa, bbb]

        //set(int index,Object el) 设置指定位置的元素为el。
        list.set(0,"BBBBBBB");
        System.out.println(list);//[BBBBBBB, 123, 1, 2, 3, 4, 456, 789, aaa, bbb]
        //如果指定的index大于等于集合的长度,会报角标越界异常  java.lang.IndexOutOfBoundsException: Index: 10, Size: 10
        //list.set(10,"BBBBBBB");

        //subList(int fromIndex,int toIndex)返回下标从fromIndex到toIndex的子集和。
        List subList = list.subList(1, 5);
        System.out.println(list);//[BBBBBBB, 123, 1, 2, 3, 4, 456, 789, aaa, bbb]
        System.out.println(subList);//[123, 1, 2, 3]
    }

五、Set接口

  1、概述

    1.1、结构

      |---------Collection接口:单列集合,用来存储一个一个的对象。

        |--------Set接口:存储无序的、不可重复的数据。

          |--------HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值。

            |--------LinkedSet:作为HashSet的子类,遍历其内部数据时,可以按照添加时的顺序遍历。

          |--------TreeSet:可以指定按照添加对象的某属性进行排序。

    1.2、Set接口中没有额外定义新的方法,使用的都是Collection接口中声明过的方法。

    1.3、要求:Set中添加的数据,其所属的类一定要重写hashCode( )方法和equals( )方法。

       要求:重写的hashCode( )方法和equals( )尽可能保持一致,相等的对象一定具有相等的散列码。

  2、Set存储无序的、不可重复的数据

    以HashSet为例说明:

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

      2.2、不可重复性:保证添加的元素按照equals方法判断时,不能返回true。即:相同的元素只能添加一个。

  3、添加元素的过程,以HashSet为例。

    我们向HashSet中添加元素a:1、首先调用a元素所在类的HashCode(  )方法,算出其哈希值。

                   2、使用某种算法,通过第一部算出的哈希值算出元素a在HashSet底层数组中的索引位置。

                   3、判断索引位置是否有元素。

                    3.1、如果没有则添加成功。--------->情况一

                    3.2、如果该索引位置存在其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的哈希值。

                      3.2.1、如果哈希值不同,则元素a添加成功。--------->情况二

                      3.2.2、如果哈希值相同,则调用元素a所在类的equals( )方法。

                        3.2.2.1、如果equals( )返回true,则说明元素a 与已存在的元素重复,添加失败。

                        3.2.2.2、如果equals( )返回false,则说明不存在与元素a重复的元素,元素a添加成功。--------->情况三

    对于添加成功的情况二和情况三而言,元素a与已存在指定索引上的数据以链表的方式存储。

  4、LinkedHashSet的使用

      LinkedHashSet作为HashSet的子类,在添加数据时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据。

    优点:对于频繁的遍历操作,性能优于HashSet。

  5、TreeSet的使用

    5.1、向TreeSet中添加的数据必须是同一类型的数据。

    5.2、两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)。

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

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

 六、Map接口:

  1、结构:

    |--------Map接口:双列集合,用来存储多对(key  -  value)形式的数据。

      |--------HashMap:作为Map的主要实现类。线程不安全,效率高。可以存储key或value为null的数据。

            底层:数组+链表(Jdk7及以前)
               数组+链表+红黑树(Jdk8)

        |--------LinkedHashMap:在原有的HashMap底层结构的基础上,添加了一对指针,指向前一个和后一个元素。保证在遍历Map元素时,可以按照添加顺序实现遍历,遍历操作执行效率优于HashMap。

      |--------TreeMap:按照添加的key-value进行排序,实现排序遍历。按照key进行自然排序或定制排序。底层使用的红黑树。

      |--------HashTable:作为Map古老实现类。线程安全,效率低。不能存储为null的key或value。

        |--------Properties:常用来处理配置文件。key和value都是String类型。

  2、Map的理解(已HashMap为例)

    1、Map中的Key:无序的、不可重复的,使用Set存储Key。------------>key所在类型要重写equals( )和hashCode( ) 。

    2、Map中的Value:无序的、可重复的。使用Collection存储所有的value。----------->value所在的类要重写equals( )方法。

    3、一个键值对(key - value)构成了一个Entry对象。

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

  3、HashMap的底层实现原理   

    3.1、Jdk7

      HashMap map = new HashMap();//在实例化之后,底层创建了长度为16的一维数组Entry[] table。

      ·······假设以存放了很多次·······

      map.put("key1","value1");

        1、首先调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。

        2、判断该位置上是否有数据。

          2.1、如果不存在数据,则添加成功。--------->情况1

          2.2、如果存在数据(一个或多个数据(多个数据以链表形式存在)),则比较key1和已存在的一个或多个数据的哈希值。

            2.2.1、如果key1的哈希值与已存在数据的哈希值都不同,则添加成功。--------->情况2

            2.2.2、如果key1的哈希值与已存在数据的哈希值相同,则调用key1所属类的equals( )方法比较key1和哈希值重复数据的key。

              2.2.2.1、如果equals( )返回false,则添加成功。--------->情况3

              2.2.2.2、如果equals( )返回true,则使用value1替换key值相同的数据的value值。

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

        在不断添加的过程中,会涉及到扩容问题,默认的扩容方式为:扩容为原来的2倍,并将原有数据复制过来。

    3.2、Jdk8

      3.2.1、new HashMap( ) :底层没有直接创建长度为16的数组。

      3.2.2、Jdk8底层的数组是Node[ ]   而非 Entry[ ]。

      3.2.3、首次调用put方法时,底层创建长度为16的数组。

      3.2.4、Jdk7底层结构只有数组+链表,而jdk8底层结构为数组+链表+红黑树。

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

      

      DEFAULT_INITIAL_CAPACITY:HashMap的默认容量。

      DEFAULT_LOAD_FACTOR:HashMap默认的加载因子:0.75。

      threshold:扩容的临界值:容量*加载因子。

      TREEIFY_THRESHOLD:8   链表最大长度大于该默认值,转化为红黑树。

      MIN_TREEIFY_CAPACITY:Node[ ]元素树化时,最小的hash表容量。64

   4、LinkedHashMap底层实现原理

    源码中:

    static class Entry<K,V> extends HashMap.Node<K,V> {

      Entry<K,V> before, after

      Entry(int hash, K key, V value, Node<K,V> next) {

        super(hash, key, value, next);

      }

    }

  5、Map中常用方法

 @Test
    public void test1(){
        Map map = new HashMap();
        //添加
        map.put("aa",123);
        map.put("cc",789);
        map.put(1,789);
        map.put("bb",456);
        System.out.println(map);//{aa=123, bb=456, cc=789, 1=789}
        //修改
        map.put("aa",1121);
        System.out.println(map);//{aa=1121, bb=456, cc=789, 1=789}
        Map map1 = new HashMap();
        map1.put("DD",999);
        map1.put("EE",111);
        map1.put("FF",222);
        //全部添加(把一个集合里的元素全部放到当前集合)
        map.putAll(map1);
        System.out.println(map);//{aa=1121, bb=456, cc=789, DD=999, EE=111, FF=222, 1=789}
        //删除
        map.remove("DD");
        System.out.println(map);//{aa=1121, cc=789, bb=456, EE=111, FF=222, 1=789}
        //清空
        map.clear();
        System.out.println(map);//{}

        //获取指定key对应的value
        map.putAll(map1);
        System.out.println(map.get("EE"));//111
        //判断是否包含指定的key
        System.out.println(map.containsKey("EE"));//true
        //判断是否包含指定的value
        System.out.println(map.containsValue(222));//true
        //获取map中元素个数
        System.out.println(map.size());//3
        //判断当前map是否为空
        System.out.println(map.isEmpty());//false
        //判断两个对象是否相同
        System.out.println(map.equals(map1));//true

        //遍历Map
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());/*DD  EE  FF*/
        }

        //遍历value
        Collection values = map.values();
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()){
            System.out.println(iterator1.next());/*999   111   222*/
        }

        //遍历key-value
        Set entrySet = map.entrySet();
        Iterator iterator2 = entrySet.iterator();
        while (iterator2.hasNext()){
            Map.Entry next = (Map.Entry) iterator2.next();
            System.out.println(next.getKey() + "   =    " + next.getValue());/*DD   =    999   EE   =    111   FF   =    222*/
        }
    }

   6、TreeMap

    6.1、TreeMap中添加key-value,要求key必须是同一个类创建的对象,因为要按照key进行排序。

  

posted @ 2021-05-27 23:30  早春的树  阅读(98)  评论(0)    收藏  举报