集合(JAVA)

1.集合

java集合体系可分为Collection与Map两种体系
java集合超详解
Java集合中List,Set以及Map等集合体系详解(史上最全)
java集合(list,set,map)

1.1 继承关系图

在这里插入图片描述

1.2 介绍

Collection 接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List 和Queue集合
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现

Collection子接口之一:Set接口
HashSet LinkedHashSet TreeSet
Collection子接口之二:List接口
ArrayList LinkedList Vector
Map接口
HashMap TreeMap Hashtable

1.3 iterator

Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象。
Iterator仅用于遍历集合,terator本身并不提供承装对象的能力。如果需要创建terator对象,则必须有一个被迭代的集合。
Iterator:迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口)
除了 map 系列的集合,我们都能通过迭代器来对集合中的元素进行遍历
Iterable :存在于 java.lang 包中。
里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器了。
在这里插入图片描述

2.List(有序、可重复)

有序:List里存放的对象插入顺序和遍历出来顺序是一致的
重复:相同的对象可以在List里重复添加
往List集合里插入或删除数据时,会伴随着后面数据的移动

2.1 ArrayList

ArrayList是基于数组的,在初始化ArrayList时,会构建空数组(Object[] elementData={})。ArrayList是一个无序的,它是按照添加的先后顺序排列,当然,他也提供了sort方法,如果需要对ArrayList进行排序,只需要调用这个方法,提供Comparator比较器即可

优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高

注意:contains,indexOf,remove等方法涉及比较的时候,若是自定义对象,则一定要重新实现equals()和hashcode()两个方法,不然无法比较集合中对象是否相同

public class User {
    
    private Integer uid;
    private String uname;

    @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(uid, user.uid) &&
                Objects.equals(uname, user.uname);
    }

    @Override
    public int hashCode() {
        return Objects.hash(uid, uname);
    }

// 创建一个空的数组链表对象list,list用来存放String类型的数据
        ArrayList<User> list = new ArrayList<User>();

        // 增加元素到list对象中
        list.add(new User(1,"Item1"));
        list.add(new User(4,"Item4"));
        list.add(new User(4,"Item4"));
        list.add(new User(2,"Item2"));
        list.add(2, new User(3,"Item3")); // 此条语句将会把“Item3”字符串增加到list的第3个位置。
        list.add(new User(4,"Item4"));

在这里插入图片描述

        // 检查元素的位置###
        int pos = list.indexOf(new User(2,"Item2"));
        System.out.println("User(2,Item2) indexOf: " + pos);

        // 检查数组链表是否为空
        boolean check = list.isEmpty();
        System.out.println("isEmpty: " + check);

        // 获取链表的大小
        int size = list.size();
        System.out.println("size: " + size);

        // 检查数组链表中是否包含某元素###
        boolean element = list.contains(new User(3,"Item3"));
        System.out.println("User(3,Item3) contains: " + element);

        // 获取指定位置上的元素
        User item = list.get(0);
        System.out.println("get(0): " + item);


    //遍历arraylist中的元素
    public void forList(List<User> list,Integer type){
        switch (type){
            case 1 :
                // 第1种方法: 循环使用元素的索引和链表的大小
                System.out.println("fori:");
                for (int i = 0; i < list.size(); i++) {
                    System.out.println("Index: " + i + " - Item: " + list.get(i));
                }
                break;
            case 2 :
                // 第2种方法:使用foreach循环
                System.out.println("foreach:");
                for (User str : list) {
                    System.out.println("Item is: " + str);
                }
                break;
            case 3 :
                // 第3种方法:使用迭代器
                // hasNext(): 返回true表示链表链表中还有元素
                // next(): 返回下一个元素
                System.out.println("iterator:");
                for (Iterator<User> it = list.iterator(); it.hasNext();) {
                    System.out.println("Item is: " + it.next());
                }
                break;
            default:
                System.out.println("无效参数");
        }
    }


在这里插入图片描述

        // 替换元素
        forList(list,1);
        System.out.println("set(1, new User(22,Item22): ");
        list.set(1, new User(22,"Item22"));
        forList(list,1);

在这里插入图片描述

        // 移除元素
        //移除第2个位置上的元素
        forList(list,2);
        System.out.println("remove(1): ");
        list.remove(1);
        forList(list,2);

        // 移除第一次找到的 "Item3"元素###
        forList(list,3);
        System.out.println("remove(User(4,Item4)): ");
        list.remove(new User(4,"Item4"));
        forList(list,3);

在这里插入图片描述
转数组

        // 转换 ArrayList 为 Array
        User[] simpleArray = list.toArray(new User[list.size()]);
        System.out.println("The array created after the conversion of our arraylist is: "
                + Arrays.toString(simpleArray));

在这里插入图片描述

2.1 LinkedList

LinkedList是基于链表的,它是一个双向链表,每个节点维护了一个prev和next指针
同时对于这个链表,维护了first和last指针,first指向第一个元素,last指向最后一个元素
LinkedList是一个无序的链表,按照插入的先后顺序排序,不提供sort方法对内部元素排序

优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高

用法同ArrayList,只不过把Arraylist< User>换成LinkedList< User>
LinkedList常用方法

        // 创建一个空的数组链表对象list,list用来存放String类型的数据
        LinkedList<User> list = new LinkedList<User>();

        // 增加元素到list对象中
        list.add(new User(1,"Item1"));
        list.add(new User(4,"Item4"));
        list.add(new User(4,"Item4"));
        list.add(new User(2,"Item2"));
        list.add(2, new User(3,"Item3")); // 此条语句将会把“Item3”字符串增加到list的第3个位置。
        list.add(new User(4,"Item4"));

        // 显示数组链表中的内容
        System.out.println("The arraylist contains the following elements: ");
        for (User user : list) {
            System.out.println("user = " + user);
        }

        // 检查元素的位置###
        int pos = list.indexOf(new User(2,"Item2"));
        System.out.println("User(2,Item2) indexOf: " + pos);

        // 检查数组链表是否为空
        boolean check = list.isEmpty();
        System.out.println("isEmpty: " + check);

        // 获取链表的大小
        int size = list.size();
        System.out.println("size: " + size);

        // 检查数组链表中是否包含某元素###
        boolean element = list.contains(new User(3,"Item3"));
        System.out.println("User(3,Item3) contains: " + element);

        // 获取指定位置上的元素
        User item = list.get(0);
        System.out.println("get(0): " + item);


        // 替换元素
        forList(list,1);
        System.out.println("set(1, new User(22,Item22): ");
        list.set(1, new User(22,"Item22"));
        forList(list,1);

        // 移除元素
        //移除第2个位置上的元素
        forList(list,2);
        System.out.println("remove(1): ");
        list.remove(1);
        forList(list,2);

        // 移除第一次找到的 "Item3"元素###
        forList(list,3);
        System.out.println("remove(User(4,Item4)): ");
        list.remove(new User(4,"Item4"));
        forList(list,3);

        // 转换 ArrayList 为 Array
        User[] simpleArray = list.toArray(new User[list.size()]);
        System.out.println("The array created after the conversion of our arraylist is: "
                + Arrays.toString(simpleArray));

3.Set(无序,唯一)

无序:存储和取出顺序不一致
唯一:元素不可重复
不能通过索引获取元素

3.1 HashSet

HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素
元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性

HashSet是基于HashMap来实现的,操作很简单,更像是对HashMap做了一次“封装”,而且只使用了HashMap的key来实现各种特性,而HashMap的value始终都是PRESENT

HashSet不允许重复(HashMap的key不允许重复,如果出现重复就覆盖),允许null值,非线程安全

覆盖hashCode()方法的原则:
1、一定要让那些我们认为相同的对象返回相同的hashCode值
2、尽量让那些我们认为不同的对象返回不同的hashCode值,否则,就会增加冲突的概率。
3、尽量的让hashCode值散列开(两值用异或运算可使结果的范围更广)
HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,我们应该为保存到HashSet中的对象覆盖hashCode()和equals()

因为将对象加入到HashSet中时,会首先调用hashCode方法计算出对象的hash值,接着根据此hash值调用HashMap中的hash方法,得到的值& (length-1)得到该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,如果相等则不添加

注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时, 也要尽量使不同对象尽量返回不同的Hash码值。

集合转数组的toArray()和toArray(T[] a)方法

        //新增
        Set<User> set = new HashSet<>();
        set.add(new User(1,"Item1"));
        set.add(new User(1,"Item1"));
        set.add(null);
        set.add(null);
        set.add(new User(2,"Item2"));
        set.add(new User(3,"Item3"));
        forSet(set);

        System.out.println("set elements:");
        Set<User> set2 = new HashSet<>();
        set2.add(new User(3,"Item3"));
        set2.add(new User(4,"Item4"));
        //把set2的元素加载到set里
        set.addAll(set2);
        System.out.println("addAll(set2):");
        forSet(set);
        System.out.println("set2 elements: ");
        forSet(set2);

        //是否为空
        System.out.println("set.isEmpty(): " + set.isEmpty());
        //元素大小
        System.out.println("set.size(): " + set.size());
        //集合中是否含有
        System.out.println("set.contains(new User(4,Item4)): " + set.contains(new User(4,"Item4")));
        //删除
        set.remove(new User(3,"Item3"));
        System.out.println("remove(new User(3,Item3)):");
        forSet(set);
        //转数组
        User[] users = set.toArray(new User[set.size()]);
        System.out.println("set.toArray: " + Arrays.toString(users));
        //清空
        set.clear();
        System.out.println("clear:");
        System.out.println("set.size(): " + set.size());
        forSet(set);

在这里插入图片描述

3.2 LinkedHashSet

底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性
线程不安全,效率高
LinkedHashSet 是Hashset的子类
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
LinkedHashSet插入性能略低于Hashset,但在迭代访问Set里的全部元素时有很好的性能
不允许集合元素重复

3.3 TreeSet

底层数据结构是红黑树。(唯一,有序)
1.如何保证元素排序的呢?
自然排序
比较器排序
2.如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
3.因为涉及比较,所以元素不允许有Null

3.3.1 自然排序

若集合元素是自定义的类,那么这个类一定要实现Comparable,不然TreeSet会报错

public class User implements Comparable{

    private Integer uid;
    private String uname;
        @Override
    public int compareTo(Object o) {
        return this.uid.compareTo(((User)o).uid);
    }
        //新增
        Set<User> set = new TreeSet<>();
        set.add(new User(8,"Item8"));
        set.add(new User(1,"Item1"));
        set.add(new User(6,"Item6"));
        set.add(new User(3,"Item3"));
        forSet(set);

        System.out.println("set elements:");
        Set<User> set2 = new HashSet<>();
        set2.add(new User(3,"Item3"));
        set2.add(new User(4,"Item4"));
        //把set2的元素加载到set里
        set.addAll(set2);
        System.out.println("addAll(set2):");
        forSet(set);
        System.out.println("set2 elements: ");
        forSet(set2);

        //是否为空
        System.out.println("set.isEmpty(): " + set.isEmpty());
        //元素大小
        System.out.println("set.size(): " + set.size());
        //集合中是否含有
        System.out.println("set.contains(new User(4,Item4)): " + set.contains(new User(4,"Item4")));
        //删除
        set.remove(new User(3,"Item3"));
        System.out.println("remove(new User(3,Item3)):");
        forSet(set);
        //转数组
        User[] users = set.toArray(new User[set.size()]);
        System.out.println("set.toArray: " + Arrays.toString(users));
        //清空
        set.clear();
        System.out.println("clear:");
        System.out.println("set.size(): " + set.size());
        forSet(set);

在这里插入图片描述

3.3.2 定制排序

可以定义一个实现Comparator接口的类,然后在定义TreeSet的时候指定这个类

public class User{
    private Integer uid;
    private String uname;
        //新增
        Set<User> set = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getUid().compareTo(o2.getUid());
            }
        });
        set.add(new User(8,"Item8"));
        set.add(new User(1,"Item1"));
        set.add(new User(6,"Item6"));
        set.add(new User(3,"Item3"));
        forSet(set);

在这里插入图片描述

4.Map(键值对、键唯一、值不唯一)

Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。
严格来说 Map 并不是一个集合,而是两个集合之间 的映射关系
Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable

  • TreeMap是有序的,HashMap和HashTable是无序的。
  • Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。

这就意味着:

  • Hashtable是线程安全的,HashMap不是线程安全的。
  • HashMap效率较高,Hashtable效率较低。
  • 如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
  • Hashtable不允许null值,HashMap允许null值(key和value都允许)
  • 父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap

在这里插入图片描述

4.1 HashMap

数组方式存储key/value,线程非安全,允许null作为key和value,key不可以重复,value允许重复,不保证元素迭代顺序是按照插入时的顺序,key的hash值是先计算key的hashcode值,然后再进行计算,每次容量扩容会重新计算所以key的hash值,会消耗资源,要求key必须重写equals和hashcode方法
在这里插入图片描述

当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

我们可以看到在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的 元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的效率。

来源: http://blog.csdn.net/fg2006/article/details/6400319

根据上面 put 方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。

从上面的源代码中可以看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry,若equals返回false,表示这个不是想找的对象,继续next,若equals为true,那么就返回该对象。

Java遍历Map对象的四种方式

    public void testMap(){

        HashMap<Integer, User> map = new HashMap<>();
        //新增
        map.put(1,new User(1,"Iterm1"));
        map.put(2,new User(2,"Iterm2"));
        map.put(3,new User(3,"Iterm3"));
        map.put(4,new User(4,"Iterm4"));
        map.put(5,new User(5,"Iterm5"));
        System.out.println("map: ");
        forMap(map.entrySet());
        //新增
        HashMap<Integer, User> map2 = new HashMap<>();
        map2.put(3,new User(3,"Iterm3"));
        map2.put(88,new User(88,"Iterm88"));
        map.putAll(map2);
        System.out.println("map.putAll(map2): ");
        forMap(map.entrySet());

        //获取
        User user = map.get(2);
        System.out.println("map.get(2): " + user);
        //是否为空
        System.out.println("map.isEmpty(): "+ map.isEmpty());
        //元素长度
        System.out.println("map.size(): "+ map.size());
        //获取key集合
        Set<Integer> keySet = map.keySet();
        for (Integer integer : keySet) {
            System.out.println("map.keySet: " + integer);
        }
        //获取value集合
        Collection<User> values = map.values();
        for (User value : values) {
            System.out.println("map.values: " + value);
        }
        //获取entry集合
        Set<Map.Entry<Integer, User>> entries = map.entrySet();
        for (Map.Entry<Integer, User> entry : entries) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        }
        //是否存在key
        System.out.println("map.containsKey(2): " + map.containsKey(2));
        //是否存在value
        System.out.println("map.containsValue(new User(3,Iterm3)): "+ map.containsValue(new User(3,"Iterm3")));
        //删除
        map.remove(3);
        System.out.println("remove(3): ");
        forMap(map.entrySet());
        //清空
        System.out.println("clear(): ");
        map.clear();
        System.out.println("map.size(): " +map.size());
    }

    private void forMap(Set<Map.Entry<Integer, User>> entries2) {
        for (Map.Entry<Integer, User> entry : entries2) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        }
    }

在这里插入图片描述
在这里插入图片描述

4.2 TreeMap

在这里插入图片描述

4.3 HashTable

Java读取Properties文件的六种方法
Java中Properties类的操作

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值