Java集合

1.集合与数组的关系及其概述

1.1集合与数组都是容器

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

注意:此时的存储是内存层面的存储,而非持久化的存储。

1.2数组存储的特点与缺点

数组存储的特点:1)一旦初始化之后,其长度就确定了;2)数组一旦定义好,其元素的类型也就确定了,我们也就只能操作指定类型的数据。

数组存储的缺点:1)一旦初始化之后,其长度就不可修改;2)数组中提供的方法十分有限;3)若想获取数组中实际元素的个数,没有现成的属性和方法可用;4)数组存储的是有序、可重复的数据,无法满足无序、不可重复数据的存储需求。

1.3集合框架

Java集合可分为Collection体系和Map体系

<1>Collection接口:单列数据,定义了存取一组对象的方法的集合

    1)List接口:元素有序,可重复的集合,(习惯上称为“动态数组” );→常用实现类:ArrayList,LinkedArrayList,Vector。

   2)Set接口:元素无序,不可重复的集合;→常用实现类:HashSet,LinkedHashSet,TreeSet。

   3)Quene接口。

<2>Map接口:双列数据,保存具有映射关系的“key-value对”的集合。→常用实现类:HashMap,LinkedHashMap,TreeMap,Hashtable,Properties。

2.Collection接口常用的方法

注意:向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()。

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

public class CommonlyUsedMethod
{
    public static void main(String[] args)
    {
        Collection coll=new ArrayList();
        /**
        1.add(Object e):boolean    将元素e添加到集合coll中
         */
        System.out.println("1.add(Object e)的使用");
        coll.add(1234);
        coll.add("aaa");

        /**
        2.size():int   获取集合中元素的个数
         */
        System.out.println("2.size()的使用");
        System.out.println(coll.size());//输出2

        /**
        3.addAll(Collection coll):boolean    将集合coll中的元素添加到当前的集合中
         */
        System.out.println("3.addAll(Collection coll)的使用");
        Collection coll2=new ArrayList();
        coll2.addAll(coll);
        coll2.add(5678);
        System.out.println(coll2.size());//输出3
        System.out.println(coll2);//输出集合coll2中的元素:[1234, aaa, 5678]

        /**
        4.isEmpty():boolean    判断集合是否为空,即集合中是否有元素存在
         */
        System.out.println("4.isEmpty()的使用");
        System.out.println(coll2.isEmpty()); //输出false

        /**
         5.clear():void    清空集合中的元素
         */
        System.out.println("5.clear()的使用");
        coll.clear();
        coll2.clear();

        /**
         6.contains(Object o):boolean   判断集合中是否含有元素o
         */
        System.out.println("6.contains(Obkect o)的使用");
        coll2.add(123);coll2.add(456);coll2.add(true);coll2.add(new String("Tom"));
        System.out.println(coll2.contains(123));//输出true
        System.out.println(coll2.contains(new String("Tom"))); //输出true,这里其实又造了一个String对象,由此可见查看是否包含有元素,调的是当前类的equals()方法

        /**
         7.containsAll(Collection coll):boolean   判断形参集合coll中的元素是否都在当前集合中
         */
        System.out.println("7.containsAll(Collection coll)的使用");
        coll.add(123);coll.add(true);
        System.out.println(coll2.containsAll(coll)); //输出true

        /**
         8.remove(Object o):boolean   删除当前集合中与元素o值相等的元素,删除成功返回true(这里也是调了equals()方法)
         */
        System.out.println("8.remove(Object o)的使用");
        System.out.println(coll2); //输出[123, 456, true, Tom]
        System.out.println(coll2.remove(456)); //输出true
        System.out.println(coll2); //输出[123, true, Tom]

        /**
         9.removeAll(Collection coll):boolean  差集:当前集合\coll, 在当前集合中删除集合coll中所包含的元素,只要有一个当前集合中有一个元素被删除就返回true
         */
        System.out.println("9.removeAll(Collection coll)的使用");
        System.out.println(coll); //输出[123, true]
        coll2.removeAll(coll);
        System.out.println(coll2);//输出Tom

        /**
         10.retainAll(Collection coll):boolean 交集,获取当前当前集合和集合coll的交集,并把交集赋给当前集合,只要存在交集就返回true
         */
        System.out.println("10.retainAll(Collection coll)的使用");
        coll.add(456);coll.add(789);
        System.out.println(coll);//coll为:[123, true, 456, 789]
        coll2.add(456);coll2.add(789);
        System.out.println(coll2);//coll2为:[Tom, 456, 789]
        System.out.println(coll2.retainAll(coll));//输出true
        System.out.println(coll2);//此时,coll2为:[456, 789]

        /**
         11.equals(Object o):boolean 比较当前集合和形参集合o是否相等(这里List注重有序;而Set注重无序)
         */
        System.out.println("11.equals(Object o)的使用");
        Collection coll3=new ArrayList();
        coll3.add(456);coll3.add(789);
        System.out.println(coll2.equals(coll3));//输出true
        Collection coll4=new ArrayList();
        coll4.add(789);coll4.add(456);
        System.out.println(coll2.equals(coll4));//输出false,因为这里ArrayList需要有序

        /**
         12.hashCode():int  返回当前集合的哈希值
         */
        System.out.println("12.hashcode()的使用");
        System.out.println(coll2.hashCode());//输出15886

        /**
         13.toArray():Object[]  将当前集合中的元素存储在一个数组中
         */
        System.out.println("13.toArray()的使用");
        Object[] arr=coll2.toArray();
        for (Object o: arr)
        {
            System.out.println(o);//遍历输出数组arr中的(其实也是coll2中的)的元素
        }

        /**
         14.拓展补充:Arrays类中的asList方法,可将数组转化为集合
         */
        System.out.println("14.Array中的asList方法");
        String[] strs=new String[]{"qwer","tyui","asdf"};
        List<String> strings = Arrays.asList(strs);
        System.out.println(strings);

        /**
         15.Iterator():Iterator<E>  返回Iterator接口的实例,用于遍历集合元素
         */
    }
}

3.集合元素的遍历

3.1使用迭代器Iterator接口遍历集合

<1>迭代器模式

访问容器对象中的各个元素,而又不暴露该对象的内部细节,主要用于遍历Collection中的元素。

iterator()仅用于遍历集合,且集合对象每次调用iterator(),都会得到一个全新的迭代器对象,默认游标在集合的第一个元素之前。

<2>迭代器的hasNext()和next()方法

hasNext():boolean→以当前位置为基础,判断下一位置是否还有元素

next():E→指针下移,并将下移以后集合位置上的元素返回。

代码:

对于代码中的“Iterator iterator=coll.iterator();”这一句,是因为集合中的iterator()方法返回的是Iterator接口的对象,其实际过程为:ArrayList实现类中定义了一个内部类Itr,Itr实现了Iterator接口,即“private class Itr implemens Iterator<E>”,重写了其中的next(),hasNext()等方法,这里的iterator()方法的方法体中只有一句,即:“return new Itr();”,所以当调用iterator()方法时,得到的是Iterator接口实现类的对象。

@Test
public void nextAndhasNextTest()
    {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(new Person("Jerry", 20));
        coll.add(new String("Tom"));
        coll.add(false);
        Iterator iterator = coll.iterator();
        while (iterator.hasNext())
        {
            System.out.println(iterator.next());
        }
    }

<3>一种用迭代器遍历集合的错误示范

//下面这样写会出现错误,陷入死循环,不断输出集合的第一个元素123
//原因是因为:集合对象每次调用iterator()方法都得到一个全新的迭代器对象!!
//        while(coll.iterator().hasNext())
//        {
//            System.out.println(coll.iterator().next());
//        }

<4>迭代器的remove()方法(注意这是迭代器哦的remove方法)

remove():void  →删除当前迭代器所在位置的元素

注意:如果还未调用next()或在上一次调用next()方法后已经调用了remove()方法,再调用remove()都会报IIlegalStateException异常。

@Test
        public void removeTest()
        {
            Collection coll = new ArrayList();
            coll.add(123);
            coll.add(new Person("Jerry", 20));
            coll.add(new String("Tom"));
            coll.add(false);
            Iterator iterator1 = coll.iterator();
            while (iterator1.hasNext())
            {
                Object o = iterator1.next();
                if ("Tom".equals(o))
                {
                    iterator1.remove();
                }
            }
            iterator1=coll.iterator();//因为上面的while循环使得iterator1指向了最后的位置,所以这里重置一下
            while (iterator1.hasNext())
            {
                System.out.println(iterator1.next());
            }
        }

3.2使用增强for循环遍历集合

<1>快捷键:集合名.for 或者 iter

<2>格式: for(集合中元素的类型   局部变量名:集合对象名) {......}

<3>其内部实质上仍调用了迭代器

<4>增强for循环不会改变原有集合中元素的值

4.Collection子接口之——List接口

4.1List接口概述

<1>List集合类中元素有序,且可重复,每个元素都有其对应的顺序索引;

<2>List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素;

<3>List接口的三个常用类:ArrayList,LinkedList,Vector的异同

同:都实现了List接口,存储数据的特点相同:即有序,可重复的数据

异:1)ArrayList:线程不安全,效率高,底层使用Object[] elementData存储;2)LinkedList:对于频繁地插入、删除操作,使用LinkedList比使用ArrayList的效率要高,底层使用双向链表存储;3)Vector:是List接口的古老实现类,线程安全,效率低,底层使用Object[] elemenData存储,且Vector是同步类,属于强同步类,故开销大于ArrayList。

4.2ArrayList源码分析

ArrayList底层使用了数组Object[] elementData

<1>▲▲jdk7情况下:

1)就ArrayList的初始化而言,ArrayList list=new ArrayList() //底层创建了长度是10的Object[]数组,数组名为elementData

具体情况为:ArrayList.java中存在代码:

    private transient Object[] elementData;
    public ArrayList (int initialCapacity)
    {
        super();
        if(initialCapacity<0)
            throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
        this.elementData=new Object[initialCapacity];
    }
    public ArrayList()
    {this(10);}//当使用空参构造器造ArrayList对象时,定义了一个容量为10的Object型数组

2)就ArrayList添加元素而言

list.add(1); //相当于elementData[0]=new Integer(1);

.......................

list.add(11);// 若此次的添加导致底层的elementData数组容量不够,则需扩容,默认情况下扩容为原来的1.5倍。同时,需将原有数组中的数据复制到新的数组中。

结论:建议开发中使用带参的构造器

<2>▲▲jdk8情况下

ArrayList list=new ArrayList(); //底层Object[] elementData初始化为{},并没有创建长度为10的数组

list.add(1);// 当第一次调用add()方法时,底层才创建了长度为10的数组,并将数据1添加到elementData数组的第一个位置上,后续的添加与扩容与jdk7无异。

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

4.3LinkedList源码分析

LinkedList list=new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null

list.add(123); //将123封装到Node中,创建了Node对象

其中,Node定义为:

private static class Node<E>
    {
        E item;
        Node<E> next;
        Node<E> prev;
        Node(Node<E> prev,E element,Node<E> next)
        {
            this.item=element;
            this.next=next;
            this.prev=prev;
        }
    }

4.4Vector源码分析

在jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组;在扩容方面,默认扩容为原来数组长度的两倍。

4.5List中的常用方法

这里主要讲关于索引的方法,那些继承于Collection接口的方法不在赘述。

1)void add(int index, Object element):在index位置处插入element元素

2)boolean addAll(int index, Collection coll):在index位置开始将集合coll中的所有元素添加到当前集合

3)int indexOf(Object obj):返回obj在当前集合中首次出现的位置(若不存在该元素,返回-1)

4)int lastIndexOf(Object obj):返回obj在当前集合中最后一次出现的位置

5)Object remove(index):移除index位置的元素,并返回该元素

6)Object set(int index, Object element):设置指定index位置的元素为element

7)List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的集合→[fromIndex,toIndex)

一个简单例题:

 这里的remove()方法运用的是remove(int index),因为remove(Object element)还需要进行自动装箱。

4.6List中应熟记的方法

增:add(Object obj)

删:remove(int index) / remove(Object obj)

改:set(int index, Object element)

查:get(int index)

插:add(int index, Object elemrent)

长度:size()

遍历:1)Iterator迭代器方式;2)增强for循环;3)普通for循环

5.Collection子接口之——Set接口

5.1Set接口概述

<1>用来存储无序,不可重复的数据

<2>Set中没有定义新的方法,都继承于Collection

<3>Set的三个常用实现类:HashSet,LinkedHashSet,TreeSet

HashSet:是Set接口的主要实现类,线程不安全,可以存储null值;LinkedHashSet:是HashSet的子类,遍历其内部的数据时,可以按照元素添加的顺序遍历(在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据)<3>TreeSet:可以照添加对象的指定属性,进行排序。

5.2理解Set存储数据的的无序性和不可重复性

以HashSet为例进行说明

<1>无序性:不等于随机性,存储的数据在底层数组中并非按照索引的顺序从前往后来添加数据,而是根据所添加元素的哈希值决定的。

<2>不可重复性:保证添加的元素与之前已有的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个。

5.3Set中元素添加的过程

以HashSet为例进行说明

step1:对要添加的某元素a,先计算a的哈希值(说明在添加对象的类中需要重写hashCode()方法),将得到的哈希值再通过某种算法计算出该元素a在HashSet底层数组中的存放位置,如果这个位置上没有其他元素,则元素a添加成功;

step2:若这个位置上已经有了其他元素,则需逐个比较a和这些元素的哈希值(因为一个位置上可能存放了多个数据,这些数据通过链表相连),若a与这些元素的哈希值都不相等,则元素a添加成功;

step3:若比较过程中发现了与a哈希值相等的元素,则需调用equals()方法(说明在添加对象的类中需要重写equals()方法),若equals()返回flase,则元素a添加成功,若返回true,则添加失败。

注意:若的确进行到了step2和step3,且成功添加了该元素,元素a与之前已经存储于指定索引位置的数据以链表的方式存储。在jdk7中,元素a指向此链表中之前的元素;在jdk8中,链表中的元素指向a。

5.4hashCode()和equals()方法的重写

向Set(主要是实现类HashSet和LinkedHashSet)中添加元素时,这些元素所在的类中需要重写hashCode()和equals()。

<1>重写的基本原则:

1)同一个对象调用hashCode()方法应该返回相同的值;

2)对象中用作equals()方法比较的属性,在计算hashCode值时,也需被用到

<2>为什么使用快捷键自动生成hashCode()方法时,经常会出现31这个数字?

1)减少冲突。选择系数时要选择尽量大的系数。如果计算出来的hash地址越大,所谓的冲突就会越少;并且31是一个素数,不管用什么数来乘它,其公因数只有1和它本身;

2)31只占用了5bits,相乘造成数据溢出的概率较小;

3)提高算法效率。31可以有i*31==(i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。

5.5LinkedHashSet实现类

作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,以用来记录此数据的前一个数据和后一个数据。并且输出时是按照添加数据的顺序输出的。

与HashSet的对比:LinkedHashSet插入性能略低于HashSet(why?是不是其实底层和HashSet一样也还是数组,反而多了两个引用,这样插入时不但要移动其他元素的位置,还要把改一下前后的元素指向);对于频繁地遍历操作,LinkedHashSet效率高于HashSet。

5.6TreeSet实现类

<1>底层是红黑树,故其中的数据有序,查询速度快;

<2>向TreeSet中添加的数据,要求是相同类的对象,否则报ClassCastException错误;

<3>TreeSet中元素若要输出,必须在元素所对应的类中告知元素的排序方法,从而按照一定的顺序输出,不然就无法输出TreeSet中的元素;

<4>TreeSet中对象数据的两种排序方式

▲▲▲自然排序

TreeSet调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认为升序)排列。

几个要求:(以Person类对象为例)

1)若想添加一个对象(Person对象)到TreeSet时,则该对象的类必须实现Comparable接口;

2)实现Comparable的类(Person类)必须实现compareTo(Object obj)方法,两个对象即通过compareTo(Object obj)方法的返回值来比较大小,若两个对象相等,会返回0。特别注意,这里没有用equals()方法;

3)代码展示:

public class Person implements Comparable
{
    public String name;
    public int age;
    public Person()
    {}
    public Person(String name,int age)
    {
        this.name=name;
        this.age=age;
    }
    @Override
    public int compareTo(Object o)
    {
        if(o instanceof Person)
        {
            Person person = (Person) o;
            int compare = this.name.compareTo(person.name);//这里调用了String类中重写的compareTo()方法
            if(compare!=0)
                return compare;
            else
                return Integer.compare(this.age,person.age);//这里调用了包装类Integer中重写的compareTo()方法
        }
        else
            throw new RuntimeException("类型不匹配");
    }
}

▲▲▲定制排序

若集合元素所属的类没有实现Comparable接口,或者希望按照其他属性大小进行排序,则考虑使用定制排序,定义排序通过Comparator接口来实现,需要重写compare(T  o1,T o2)方法。

注意:顺序相等,则返回0,变量o1的顺序在变量o2之前则返回负值数,变量o1的顺序在变量o2之后则返回正值数。以此比较!也就是说,o1-o2也就代表着升序排序;同理o2-o1代表着降序排序。如果有一个n✖2的二维数组array,则对其排序需要进行如下操作,见代码如下:

解释:对array中每个二维分量,按照第一个元素进行升序排序,如果第一个元素相等,则按照第二个元素的降序排序!!

Arrays.sort(array, new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                return a[0]==b[0] ? b[1]-a[1] : a[0]>b[0];
            }
        });

一些要求:

1)利用compare(T  o1,T o2)方法比较o1和o2的大小,若返回正整数,表示o1大于o2;若返回负整数,则反之;若返回0,表示两者相等;

2)要实现定制排序,需要将实现的Comparato接口的实例作为形参传递给TreeSet的构造器,所以只可能在main方法(或单例测试方法)内部来创建一个匿名实现类;

3)代码展示:

这个代码我不知道为什么compare方法中有throw异常,但是方法声明处不写throws来处理,也没有try-catch-finall结构,这是为什么?

public class TreeSetTest
{
    @Test
    public void test()
    {


        Comparator com = new Comparator()
        {
            //照年龄从小到大排列
            @Override
            public int compare(Object o1, Object o2)
            {
                if (o1 instanceof Person && o2 instanceof Person)
                {
                    Person u1 = (Person) o1;
                    Person u2 = (Person) o2;
                    return Integer.compare(u1.age, u2.age);
                } else
                {
                    throw new RuntimeException("输入的数据类型不匹配");
                }
            }
        };
        TreeSet set = new TreeSet(com);//将Comparator接口的实例作为TreeSet构造器的形参
        set.add(new Person("tom", 12));
        set.add(new Person("tom", 48));
        set.add(new Person("herry", 34));
        set.add(new Person("mary", 26));
        System.out.println(set);
    }
}

5.7一个小结

集合Collection中存储的如果是自定义类的对象,需要自定义类重写的方法:

List:equals()方法;、

Set:-----对于HashSet,LinkedHashSet:equals()和hashCode()方法;

         -----对于TreeSet:Comparable接口:重写compareTo()方法或Comparator接口:重写compare(Object o1, Object o2)方法

5.8例题

<1>例题一

对于一个类Person,定义两个属性String name和int age,一个带参构造器,并重写了toString(),hashCode(),equals()方法,现有如下代码,判断其输出内容。

过程解析以及输出结果见代码注释。

        @Test
        public void outPredict()
        {
            HashSet set = new HashSet();
            Person p1 = new Person( "AA",1001);
            Person p2 = new Person( "BB",1002);
            set.add(p1);
            set.add(p2);
            p1.name = "CC";
            /**
             这里删除的仍是p1,但remove()是要先检索Set中是否有元素的哈希值与待删除元素的哈希值相等,
             即,需计算此时p1对应的哈希值,即为Person("CC",1001)的哈希值,在根据该值找到底层数组上对应的位置,
             发现根本没有相同的元素,所以此时返回了false,即没有删除p1
             */
            set.remove(p1);

            /**
            如果真想删除这个Person对象Person("CC",1001),可以使用迭代器中的remove()方法
            注意:迭代器的使用相当于是HashSet类对象将自己的元素暂时托管了给迭代器。所以如果要进行增删操作,
            必须也要通过迭代器对象来完成。查阅源码可知,如果在迭代器里直接对HashSet类对象进行操作,
            会引起元素的修改次数和期待修改次数不等进而报错。
            while (iterator.hasNext())
            {
                Object a=iterator.next();
                if(a instanceof Person)
                {
                   if (a.equals(new Person("CC",1001)))
                    iterator.remove();
                }
            }
             */

            System.out.println(set);
            /**
             此时也是计算Person("CC",1001)的哈希值,在找到对应的位置,不会出现在刚才定义的那两个元素的位置上,
             所以这个元素可以插入
             */
            set.add(new Person("CC",1001));
            System.out.println(set);
            /**
             计算Perosn("AA",1001)哈希值并找到对应位置后,上面已经有了之前的元素Person("CC",1001),再比equals()发现不同,故可以插入
             */
            set.add(new Person("AA",1001));
            System.out.println(set);
        }


//输出结果为:
//[Person{name='BB', age=1002}, Person{name='CC', age=1001}]
//[Person{name='BB', age=1002}, Person{name='CC', age=1001}, Person{name='CC', age=1001}]
//[Person{name='BB', age=1002}, Person{name='CC', age=1001}, Person{name='CC', age=1001}, Person{name='AA', age=1001}]

<2>例题二

 代码如下,问题1)我没有注释,问题2)见大幅注释部分,一些相关说明也见注释:

MyDate类:

package exer1p546;

public class MyDate //implements Comparable
{
    private int year;
    private int month;
    private int day;
    /**
     构造器
     */
    public MyDate(int year,int month,int day)
    {
        this.year=year;
        this.month=month;
        this.day=day;
    }
    /**
     输出年月日
     */
    public String toDateString()
    {
        return year+"年 "+month+"月 "+day+"日 ";
    }
    public int getMonth()
    {
        return month;
    }
    public int getYear() {return year;}
    public int getDay() {return day;}
//    @Override
//    public int compareTo(Object o)
//    {
//        if(o instanceof MyDate)
//        {
//            int a=this.year-((MyDate) o).getYear();
//            if(a!=0)
//                return a;
//            int b=this.month-((MyDate) o).getMonth();
//            if(b!=0)
//                return b;
//            int c=this.day-((MyDate) o).getDay();
//            if(c!=0)
//                return c;
//            else
//                return 0;
//        }
//        else
//            throw new RuntimeException("输入有误");
//    }
}

Employee类:

package exer1p546;

public class Employee implements Comparable
{
    private String name;
    private int age;
    private MyDate birthday;
    public Employee()
    {}
    /**
     构造器
     */
    public Employee(String name,int number,MyDate birthday)
    {
        this.name=name;
        this.age=number;
        this.birthday=birthday;
    }
    /**
     重写toString方法
     */
    @Override
    public String toString()
    {
        return "Employee{" +
                "name='" + name + '\'' +
                ", number=" + age +
                ", birthday=" + birthday.toDateString() +
                '}';
    }
    public MyDate getBirthday()
    {
        return birthday;
    }
    public String getName()
    {
        return name;
    }
    public int getAge()
    {
        return age;
    }
    /**
     实现了Comparable接口的compareTo方法,从而按name排序
     */
    @Override
    public int compareTo(Object o)
    {
        if(o instanceof Employee)
        {
            return this.name.compareTo(((Employee) o).getName());
        }
        else
            throw new RuntimeException("类型错误!");
    }
}

EmployeeTest类:

package exer1p546;

import com.sun.source.util.Trees;

import java.util.Comparator;
import java.util.TreeSet;

public class EmployeeTest
{
    public static void main(String[] args)
    {
        /*
        在EmployeeTest类中实例化Comparator接口来按生日日期排序
        这里我是在MyDate类中实现了Comparable接口的compareTo方法来做的,所以代码是如下这样
        其实也可以直接将MyDate中重写compareTo的代码放到这里,只需做些许改变即可
        */
//        Comparator comparator=new Comparator()
//        {
//            @Override
//            public int compare(Object o1, Object o2)
//            {
//                if(o1 instanceof Employee && o2 instanceof Employee)
//                {
//                    Employee e1=(Employee)o1;
//                    Employee e2=(Employee)o2;
//                    return e1.getBirthday().compareTo(e2.getBirthday());
//                }
//                else
//                    return 0;
//            }
//        };
        Employee e1=new Employee("liudehua",55,new MyDate(1968,12,3));
        Employee e2=new Employee("liming",54,new MyDate(1978,9,3));
        Employee e3=new Employee("zhangxueyou",56,new MyDate(1967,1,3));
        Employee e4=new Employee("guofucheng",50,new MyDate(1971,11,4));
//        TreeSet set1=new TreeSet(comparator);
        TreeSet set1=new TreeSet();
        set1.add(e1);
        set1.add(e2);
        set1.add(e3);
        set1.add(e4);
        System.out.println(set1);
    }
}

6.Map接口

6.1Map接口概述

<1>Map:存储双列数据(key—value对);

<2>Map接口的常用实现类及其简单对比

1)HashMap:是Map的最主要实现类,底层为数组+链表+红黑树,线程不安全,效率高,可以存储null的key和value

          -------LinkedHashMap:是HashMap的子类,保证在遍历Map中的元素时,可以按照添加的                                                  顺序实现遍历(原因是因为在原有的HashMap底层结构的基础上,                                                  在每个元素上添加了一对指针,分别指向添加的前一个元素和后一                                                  个元素)

2)TreeMap:保证按照添加的key-value对进行排序,实现排序遍历,底层为红黑树。此时需要考                         虑key的自然排序或者定制排序。

3)Hashtable:古老的实现类,线程安全,效率低,存储null的key和value

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

🔺🔺HashMap,HashSet,Hashtable的区别与联系:三者的区别

6.2Map结构的理解

一个key-value键值对构成了一个Entry(或Node)对象

<1>Map中的Entry:无序的、不可重复的,使用Set储存所有的Entry;

<2>Map中的key:无序的、不可重复的(故对于储存key的有关实现类而言,对HashMap,需要重写hashCode()和equals()方法;对TreeMap,需要重写自然排序算法或定制排序算法),使用Set储存所有的key;

<3>Map中的value:无序的,可重复的,使用Collection储存所有的value(value所在类需要重写equals()方法)。

6.3Map中常用的方法

额外添加的:

<1>V getOrDefault (Object key, V defaultValue) :意思就是当Map集合中有这个key时,就返回这个key对应的value值,如果没有这个key就使返回默认值defaultValue;

<2>V merge(Object key, V value,  BiFunction<? super V, ? super V, ? extends V> remappingFunction) : 其中BiFunction接口接受两个参数, 返回一个参数,merge方法可以这么理解。如上式,1)key不存在等于是map.put(key, value),后面的处理失效,2)key存在,value就会变成你处理后的value值。

eg:当前存在一个名为map的HashMap型对象,里面存有数据(1,1),(2,3),运行代码

map.merge(1, 2, (v1,v2)->v1+v2);// map中的数据变为(1,3), (2,3)

map.merge(3,1,(v1+v2)->v1+v2); // map中的数据变为(1,3),(2,3),(3,1)

<3>replace(Object key, V newvalue ):将指定键key所对应的value值改为新的的value值;

replace()方法的介绍和使用

6.4HashMap的底层实现原理

<1>在jdk7中的实现原理

1)提前说明几个量:

▲DEFAULT_INITIAL_CAPACITY:刚开始创建的数组的长度;

▲threshold:数组扩容的阈值(=(int)Math.min(capacity*loadFactor, MAXIMUM_CAPACITY+1)),loadFactor为负载因子;

▲hash(key)方法:在使用hashCode()计算一次key的哈希值之后,在使用hash()方法计算一次,以获取更好的散列值;

▲indexFor(int, int)方法:计算元素在数组中的位置;

对其put()方法(即添加元素的方法,这里用的不是add()),有如下源码:

public V put(K key,V value)
{
    if(key==null)  //若key为null
        return putForNullKey(value);
    int hash=hash(key);
    int i=indexFor(hash,table.length); //hash:key经过hashCode和hash方法计算后得到的值;table:数组名
    for(Entry<K,V>e=table[i];e!=null;e=e.next)
    {
        Object k;
        if(e.hash==hash &&(k=e.key)==key||key.equals(k))
        //如果两个键值对哈希值相等并且equals()返回true,则将新值代替旧值
        {
            V oldValue=e.value;
            e.value=value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //运行到这里的话,说明这个元素被成功插入了
    modCount++;
    addEntry(hash,key,value,i);
    return null;
}
void addEntry(int hasn,K key,V value,int bucketIndex)
{
    if(size>= threshold && (null!=table[bucketIndex]))
        //这句的意思是如果数组长度大于threshol
        //且这个元素准备插入的位置上已经有元素了
        //则扩容为原来数组长度的两倍
        //这是为了让每个位置上的链表尽可能的少一些
    {
        resize(2*table.length);
        hash=(null!=key) ? hash(key) : 0;
        bucketIndex=indexFor(hash,table.length); //数组扩容后,重新计算该元素应该插入的位置
    }
    createEntry(hash,key,value,buckrtIndex);
}

2)文字叙述过程

HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
...可能已经执行过多次put...
map.put(key1,value1):
step1:调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置,如果此位置上的数据为空,此时的key1-value1添加成功。
step2:如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。
step3:如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:如果equals()返回false:此时key1-value1添加成功,如果equals()返回true:使用value1替换value2。
补充:

①关于step2和step3:此时key1-value1和原来的数据以链表的方式存储;
②在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来;

<2>HashMap在jdk8与jdk7相比在底层实现方面的不同

1)jdk8中,对于HashMap map=new HashMap(),底层没有创建长度为16的数组;

2)jdk8中,数组类型为Node[],而不是Entry[];

3)首次调用put()方法时,才创建长度为16的数组;

4)形成链表结构时,jdk7中,在头部添加,即新的元素指向旧的元素;jdk8中,在尾部添加,即旧的元素指向新的元素;

5)jdk7中底层结构只有:数组+链表;jdk8中底层结构有:数组+链表+红黑树;

6)jdk8中,当数组的某一个索引位置上的元素以链表形式存在的数据个数>=7且当前数组的长度>64时,此时此索引位置上的数据由链表改为红黑树(提高了查询效率)储存;小于64子需要扩容。

<3>一个问题:负载因子的大小,对HashMap有什么影响?

负载因子的大小决定了 HashMap 的数据密度。
1) 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长 ,
造成查询或插入时的比较次数增多,性能会下降;
2)负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的
几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性
能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建
议初始化预设大一点的空间;
3)按照其他语言的参考及研究经验,会考虑将负载因子设置为 0.7~0.75 ,此
时平均检索长度接近于常数。

6.3LinkedHashMap的底层实现原理

LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap,区别在于:LInkedHashMap内部提供了Entry内部类,替换了HashMap中的Node。

 可以看到,这里多了before和after两个属性,其分别指向前后两个元素。

6.5TreeMap的使用

底层使用红黑树,TreeMap按照key进行排序,并且key必须时同一个类创建的对象,否则抛出异常。

对key的两种排序措施:

1)自然排序

TreeMap的key所在的类必须实现Comparable接口,重写x.compareTo(y)方法;

2)定制排序

当创建TreeMap对象时,在形参处传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序,在这个Comparator对象中使用匿名实现类的方法重写compare(x1,x2)方法。

6.6Properties的使用

Properties类是Hashtable的子类,该对象用于处理属性文件。由于属性文件里的key、value都是字符串类型,所以Properties中存储的key和value也都是String类型的数据。

Properties对象存取数据时,建议使用setProperty(String key, String value)方法和getProperty(String key)方法。

7.Collections工具类的使用

是一个操作Collection和Map对象的工具类

7.1常用的方法

方法        作用
reverse(List)反转List中元素的顺序
shuffle(List)对List集合元素进行随机排序
sort(List)根据元素的自然排序对指定List集合元素升序排序
sort(List, Comparator)根据指定的Comparator产生的顺序对List集合元素进行排序
swap(List,int,int)将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max/min(Collection)根据元素的自然顺序,返回给定集合中的最大/最小元素
Object max/min(Collection,Comparator)根据 Comparator 指定的顺序,返回给定集合中的最大/最小元素
int frequency(Collection,Object)返回指定集合中指定元素的出现次数
void copy(List dest,List src)将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal)使用新值替换 List 对象的所旧值

注意,对于其中的void copy(List dest,List src)方法,不能写成如下形式:

    @Test
    public void copyMethod()
    {
        List src=new ArrayList();
        src.add(123);
        src.add(7);
        List dest=new ArrayList();
        //下面两行错误
        Collections.copy(dest.src);
        System.out.println(dest);
    }

因为新建一个ArrayList对象时,其中没有定义相关容器来存储数据,运行会报IndexOutOfBoundException异常,可以改写如下:

    @Test
    public void copyMethod()
    {
        List src=new ArrayList();
        src.add(123);
        src.add(7);
        List dest=Arrays.asList(new Object[src.size()]);
        Collections.copy(dest,src);
    }

7.2同步控制

Collections类中提供了多个synchronizedXxx()方法,此方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。

eg:synchronizedMap(Map map);

      List dest=new ArrayList(); List dest1=Collections.synchronizedList(dest);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值