JAVA 集合详解

集合的由来:

​ Java是面向对象语言,如果我们要针对多个对象进行操作,就必须对多个对象进行存储。 而对多个元素进行存储,前面我们学习过数组,数组的弊端,长度固定。这样,数组将不能满足变化的要求。所以,Java就提供了集合供我们使用。

集合的特点:

​ 长度可以发生改变。

​ 只能存储对象。

​ 可以存储多种类型对象(一般存储的还是同一种,因为1.5JDK的新特性 泛型)。

常用集合的分类:

Collection 接口的接口 对象的集合(单列集合)

  • List 接口:有序,可重复
    • LinkedList 接口实现类, 链表结构(双向循环链表),(添加删除快,循环遍历慢)插入删除,没有同步, 线程不安全
    • ArrayList 接口实现类, 数组+线性长度,(添加删除慢,循环遍历快)随机访问, 没有同步, 线程不安全
    • Vector 接口实现类 数组, 同步, 线程安全 (早期出现,和ArrayList使用方法相同,了解,不常用)
      • Stack:栈 是Vector类的实现类,先进后出 (在一端添加删除,特殊的线性结构)
        • push:进栈(添加数据)
        • pop:出栈(删除数据)
        • peek:指向(栈顶,位置)
      • Queue:队列 先进先出
        • 在一端添加,另一端删除(队尾添加,队头删除)
  • Set 接口: 无序。不可重复
    • HashSet 不可重复,基于HashMap实现,在添加的时候,是根据元素的哈希码值相关(hashCode())做比较
      • LinkedHashSet 链表维护元素的插入次序(输出的顺序就是添加的顺序)
    • TreeSet 基于TreeMap实现,底层实现为红黑树结构(特殊的二叉树结构,自动平衡),元素排好序

Map 接口 键值对的集合 (双列集合)

  • Hashtable 接口实现类, 同步, 线程安全,效率低,不可以null键 null值

    • Properties:key和value是String类型,获取时读取 *.properties
  • HashMap 接口实现类 ,没有同步, 线程不安全。效率高,可以null键 null值,添加时和已存在的key作比较(根据哈希码值相关的)

    • LinkedHashMap 双向链表和哈希表实现

    • TreeMap 红黑树对所有的key进行排序(key元素类型一定要实现Comprable接口)

    • Collections:集合的工具类,辅助类


集合详解:

Iterator:迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口是 map 系列集合的顶层接口)
  • Object next():返回迭代器刚越过的元素的引用,返回值是 Object,需要强制转换成自己需要的类型
  • boolean hasNext():判断容器内是否还有可供访问的元素
  • void remove():删除迭代器刚越过的元素

所以除了 map 系列的集合,我们都能通过迭代器来对集合中的元素进行遍历。

注意:我们可以在源码中追溯到集合的顶层接口,比如 Collection 接口,可以看到它继承的是类 Iterable
在这里插入图片描述

说明一下 Iterator 和 Iterable 的区别:

Iterable :存在于 java.lang 包中。
在这里插入图片描述
我们看到里面封装了 Iterator 接口。所以只要实现了Iterable接口的类,就可以使用Iterator迭代器了。

Iterator :存在于 java.util 包中。核心的方法next(),hasnext(),remove()。

这里我们引用一个Iterator 的实现类 ArrayList 来看一下迭代器的使用:暂时先不管 List 集合是什么,只需要看看迭代器的用法就行了

 1         //创建一个 List 集合,典型实现为 ArrayList。
 2         List list = new ArrayList();
 3         //添加三个元素
 4         list.add("Jason");
 5         list.add("Peter");
 6         list.add("Code");
 7         //构造 List 的迭代器
 8         Iterator it = list.iterator();
 9         //通过迭代器遍历元素
10         while(it.hasNext()){
11             Object obj = it.next();
12             System.out.println(obj);
13         }
Collection:List 接口和 Set 接口的父接口

在这里插入图片描述

看一下 Collection 集合的使用例子:

 1         //我们这里将 ArrayList集合作为 Collection 的实现类
 2         Collection collection = new ArrayList();
 3         
 4         //添加元素
 5         collection.add("Tom");
 6         collection.add("Bob");
 7         
 8         //删除指定元素
 9         collection.remove("Tom");
10         
11         //删除所有元素
12         Collection c = new ArrayList();
13         c.add("Bob");
14         collection.removeAll(c);
15         
16         //检测是否存在某个元素
17         collection.contains("Tom");
18         
19         //判断是否为空
20         collection.isEmpty();
21         
22         //利用增强for循环遍历集合
23         for(Object obj : collection){
24             System.out.println(obj);
25         }
26         //利用迭代器 Iterator
27         Iterator iterator = collection.iterator();
28         while(iterator.hasNext()){
29             Object obj = iterator.next();
30             System.out.println(obj);
31         }
List :有序,可以重复的集合。

在这里插入图片描述

由于 List 接口是继承于 Collection 接口,所以基本的方法如上所示。

List 接口的三个典型实现:

  • List list1 = new ArrayList();

底层数据结构是数组,查询快,增删慢;线程不安全,效率高

  • List list2 = new Vector();

底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合

  • List list3 = new LinkedList();

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

我在网上看到可以帮助理解记忆的一段:

数组就像身上编了号站成一排的人,要找第10个人很容易,根据人身上的编号很快就能找到。但插入、删除慢,要望某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人始终末尾的也快。

链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。

参考于:https://www.cnblogs.com/ysocean/p/6555373.html

List 接口遍历可以使用普通 for 循环进行遍历,还可以在指定位置添加元素,替换元素等等。

 1      //产生一个 List 集合,典型实现为 ArrayList
 2         List list = new ArrayList();
 3         //添加三个元素
 4         list.add("Tom");
 5         list.add("Bob");
 6         list.add("Marry");
 7         //构造 List 的迭代器
 8         Iterator it = list.iterator();
 9         //通过迭代器遍历元素
10         while(it.hasNext()){
11             Object obj = it.next();
12             //System.out.println(obj);
13         }
14         
15         //在指定地方添加元素
16         list.add(2, 0);
17          
18         //在指定地方替换元素
19         list.set(2, 1);
20          
21         //获得指定对象的索引
22         int i=list.indexOf(1);
23         System.out.println("索引为:"+i);
24         
25         //遍历:普通for循环
26         for(int j=0;j<list.size();j++){
27              System.out.println(list.get(j));
28         } 
Set:典型实现 HashSet()是一个无序,不可重复的集合

在这里插入图片描述

Set接口的几个实现:

  • Set hashSet = new HashSet();
    1. HashSet:不能保证元素的顺序;不可重复;不是线程安全的;集合元素可以为 NULL;
    2. 其底层其实是一个数组,存在的意义是加快查询速度。我们知道在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较,此时的查询效率依赖于查找过程中比较的次数。而 HashSet 集合底层数组的索引和值有一个确定的关系:index=hash(value),那么只需要调用这个公式,就能快速的找到元素或者索引。
    3. 对于 HashSet: 如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
      1. 当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
        1. 如果 hashCode 值不同,直接把该元素存储到 hashCode() 指定的位置
        2. 如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals() 作比较
          1. hashCode 相同,equals 为 true,则视为同一个对象,不保存在 hashSet()中
          2. hashCode 相同,equals 为 false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该约束这种情况,即保证:如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。

注意:每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。

  • Set linkedHashSet = new LinkedHashSet();

    ​ 不可以重复,有序

    因为底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性

  • Set treeSet = new TreeSet();

    1. TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。
    2. 如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口所以, 在其中不能放入 null 元素
    3. 必须放入同样类的对象.(默认会进行排序) 否则可能会发生类型转换异常.我们可以使用泛型来进行限制
    4. 自动排序:添加自定义对象的时候,必须要实现 Comparable 接口,并要覆盖 compareTo(Object obj) 方法来自定义比较规则
      1. 如果 this > obj,返回正数 1
      2. 如果 this < obj,返回负数 -1
      3. 如果 this = obj,返回 0 ,则认为这两个对象相等
	Set set = new TreeSet();
		set.add("a");
		set.add(1);
		System.out.println(set);

在这里插入图片描述

两个对象通过 Comparable 接口 compareTo(Object obj) 方法的返回值来比较大小, 并进行升降序排列

数据类型排序规则
BigDecimal,BigInteger,Byte,Double,Float,Integer,Long,Short按数字大小排序
Character按字符的Unicode值得数字大小排序
String按字符串中字符的Unicode值排序

定制排序: 创建 TreeSet 对象时, 传入 Comparator 接口的实现类.

要求: Comparator 接口的 compare 方法的返回值和 两个元素的 equals() 方法具有一致的返回值

public class TreeSetTest {
    public static void main(String[] args) {
        Person p1 = new Person(1);
        Person p2 = new Person(2);
        Person p3 = new Person(3);
         
        Set<Person> set = new TreeSet<>(new Person());
        set.add(p1);
        set.add(p2);
        set.add(p3);
        System.out.println(set);  //结果为[1, 2, 3]
    }
 
}
 
class Person implements Comparator<Person>{
    public int age;
    public Person(){}
    public Person(int age){
        this.age = age;
    }
    @Override
    /***
     * 根据年龄大小进行排序
     */
    public int compare(Person o1, Person o2) {
        // TODO Auto-generated method stub
        if(o1.age > o2.age){
            return 1;
        }else if(o1.age < o2.age){
            return -1;
        }else{
            return 0;
        }
    }
     
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return ""+this.age;
    }
}

当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果

以上三个 Set 接口的实现类比较:

共同点:

  • 都不允许元素重复
  • 都不是线程安全的类,解决办法:Set set = Collections.synchronizedSet(set 对象)

不同点:

  • **HashSet:**不保证元素的添加顺序,底层采用 哈希表算法,查询效率高。判断两个元素是否相等,equals() 方法返回 true,hashCode() 值相等。即要求存入 HashSet 中的元素要覆盖 equals() 方法和 hashCode()方
  • **LinkedHashSet:**HashSet 的子类,底层采用了 哈希表算法以及 链表算法,既保证了元素的添加顺序,也保证了查询效率。但是整体性能要低于 HashSet
  • **TreeSet:**不保证元素的添加顺序,但是会对集合中的元素进行排序。底层采用 红-黑 树算法(树结构比较适合范围查询)
Map:key-value 的键值对,key 不允许重复,value 可以
  • Map 并不像一个集合,而是两个集合之间 的映射关系。
  • 这两个集合的每一条数据通过映射关系,我们可以看成是一条数据。即 Entry(key,value)。Map 可以看成是由多个 Entry 组成。
  • 因为 Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 for-each 遍历。

HashMap有几个关键点:(了解,真的只是了解一下)

  • 初始容量:16
  • 加载因子:75%
  • resize():扩容的方法
  • hash(key):作比较的方法
  • modCount:统计修改的计数
  • 节点数量超过8(阈值)链表结构转换成红黑树结构
  • 节点数量小于6(阈值)红黑树转换成链表结构

TreeMap和HashMap的方法使用上大同小异,就不写过多的例子了,要不然就该看烦了

要注意一点:TreeMap会对所有的key进行排序,但是key元素类型一定要实现Comprable接口

		Map<String,Object> hashMap = new HashMap<>();
        //添加元素到 Map 中
        hashMap.put("key1", "value1");
        hashMap.put("key2", "value2");
        hashMap.put("key3", "value3");
        hashMap.put("key4", "value4");
        hashMap.put("key5", "value5");
         
        //删除 Map 中的元素,通过 key 的值
        hashMap.remove("key1");
         
        //通过 get(key) 得到 Map 中的value
        Object str1 = hashMap.get("key1");
         
        //可以通过 添加 方法来修改 Map 中的元素
        hashMap.put("key2", "修改 key2 的 Value");
         
        //通过 map.values() 方法得到 Map 中的 value 集合
        Collection<Object> value = hashMap.values();
        for(Object obj : value){
            //System.out.println(obj);
        }
         
        //通过 map.keySet() 得到 Map 的key 的集合,然后 通过 get(key) 得到 Value
        Set<String> set = hashMap.keySet();
        for(String str : set){
            Object obj = hashMap.get(str);
            //System.out.println(str+"="+obj);
        }
         
        //通过 Map.entrySet() 得到 Map 的 Entry集合,然后遍历
        Set<Map.Entry<String, Object>> entrys = hashMap.entrySet();
        for(Map.Entry<String, Object> entry: entrys){
            String key = entry.getKey();
            Object value2 = entry.getValue();
            System.out.println(key+"="+value2);
        }
         
        System.out.println(hashMap);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值