13 - 集合

读书的时候,不要时时刻刻都去咬文嚼字,若是遇到了瓶颈,不妨先退一步,再登高数步,尽量往高处走一走,不登山峰,不显平地。

1.集合的好处

之前保存多个数据使用的是数组,但是数组有不足的地方:

  1. 长度开始时必须指定,而且一旦指定,不能更改。
  2. 保存的必须为同一类型元素。
  3. 使用数组进行增加/删除元素比较麻烦。

集合的好处:

  1. 可以动态保存任意多个对象。
  2. 提供一系列方便的操作对象方法:add、remove、set、get等。
  3. 使用集合添加删除新元素方便。

2.集合的框架体系

Java集合主要分为两大类

Collection 单列集合

Map 双列集合 key-value

3.Collection接口

3.1 特点

  1. Collection实现子类可以存放多个元素,每个元素可以是Object。
  2. Collection的实现类,可以存放重复的元素,有些不可以。
  3. Collection的实现类,有些是有序的(List),有些不是有序的(Set)。
  4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的。

代码示例

public static void main(String[] args) {
    List list = new ArrayList();
    // add:添加单个元素
    list.add("jack");
    list.add(10);
    list.add(true);
    System.out.println("list=" + list);

    // remove:删除指定元素
    list.remove(0); //删除第一个元素
    list.remove(true); //指定删除某个元素
    System.out.println("list=" + list);

    // contains:查找元素是否存在
    System.out.println(list.contains("jack"));

    // size:获取元素个数
    System.out.println(list.size());

    // isEmpty:判断是否为空
    System.out.println(list.isEmpty());

    // clear:清空
    list.clear();
    System.out.println("list=" + list);

    // addAll:添加多个元素
    ArrayList list2 = new ArrayList();
    list2.add("红楼梦");
    list2.add("三国演义");
    list.addAll(list2);
    System.out.println("list=" + list);

    // containsAll:查找多个元素是否都存在
    System.out.println(list.containsAll(list2));

    // removeAll:删除多个元素
    list.add("聊斋");
    list.removeAll(list2);
    System.out.println("list=" + list);//[聊斋]
}

输出结果

3.2 遍历元素 - 使用 Iterator (迭代器)

注意

在调用 iterator.next()方法之前必须要调用 iterator.hasNext()进行检测。若不调用,且下一条数据无效,直接调用 it.next()会抛出 NoSuchElementException异常。

代码示例

public static void main(String[] args) {
    // 创建一个集合
    Collection col = new ArrayList();
    col.add(new Book("三国演义", "罗贯中", 50));
    col.add(new Book("小李飞刀", "古龙", 40));
    col.add(new Book("红楼梦", "曹雪芹", 60));

    // 使用迭代器进行遍历
    // 1. 先得到 col 对应的 迭代器
    Iterator iterator = col.iterator();
    // 2. 使用 while 循环遍历
    while (iterator.hasNext()) {
        Object obj = iterator.next();
        System.out.println("obj=" + obj);
    }
    // 3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
    // iterator.next(); //NoSuchElementException
    // 4. 如果希望再次遍历,需要重置我们的迭代器
    iterator = col.iterator();
    System.out.println("===第二次遍历===");
    while (iterator.hasNext()) {
        Object obj = iterator.next();
        System.out.println("obj=" + obj);
    }
}

class Book {
    private String name;
    private String author;
    private double price;

    //......
}

3.3 遍历元素 - 增强for循环

简化版的 iterator,本质一样。

代码示例

public static void main(String[] args) {
    // 创建集合
    List list = new ArrayList();
    list.add(new Dog("小黑", 3));
    list.add(new Dog("大黄", 100));
    list.add(new Dog("大壮", 8));
    
    // 使用 for 增强
    for (Object dog : list) {
        System.out.println("dog=" + dog);
    }

    // 使用迭代器
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        Object dog = iterator.next();
        System.out.println("dog=" + dog);
    }
}

class Dog {
    private String name;
    private int age;

    //......
}

4.List接口和常用方法

介绍

List接口是Collection的子接口。

特点

  • List集合类中的元素有序、且可以重复。
  • List集合中每个元素都有其对应的顺序索引,可以根据索引存取元素。

4.1 ArrayList(线程不安全,改查效率高)

介绍

  • 可以加入null,并且多个。
  • 底层是用数组来实现数据存储的。
  • 基本等同于Vector,但是ArrayList是线程不安全的(执行效率高),在多线程的情况下,不建议使用ArrayList。

ArrayList的底层操作机制

1. ArrayList中维护了一个Object类型的数组 elementData。

2. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容为10,如果需要再次扩容,则扩容elementData为1.5倍。

3. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,这直接扩容elementData为1.5倍。 

4.2 Vector(线程安全)

底层结构和源码

1. Vector底层也是一个对象数组。

2. Vector是线程同步的,即线程安全,操作方法带有 synchronized关键字。

3. 在开发中,需要线程同步安全时,考虑使用Vector。

ArrayList和Vector的比较

底层结构版本线程安全扩容机制
ArrayList可变数组 Object[]jdk1.2不安全、效率高

有参1.5倍

无参

第一次10

第二次开始1.5倍

Vector可变数组 Object[]jdk1.0安全、效率不高

无参

默认10

满后,按2倍扩容

有参,每次2倍扩容

4.3 LinkedList(线程不安全,增删效率高)

特点

  • LinkedList底层实现了双向链表和双端队列的特点。
  • 可以添加任意元素,元素可以重复,包括null。
  • 线程不安全。

底层操作机制

1. LinkedList底层维护了一个双向链表,其中维护了两个属性first和last分别指向首节点和尾节点。

2. 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next执行后一个节点。最终实现双向链表。

3. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

代码示例

public static void main(String[] args) {
    LinkedList linkedList = new LinkedList();
    linkedList.add(1);
    linkedList.add(2);
    linkedList.add(3);
    System.out.println("linkedList=" + linkedList);

    // 演示一个删除结点的
    linkedList.remove(); // 这里默认删除的是第一个结点
    System.out.println("linkedList=" + linkedList);

    // 修改某个结点对象
    linkedList.set(1, 999);
    System.out.println("linkedList=" + linkedList);

    // 得到某个结点对象
    Object o = linkedList.get(1);
    System.out.println(o);

    // 因为 LinkedList 是 实现了 List 接口, 遍历方式
    System.out.println("===LinkeList 遍历迭代器====");
    Iterator iterator = linkedList.iterator();
    while (iterator.hasNext()) {
        Object next = iterator.next();
        System.out.println("next=" + next);
    }
    System.out.println("===LinkeList 遍历增强 for====");
    for (Object o1 : linkedList) {
        System.out.println("o1=" + o1);
    }
    System.out.println("===LinkeList 遍历普通 for====");
    for (int i = 0; i < linkedList.size(); i++) {
        System.out.println(linkedList.get(i));
    }
}

输出结果

ArrayList和LinkedList比较

底层结构增删的效率改查的效率
ArrayList可变数组较低较高
LinkedList双向链表较高,通过链表追加较低

怎么选择

  • 如果在业务中的改查操作多,选择ArrayList。
  • 如果在业务中的增删操作多,选择LinkedList。
  • 一般来说,在程序中,80%-90%的业务都是查询,因此,大部分情况下会选择ArrayList。

5.Set接口和常用方法

特点

  • 无序(添加和取出的顺序不一致),没有索引。
  • 不允许重复元素,最多包含一个null。

5.1 HashSet(无重复元素,无序)

HashSet底层实际上是HashMap。

代码示例

public static void main(String[] args) {
    HashSet set = new HashSet();
    // 1. 在执行 add 方法后,会返回一个 boolean 值
    // 2. 如果添加成功,返回 true, 否则返回 false
    // 3. 可以通过 remove 指定删除哪个对象
    System.out.println(set.add("john"));//T
    System.out.println(set.add("lucy"));//T
    System.out.println(set.add("john"));//F
    System.out.println(set.add("jack"));//T
    System.out.println(set.add("Rose"));//T
    set.remove("john");
    System.out.println("set=" + set);//3 个
}

5.2 TreeSet(无重复元素,升序)

不允许有重复元素的有序集合。

TreeSet 中的元素默认按照自然顺序(升序)进行排序。如果你想要自定义排序顺序,可以通过提供一个 Comparator 对象给 TreeSet 的构造器来实现。

TreeSet 不是线程安全的。如果你需要在多线程环境下使用 TreeSet,你需要自己处理同步问题,或者使用 Collections.synchronizedSortedSet 方法来获取一个线程安全的 SortedSet

代码示例

public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        // 添加元素
        set.add(3);
        set.add(1);
        set.add(2);

        // 输出集合,元素会按照升序排列
        System.out.println(set); // 输出 [1, 2, 3]

        // 尝试添加重复元素
        set.add(2);
        
        // 再次输出集合,重复的元素不会被添加
        System.out.println(set); // 输出 [1, 2, 3]
        
        // 自定义排序
        TreeSet<String> stringSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
        stringSet.add("Apple");
        stringSet.add("banana");
        stringSet.add("Cherry");
        System.out.println(stringSet); // 输出 [Apple, banana, Cherry],字符串按照不区分大小写的顺序排列
}

6.Map接口和常用方法

特点

  • Map与Collection是并列存在。
  • Map中的key和value可以是任何引用类型的数据。
  • Map的key不允许重复,value可以重复。
  • Map的key只能有一个为null,value可以有多个。
  • 常用String类作为key。
  • key和value之间存在单向一对一关系,即通过key总能找到对应的value。

6.1 HashMap(线程不安全)

HashMap是Map接口使用频率最高的实现类。

代码示例

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("邓超", "孙俪");
    map.put("王宝强", "马蓉");
    map.put("宋喆", "马蓉");
    map.put("刘令博", null);
    map.put(null, "刘亦菲");
    map.put("鹿晗", "关晓彤");

    // 第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
    Set keyset = map.keySet();
    (1) 增强 for
    System.out.println("-----第一种方式-------");
    for (Object key : keyset) {
        System.out.println(key + "-" + map.get(key));
    }
    // (2) 迭代器
    System.out.println("----第二种方式--------");
    Iterator iterator = keyset.iterator();
    while (iterator.hasNext()) {
        Object key = iterator.next();
        System.out.println(key + "-" + map.get(key));
}

    // 第二组: 把所有的 values 取出
    Collection values = map.values();
    // (1) 增强 for
    System.out.println("---取出所有的 value 增强 for----");
    for (Object value : values) {
        System.out.println(value);
    }

    // (2) 迭代器
    System.out.println("---取出所有的 value 迭代器----");
    Iterator iterator2 = values.iterator();
    while (iterator2.hasNext()) {
        Object value = iterator2.next();
        System.out.println(value);
    }

    // 第三组: 通过 EntrySet 来获取 k-v
    Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
    // (1) 增强 for
    System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
    for (Object entry : entrySet) {
        Map.Entry m = (Map.Entry) entry;
        System.out.println(m.getKey() + "-" + m.getValue());
    }
    // (2) 迭代器
    System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
    Iterator iterator3 = entrySet.iterator();
    while (iterator3.hasNext()) {
        Object entry = iterator3.next();
        Map.Entry m = (Map.Entry) entry;
        System.out.println(m.getKey() + "-" + m.getValue());
    }
}

6.2 Hashtable(线程安全)

特点

  • Hashtable使用方法基本上和HashMap一样。
  • Hashtable的k-v都不能为null。
  • Hashtable是线程安全的,HashMap是线程不安全的。

6.3 TreeMap(线程不安全,有序)

特点

  • TreeMap 中的元素总是按照键的自然顺序或者创建 TreeMap 时提供的 Comparator 进行排序
  • 和大多数的 Java 集合类一样,TreeMap 不是线程安全的。

代码示例

public static void main(String[] args) {
    TreeMap<Integer, String> treeMap = new TreeMap<>();
        
    // 添加元素
    treeMap.put(3, "Three");
    treeMap.put(1, "One");
    treeMap.put(2, "Two");
        
    // 输出集合,元素会按照键的升序排列
    System.out.println(treeMap); // 输出 {1=One, 2=Two, 3=Three}
        
    // 获取第一个键和最后一个键
    System.out.println("First key: " + treeMap.firstKey()); // 输出 First key: 1
    System.out.println("Last key: " + treeMap.lastKey()); // 输出 Last key: 3
        
    // 获取子映射
    TreeMap<Integer, String> subMap = (TreeMap<Integer, String>) treeMap.subMap(1, 3);
    System.out.println("Submap: " + subMap); // 输出 Submap: {1=One, 2=Two}
}

7.开发中如何选择集合实现类

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现特性进行选择。

1. 先判断存储的类型

是一组对象【单列】还是一组键值对【双列】。

2. 在判断是否允许重复

如果是一组对象【单列】:Collection接口

允许重复:List

  • 增删多:LinkedList【底层是一个双向链表】
  • 改查多:ArrayList【底层是一个可变数组】

不允许重复:Set

  • 无序:HashSet
  • 排序:TreeSet
  • 插入和取出顺序一致:LinkedHashSet

8.Collections工具类

介绍

  • Collections是一个操作Set、List、Map等集合的工具类。
  • Collections中提供了一系列静态方法对集合元素进行排序、查询、和修改等操作。

主要方法和功能

1. 排序

  • sort(List<T> list):对列表进行自然排序。
  • sort(List<T> list, Comparator<? super T> c):使用指定的比较器对列表进行排序。
  • reverse(List<?> list):反转列表中元素的顺序。
  • shuffle(List<?> list):对列表中的元素进行随机排序(洗牌)。
  • swap(List<?> list, int i, int j):在列表中交换指定位置的两个元素。

2. 搜索与查找

  • binarySearch(List<? extends Comparable<? super T>> list, T key):使用自然顺序对列表进行二分查找。
  • binarySearch(List<?> list, T key, Comparator<? super T> c):使用指定的比较器对列表进行二分查找。
  • max(Collection<? extends T> coll) 和 min(Collection<? extends T> coll):返回集合中的最大和最小元素(根据自然顺序或比较器)。
  • frequency(Collection<?> c, Object o):返回指定元素在集合中出现的次数。

3. 不可变集合

  • emptyList()emptyMap()emptySet():返回不可变的空集合。
  • singletonList(T o)singletonMap(K key, V value)singleton(T o):返回只包含一个元素的不可变集合。
  • unmodifiableList(List<? extends T> list)unmodifiableSet(Set<? extends T> set)unmodifiableMap(Map<? extends K, ? extends V> m) 等:返回指定集合的不可变视图。
  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值