一篇文章搞懂集合

一.为何要用集合?

用数组存储,一旦初始化以后,其长度就确定了,元素的类型也确定了,我们只能操作指定类型的数据,而且是有限个。
而且数组提供的方法非常有限,不适合添加,插入和删除等操作。对于无序、不可重复地存数据的需求不能满足。
针对数组存储的缺点,集合就应运而生了

二.Collection集合继承结构图

在这里插入图片描述
如果两者之间有冲突,以下面的为准
在这里插入图片描述

三.Collection集合的常用方法

1.非常常用的,也是很简单的(5个)

		//声明一个集合
        Collection col = new ArrayList();

        //add方法
        col.add("123");
        col.add(1);
        col.add(new Date());

        //获取元素个数
        System.out.println(col.size());//打印3

        //声明另外一个集合
        Collection col2 = new ArrayList();
        col2.add("111");
        col2.add(1);

        //addAll方法
        col.addAll(col2);

        System.out.println(col.size());//打印5
        System.out.println(col);//直接打印

        //isEmpty()方法
        System.out.println(col.isEmpty());//false

        //clear()方法,清空元素
        col.clear();
        //isEmpty()方法
        System.out.println(col.isEmpty());//true

2.Contains与ContainsAll

需要注意的是:Contains方法比较的是内容而不是地址。

		//声明一个集合
        Collection col = new ArrayList();

        col.add("123");
        col.add(1);
        col.add(false);
        col.add(new Person("张三",18));
        //关于contains方法
        //我们在判断时会调用obj对象所在类的equals()
        //所以向Collection接口的实现类的对象中(比如ArrayList)添加数据obj时,要求obj所在类要重写equals方法

        //调用contains方法
        //★注意:contains比较的是内容而不是地址
        System.out.println(col.contains("123"));//true
        System.out.println(col.contains(new Person("张三",18)));
        /*
        总共比较了四次,因为加了四个对象,而且第四个对象才与要比较的对象是“一样的”
        调用了equals方法
        调用了equals方法
        调用了equals方法
        调用了equals方法
        true
        */
        
        Collection col2 = new ArrayList();
        col2.add("111");
        col2.add(11111);
        col.addAll(col2);
        System.out.println(col.containsAll(col2));//true

3.remove和removeAll

		//声明一个集合
        Collection col = new ArrayList();

        col.add("123");
        col.add(1);
        col.add(false);
        col.add(new Person("张三",18));
        System.out.println(col);

        //remove
        col.remove("123");//调用了String类的equals方法,但是我这里没有重写,也就是没有输出
        col.remove(new Person("张三",18));//这里会调用Person类的equals方法,删除“123”后还需要比较三次,否则需要比较四次
        System.out.println(col);

        Collection col1 = new ArrayList();
        col1.add("123");
        col1.add(1);

        //removeAll:从数学角度来说,就是求差集
        col.removeAll(col1);
        System.out.println(col);
        /*
        [123, 1, false, com.day02.Person@16e8e0a]
        调用了equals方法
        调用了equals方法
        调用了equals方法
        [1, false]
        [false]
        */

4.retainAll求交集和equals

		//声明一个集合
        Collection col = new ArrayList();

        col.add("123");
        col.add(1);
        col.add(false);
        col.add(new Person("张三",18));
        System.out.println(col);//[123, 1, false, com.day02.Person@16e8e0a]

        //创建col1集合
        Collection col1 = new ArrayList();
        col1.add("123");
        col1.add(1);

        //equals方法
        System.out.println(col.equals(col1));//false

        //求集合之间的交集,并且把当前集合修改
        col.retainAll(col1);
        System.out.println(col);//[123, 1]

        //equals
        System.out.println(col.equals(col1));//true

4.hashCode,toArray以及拓展的asList

		//声明一个集合
        Collection col = new ArrayList();

        col.add("123");
        col.add(1);
        col.add(false);
        col.add(new Person("张三",18));
        System.out.println(col);//[123, 1, false, com.day02.Person@16e8e0a]

        //hashCode
        //返回当前对象的哈希值
        System.out.println(col.hashCode());//1475509157

        //toArray,将集合转化为数组
        //这个是Object类型的数组
        Object[] objs = col.toArray();
        for (Object obj: objs){
    
    
            System.out.println(obj);
        }
        /*
        123
        1
        false
        com.day02.Person@16e8e0a
         */

        //拓展:将数组转化为集合
        List<String> strings = Arrays.asList(new String[]{
    
    "aa", "bb", "cc"});
        System.out.println(strings);//[aa, bb, cc]

        //注意:asList方法的一个坑,就是如果是int数组,它会当成是一个元素
        List<int[]> ints = Arrays.asList(new int[]{
    
    111, 222});
        System.out.println(ints + "|" + ints.size());//[[I@34c45dca]|1
        //如果想让它当成是多个元素:
        List<Integer> integers = Arrays.asList(123, 456);
        System.out.println(integers + "|" + integers.size());//[123, 456]|2

5.Iterator以及集合的遍历

(1)简介

首先需要知道迭代器模式是什么
GOF给迭代器模式定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需要暴露该对象的内部细节。迭代器模式,就是为容器而生
而Iterator对象就被称为迭代器,主要用于遍历Collection集合中的元素。(当然,也可以用增强for循环来遍历)

(2)遍历示例

		//声明一个集合
        Collection col = new ArrayList();

        col.add("123");
        col.add(1);
        col.add(false);
        col.add(new Person("张三",18));


        //获取迭代器对象
        Iterator iterator = col.iterator();

        //方式一遍历(不这样用)
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        /*
        123
        1
        false
        com.day02.Person@16e8e0a
        */
        //System.out.println(iterator.next());//NoSuchElementException

        //方式二遍历(也不这样用)
        for (int i = 0;i < col.size();i++){
    
    
            System.out.println(iterator.next());
        }

        //方式三遍历
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }

(3)原理介绍

原理在之前没学数据结构的时候不太清楚,但是现在很清楚,它就类似于链表的一个遍历。这里就不再介绍了。
需要注意的一点是:有时候需要重新获取Iterator,而不是用原来的指针,因为那个是一直往后赋值的。

四.Collection子接口之一:List接口

1.简介

List接口常被称为动态数组,里面的元素有序、可重复
实现类有ArrayList、LinkedList和Vector

2.三个具体实现类的异同

相同点
三个类都实现了List接口,存储数据的特点相同(即有序可重复)
不同点

特点
ArrayList List接口的主要实现类,是线程不安全的,效率高,底层使用Object数组存储。
LinkedList 底层使用双向链表存储,如果需要频繁插入删除操作时,使用效率较高。
Vector List接口的古老实现类(不用了),是线程安全的,效率低,底层使用Object数组存储。

3.ArrayList底层源码

其实学完数据结构后就很好理解,

jdk7:它一开始是创建了容量是10的Object数组(jdk8,当数组容量不够的时候会进行扩容,默认扩为原来的1.5倍。(类似于单例模式的饿汉式)
jdk8:一开始并没有创建长度为10的数组,而是初始化为{},在第一次add的时候才进行创建,后续操作和jdk7无异。(类似于单例模式的懒汉式)

启示
①在开发中,如果想要其效率高一些(说白了就是减少它的扩容次数),建议使用其带参的构造器(其参数就是容量),所以还有一个要求就是要提前预估其容量。
②jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

4.LinkedList底层源码

和数据结构双向链表的基本操作几乎是一样的。

扫描二维码关注公众号,回复: 12453715 查看本文章

5.List接口的常用方法

除了Collection中的常用方法之外,由于List是有序可重复的,所以它又定义了一些其他的方法。

		ArrayList list = new ArrayList();
        list.add(123);
        list.add("111");
        list.add(true);
        list.add(new Person("张三",18));

        //add按照索引值添加
        list.add(1,"BB");
        System.out.println(list);//[123, BB, 111, true, com.day02.Person@16e8e0a]

        //get
        System.out.println(list.get(1));//BB

        //indexOf,返回首次出现的索引位置,如果不存在,返回-1
        System.out.println(list.indexOf("111"));//2

        //lastIndexOf:最后一次出现的索引值
        System.out.println(list.lastIndexOf("111"));//2

        //remove,按照索引删除
        list.remove(1);
        System.out.println(list);//[123, 111, true, com.day02.Person@16e8e0a]

        //set:设置
        list.set(1,"1234");
        System.out.println(list);//[123, 1234, true, com.day02.Person@16e8e0a]

        //subList:返回一个子集合,左闭右开
        System.out.println(list.subList(1,3));//[1234, true]

6.总结:List最常用的方法

		增: add(Object obj)
        删: remove(int index)  /  remove(Object obj)
        改: set(int index,Object ele)
        查: get(int index)
        插: add(int index,Object ele)
        长度: size()
        遍历:①Iterator迭代器方式 ②增强for循环 ③普通循环(List特有的)

五.Collection子接口之二:Set接口

set接口不像List接口,没有定义自己新的方法

1.三个实现类的对比

实现类 特点
HashSet Set的主要实现类,是线程不安全的,可以存储null值
LinkedHashSet 是HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历
TreeSet 可以按照添加对象的指定属性进行排序,底层采用红黑树来存储

2.Set无序、不可重复的理解

以HashSet为例
无序性:
①不是随机性,每次遍历还是那个顺序
②存储的数据在底层数组中并非按照索引的顺序添加(我猜测是哈希表存储),而是由数据的哈希值决定的
比如下面这个图,上面的是有序的数组存储,下面就是“无序”的哈希表存储。学完数据结构后就很好理解。
在这里插入图片描述
不可重复性:
保证添加的元素按照equals方法判断时,不能返回true,即相同的元素只能添加一个(必须重写equals和hashCode方法)

所以:要求:向Set中添加的元素,其所在类一定要重写equals方法和hashCode方法,而且重写的这两个方法尽可能保持一致性,即相等的对象必须具有相等的散列码。(直接用idea的快捷方式生成就可以)

3.TreeSet的使用

TreeSet可以按照添加对象的指定属性进行排序,所以向TreeSet中添加的数据,要求是相同类的对象。也就是说必须是同一类型。

自然排序

实现Comparable接口
它的比较是根据compareTo方法进行比较的,而不是equals
如果compareTo方法返回的是0,则说明两个是一样的,就不再进行添加了。
①比如都是整型:

		Set a = new TreeSet();
        a.add(1);
        a.add(-1);
        a.add(99);
        a.add(-999);
        Iterator iterator = a.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }

输出结果就是

-999
-1
1
99

②如果是添加对象

		Set a = new TreeSet();
        a.add(new Person("Jack",18));
        a.add(new Person("Hurry",17));
        a.add(new Person("Aric",99));
        a.add(new Person("OldMan",101));
        Iterator iterator = a.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }

那么Person类必须实现Comparable接口,这里我按照姓名进行排序,则实现的具体方法为

	//按照姓名大小进行排序
    @Override
    public int compareTo(Object o) {
    
    
        if(o instanceof Person){
    
    
            Person person = (Person)o;
            return this.name.compareTo(person.name);
        }else{
    
    
            throw new RuntimeException("输入的类型不匹配");
        }
    }

则遍历得到的结果为

Person{
    
    name='Aric', num=99}
Person{
    
    name='Hurry', num=17}
Person{
    
    name='Jack', num=18}
Person{
    
    name='OldMan', num=101}

③第②种情况的延申
可以发现,有两个人名字一样,但是年龄不一样,按照刚刚的方法就有很大的局限性了,所以在这里还需要将刚刚的compareTo方法进行改进

		Set a = new TreeSet();
        a.add(new Person("Jack",18));
        a.add(new Person("Hurry",17));
        a.add(new Person("Aric",99));
        a.add(new Person("OldMan",101));
        a.add(new Person("OldMan",99));
        Iterator iterator = a.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }

改进后的compareTo方法(如果姓名相同,比年龄)

	//按照姓名大小进行排序
    @Override
    public int compareTo(Object o) {
    
    
        if(o instanceof Person){
    
    
            Person person = (Person)o;
            int compare =  this.name.compareTo(person.name);
            if(compare != 0){
    
    
                return compare;
            }else {
    
    
                return Integer.compare(this.num,person.num);
            }
        }else {
    
    
            throw new RuntimeException("输入的类型不匹配");
        }
    }

则输出结果为

Person{
    
    name='Aric', num=99}
Person{
    
    name='Hurry', num=17}
Person{
    
    name='Jack', num=18}
Person{
    
    name='OldMan', num=99}
Person{
    
    name='OldMan', num=101}

定制排序

它的比较是根据compare方法进行比较的,而不是equals

		Comparator com = new Comparator() {
    
    
            //按照年龄大小进行排序
            @Override
            public int compare(Object o1, Object o2) {
    
    
                if(o1 instanceof Person && o2 instanceof Person) {
    
    
                    Person p1 = (Person) o1;
                    Person p2 = (Person) o2;
                    return Integer.compare(p1.num, p2.num);
                }else {
    
    
                    throw  new RuntimeException("类型不匹配");
                }
            }
        };
        
        Set a = new TreeSet(com);
        a.add(new Person("Jack",18));
        a.add(new Person("Hurry",17));
        a.add(new Person("Aric",99));
        a.add(new Person("OldMan",101));
        a.add(new Person("OldMan",99));
        Iterator iterator = a.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }

输出的结果为

Person{
    
    name='Hurry', num=17}
Person{
    
    name='Jack', num=18}
Person{
    
    name='Aric', num=99}
Person{
    
    name='OldMan', num=101}

六.小总结

如果集合种存储的是自定义类的对象,需要自定义类重写哪个/些方法?
List:重写equals()方法
Set
如果是HashSet或LinkedHashSet,则重写equals()方法和hashCode()方法。
如果是TreeSet,则重写compare(自制排序:Comparable)或compareTo方法(自然排序:Comparator)

七.Map接口

1.Map接口继承结构图

在这里插入图片描述

在这里插入图片描述

2.Map各个实现类的特点

key和value类似于函数。

特点
HashMap 是Map的主要实现类,是线程不安全的,效率高。可以存储null的key和value
LinkedHashMap 可以保证在遍历Map元素时,按照添加的顺序实现遍历。对于频繁的遍历操作,此类的执行效率高于HashMap
TreeMap 可以保证按照添加的键值对进行排序,实现排序遍历,此时考虑key的自然排序或者定制排序,而不是value的。底层使用红黑树实现
Hashtable 是Map的古老实现类,是线程安全的,效率低。不能存储null的key和value
Properties 常用来处理配置文件。要求key和value都是String类型

3.Map键值对的理解

在这里插入图片描述

对象 特点
Map中的key 是无序的、不可重复的。使用Set存储所有的key 。所以key所在的类要重写equals和hashCode方法(以hashMap为例)
Map中的value 是无序的、可重复的。使用Collection来存储所有的value。所以value所在的类要重写equals方法
一个键值对 构成了一个Entry对象。Map中的entry是无序的、不可重复的(和key一致),所以使用Set存储所有的entry

4.HashMap的底层实现原理

JDK7:
在执行完这一条语句后

HashMap map = new HashMap();

也就是实例化以后,底层创建了长度是16的一维数组Entry[] table.
当执行时

map.put(key1,value1);
  • (1)首先,会调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过某种算法计算之后,得到在Entry数组中的存放位置。如果没有冲突,则直接存。---------------情况一
  • (2)如果有冲突,则比较key1和已经存在的一个或多个数据的哈希值。
  • 如果key1的哈希值与已经存在在的数据哈希值都不相同,则此键值对添加成功。---------------情况二
  • 如果key1的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,则调用key1所在类的equals(key2)方法进行比较。
    如果equals返回false,则此键值对添加成功。---------------情况三
    如果equals返回true,则使用value1去替换value2。此时的put方法还具有一个修改功能。
    补充:
    ①关于情况二和情况三:此时key1-value1和原来的数据以链表的方式存储。
    ②在不断的添加过程中,会涉及扩容问题,则默认的扩容方式为:扩容为原来容量的2倍,并将原有的数据复制过来。
    jdk8相较于jdk7在底层实现原理的不同:
    ①在new HashMap()之后,底层没有创建一个长度为16的数组,而是数组是Node数组而非Entry数组
    ②在首次调用put方法时,底层创建长度为16的数组。(类似于ArrayList )
    ③jdk7底层结构只有数组+链表。在jdk8中底层结构有数组+链表+红黑树。当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64,则此索引位置上的所有数据改为使用红黑树存储。(提高查找效率)

5.Map接口常用方法

添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet()/values()/entrySet()

没有插入,因为Map是无序的,没法插。

(1)put、putAll、remove、clear(增删改)
	    Map map = new HashMap();

        //put方法
        map.put("AA",123);
        map.put("BB",456);
        map.put(1,999);
        map.put("AA","替换后的");

        System.out.println(map);//{AA=替换后的, BB=456, 1=999}

        //purAll方法
        Map map1 = new HashMap();
        map1.put(11,11);
        map.putAll(map1);

        System.out.println(map);//{AA=替换后的, BB=456, 1=999, 11=11}

        //remove
        map.remove("AA");
        System.out.println(map);//{BB=456, 1=999, 11=11}

        //clear
        map.clear();//与map=null不同
        System.out.println(map);//{}
        System.out.println(map.size());//0
(2)查

put、get、containsKay、containsValue、isEmpty、equals

	   Map map = new HashMap();

       //put方法
       map.put("AA",123);
       map.put("BB",456);
       map.put(1,999);

       //get
       System.out.println(map.get("AA"));//123

       //containsKay
       System.out.println(map.containsKey("AA"));//true

       //containsValue
       System.out.println(map.containsValue(123));//true

       //isEmpty
       System.out.println(map.isEmpty());//false

	   //equals
       Map map1 = new HashMap();
       map1.put(111,111);
       System.out.println(map.equals(map1));//false

       Map map2 = new HashMap();
       map2.putAll(map1);
       System.out.println(map2.equals(map1));//true
(3)遍历(keySet、values、entrySet)

Map没有对应的迭代器,但是可以先得到key的Set集合,然后使用迭代器遍历。也可以得到value的Collection集合,然后使用迭代器遍历。也可以得到所有Entry构成的Set,然后使用迭代器遍历。

		Map map = new HashMap();

        //put方法
        map.put("AA",123);
        map.put("BB",456);
        map.put(1,999);

        //keySet
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }
        /*
        AA
        BB
        1
        */

        //values
        Collection values = map.values();
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()){
    
    
            System.out.println(iterator1.next());
        }
        /*
        123
        456
        999
        */
        //遍历所有的key-value
        Set set1 = map.entrySet();
        Iterator iterator2 = set1.iterator();
        while (iterator2.hasNext()){
    
    
            Object obj = iterator2.next();
            Map.Entry entry = (Map.Entry)obj;
            System.out.println(entry.getKey() +"-->"+ entry.getValue());
        }
        /*
        AA-->123
        BB-->456
        1-->999
        */

6.TreeMap

向TreeMap中添加key-value,要求key必须是由同一个类创建的对象。因为要按照key进行排序(自然排序、定制排序),所以key的类要实现Comparable,或者创建Comparator对象。

自然排序
		Map map = new TreeMap();

        //创建几个User类
        User u1 = new User("bb",11);
        User u2 = new User("aa",22);
        User u3 = new User("dd",2);

        //put方法
        map.put(u1,123);
        map.put(u2,456);
        map.put(u3,999);

        System.out.println(map);
        /*
        {User{name='aa', num=22}=456,
        User{name='bb', num=11}=123,
        User{name='dd', num=2}=999}
        */
定制排序
	    Comparator comparator = new Comparator() {
    
    
            @Override
            public int compare(Object o1, Object o2) {
    
    
                if(o1 instanceof User && o2 instanceof User){
    
    
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return u1.name.compareTo(u2.name);
                }else {
    
    
                    throw new RuntimeException("类型不匹配");
                }
            }
        };
        Map map = new TreeMap(comparator);

        //创建几个User类
        User u1 = new User("bb",11);
        User u2 = new User("aa",22);
        User u3 = new User("dd",2);

        //put方法
        map.put(u1,123);
        map.put(u2,456);
        map.put(u3,999);

        System.out.println(map);
        /*
        {User{name='aa', num=22}=456,
        User{name='bb', num=11}=123,
        User{name='dd', num=2}=999}
        */

7.Properties

常用来处理配置文件。要求key和value都是String类型
直接看使用示例

		Properties pros = new Properties();

        //加载流对应的文件
        FileInputStream fis = new FileInputStream("test.properties") ;
        pros.load(fis);

        System.out.println(pros.getProperty("name"));
        System.out.println(pros.getProperty("password"));
        /*
        Tom
        111222
         */
        fis.close();

八.Collections工具类

Collections是一个操作Collection(Set、List)和Map等集合的工具类

常规方法
		List list = new ArrayList();
        list.add(123);
        list.add(-11);
        list.add(60);
        list.add(200);

        System.out.println(list);//[123, -11, 60, 200]

        //reverse:逆置
        Collections.reverse(list);

        System.out.println(list);//[200, 60, -11, 123]
        /*
        [123, -11, 60, 200]
        [200, 60, -11, 123]
        */

        //shuffle:对List集合元素进行随机排序
        Collections.shuffle(list);
        System.out.println(list);//[60, 200, 123, -11]

        //sort(List):根据元素的自然排序对指定List集合元素按升序排序
        Collections.sort(list);
        System.out.println(list);//[-11, 60, 123, 200]

        //sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序

        //swap:交换
        Collections.swap(list,0,1);
        System.out.println(list);//[60, -11, 123, 200]

        //max与min,得到最大值和最小值

        //frequency:返回指定集合中指定元素的出现次数
        System.out.println(Collections.frequency(list,60));//1

        //copy:复制
        List dest = Arrays.asList(new Object[list.size()]);

        Collections.copy(dest,list);
        System.out.println(dest);//[60, -11, 123, 200]
synchronizedxxx()的使用

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

之前说过ArrayList和HashMap这些是线程不安全的,但是我们也不会因此去选择使用Vector或者Hashtable,而是把他们包装成线程安全的。

List list1 = Collections.synchronizedList(list);

猜你喜欢

转载自blog.csdn.net/afdafvdaa/article/details/113242311