集合框架

集合概述

生活中的集合:人或事物聚集在一起。
数学中的集合:具有某种特性的事物的整体。
Java中的集合:是工具类,可以存储任意数量的具有共同属性的对象
Java集合中只能存放对象的引用,不能存放基本类型数据

集合与数组

数组的缺陷:长度必须确定且不能改变,类型固定,算法需自己实现
而集合的长度是可以动态改变的,集合允许存储不同类型的对象,集合对外提供了很多算法实现。
用数组去满足存储固定长度的数据场景,比如存储20名学生的信息;而集合更适用于动态变化的场景,比如存储购物车的商品信息。

应用场景
  • 无法预测存储数量的数据
  • 同时存储具有一对一关系的数据(键值对)
  • 需要进行数据的增删
  • 数据重复问题
  • ……

集合框架

为了表示和操作集合而规定的一种统一的标准体系结构。

分类
  • 单列集合Collection
    — List:元素有序、可包含重复元素
    — Set:元素无序、不包含重复元素
  • 双列集合Map:键值映射关系
  • 迭代器Iterator
  • 比较接口Comparable与Comparator
主要组成
  1. 对外的接口
    表示集合的抽象数据类型,提供了让我们对集合中所表示的内容进行单独操作的可能
  2. 接口的实现
    实际上就是那些可复用的数据结构
  3. 对集合运算的算法
    这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现
使用集合框架的好处
  • 减少了程序设计的辛劳
  • 提高了程序速度和质量
  • 减少学习和使用新的API的辛劳
  • 减少了设计新API的努力
  • 集合框架鼓励软件的复用
集合框架的体系结构

Collection接口

用于表示任何对象或元素组,集合框架的顶层接口,想要尽可能以常规方式处理一组元素时,就使用该接口

常用方法

Iterator接口

用于对集合容器进行向前的单方向遍历,通常称为迭代器。该接口的实例也是容器。在返回的迭代器这个容器中,已经将集合中的内容复制了一份。

常用方法
遍历集合
    //创建集合
    List list = new ArrayList();
    //添加元素
    list.add(1);list.add(2);list.add(3);
    //获取迭代器对象进行遍历
    Iterator iterator = list.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }

List接口

List是元素有序并且可以重复的集合,称为序列,顺序性是List最重要的特性。
List可以精确的控制每个元素的插入位置,或删除某个位置的元素。
List的两个主要实现类是ArrayListLinkedList

特有方法

ListIterator接口

list特有的迭代器,内含避免并发修改异常的算法。

并发修改异常

并发修改异常,java.util.ConcurrentModificationException
如果在迭代集合的过程中,使用集合本身添加或删除元素,则会产生该异常。

    //创建集合
    List list = new ArrayList();
    //添加元素
    list.add(1);list.add(2);list.add(3);
    //遍历
    Iterator iterator = list.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
        //在迭代的过程中使用集合本身添加/删除元素,抛出异常ConcurrentModificationException
        //list.remove(0);
        list.add(4);
    }
特有方法

除了迭代器的hasNext()next()remove()方法,还包含以下方法

使用列表迭代器解决并发修改问题

    //创建集合
    List list = new ArrayList();
    //添加元素
    list.add(1);list.add(2);list.add(3);
    //使用列表迭代器
    ListIterator iterator = list.listIterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
        //使用迭代器的添加方法进行添加元素
        iterator.add(4);
    }
    System.out.println(list);
    //[1, 4, 2, 4, 3, 4]

ArrayList类

  • ArrayList底层是由数组实现的,实现了动态增长,以满足应用程序的需求
  • 在列表尾部插入或删除数据非常高效,更适合查找和更新元素,不适合中间插入和删除
  • ArrayList中的元素可以为null

特殊方法:public void trimToSize()
因为remove()方法删除后集合实际内存空间不变,使用该方法将此 ArrayList 实例的容量调整为列表的当前大小。应用程序可以使用此操作来最小化 ArrayList 实例的存储量

使用示例
    public static void main(String[] args) {
        //创建对象
        List list = new ArrayList();
        //添加元素
        list.add("maven");
        list.add("redis");
        list.add("git");
        list.add("docker");
        list.add(3,"shiro");
        //集合元素的数量:5
        System.out.println(list.size());
        //获取第二个元素:redis
        System.out.println(list.get(1));
        //删除docker元素
        list.remove(4);
        //修改元素:将maven替换为gradle
        list.set(0, "gradle");
        //遍历ArrayList
        for (int i = 0; i < list.size(); i++) {
            //gradle redis git shiro 
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
        //查找git元素的位置:2
        System.out.println(list.indexOf("git"));
        //判断集合是否包含redis元素
        System.out.println(list.contains("redis")); //true
        //清空集合
        list.clear();
        System.out.println(list.isEmpty()); //true
    }

LinkedList类

  • LinkedList底层是由双向循环列表实现的,该类添加了一些处理列表两段元素的方法,使用这些方法可以轻松把LinkedList当作一个堆栈、队列或双端队列的数据结构
  • 查询效率慢,更适合快速的插入和删除元素
特殊方法

Set接口

  • Set是元素无序并且不可以重复的集合,被称为集。
  • Set就是一个不包含重复元素的Collection,没有引入新的方法。

HashSet类

  • HashSet称为散列表或者哈希集,底层实现实际上是HashMap
  • 允许存储一个null元素,具有良好的存取和查找性能
  • 默认负载因子是0.75,默认容量是16
  • 按照哈希算法计算出哈希码作为存取和比较的依据
HashSet判断元素唯一性
  1. HashSet存储对象时,先调用hashCode方法生成哈希码
  2. 如果生成的哈希码与已有的某个对象哈希码相同,则继续调用equals方法比较两个对象
  3. 如果equals方法也返回true,则认为是重复元素,不进行存储操作
  4. 如果哈希码不相等或者哈希码相等但是equals返回false,则根据哈希码在集合中找合适位置存储对象
关于哈希表结构

使用ArrayList存储元素如100个,如果希望查找某个元素,需要遍历整个容器,效率低。
假设我们在内存区域开辟三块空间来存储这100个元素,可以看成是三个桶,编号分别为0、1、2。什么数据存放在哪个桶里我们需要定义一个规则,这个规则其实就是hashCode,hashCode需要根据需求编写相应的算法
比如规则为n%3,则元素值为1存入1号桶,元素值为2存入2号桶,元素值为3存入0号桶...
然后查找元素时先判断数据在哪个桶里(hashCode),然后遍历桶里的元素(equals)即可。

重写hashCode方法

考虑到效率,添加到HashSet的对象需要采用恰当分配哈希码的方式实现HashCode()方法,一般的规则如下

  • 对象的属性如果是类似int类型的基本数据类型,直接返回该值本身就是hashCode值
  • 如果是引用数据类型,则调用该属性值的hashCode方法返回hashCode值
  • 将所有属性的hashCode值相加,就是该对象根据内容返回的hashCode值
private int age;
private String name;
@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}
Set的并发修改异常

List有提供解决并发修改异常的迭代器ListIterator,那么Set集合如何解决呢?
思路就是对于修改单个元素,修改后使用break;退出。修改多个元素,则暂存在新的Set集合中,然后调用removeAll或者addAll方法

    HashSet set = new HashSet<>();
    set.add("小明");
    set.add("小李");
    set.add("小红");
    set.add("苗苗");
    set.add("笑笑");
    //只删除一个元素
    for(String elem : set){
        if("小明".equals(elem)){
            set.remove(elem);
            break;
        }
    }
    //删除多个元素
    HashSet set1 = new HashSet<>();
    for(String elem : set){
        if(elem.indexOf("小") != -1){
            set1.add(elem);
        }
    }
    set.removeAll(set1);
    System.out.println(set.size());//2

Comparable接口

  • 实现该接口表示:这个类的实例可以比较大小,可以进行自然排序
  • 该接口定义了默认的比较规则
  • 其实现类需实现compareTo(T o)方法
  • 返回值:如果调用方法的对象小于指定对象,则返回负整数,大于则返回正整数,等于则返回0

Comparator接口

  • 比较工具接口,用于定义临时比较规则,而不是默认比较规则
  • 其实现类需要实现compare(T o1, T o2)方法,返回值同Comparable

TreeSet类

  • 基于TreeMap的NavigableSet实现,线程不安全的
  • TreeSet集合是用来对象元素进行排序的,同样他也可以保证元素的唯一
  • 添加到TreeSet的元素必须是可排序的,即具备比较能力,如果不具备,则运行时抛出ClassCastException异常
构造方法

public TreeSet(Comparator comparator) 传入指定的比较器进行排序
public TreeSet() 根据元素的自然顺序排序(必须实现Comparable接口)

排序说明
  • 通过Comparable让集合内元素具备比较性
  • 通过Comparator让集合具备比较某种类型元素的能力
  • 当Comparator与Comparable冲突时,优先使用Comparator
  • TreeSet判断元素唯一性的方法:就是元素大小顺序的比较方法,方法返回0时表示元素相等
  • TreeSet之所以可以存储字符串,Integer等类型,因为它们实现了Comparable接口
TreeSet存储自定义类型

1.自定义类实现Comparable接口,重写compareTo方法

public class Person implements Comparable {
    
    private String name;
    private Integer age;
    @Override
    public int compareTo(Person p) {
        // this: 新添加的元素,p: 集合中已经存在的老元素
        // 先比较年龄
        int result = this.age - p.age;
        // 如果年龄相同,再比较姓名
        return result == 0 ? this.name.compareTo(p.name) : result;
    }
    //测试
    public static void main(String[] args) {
        //使用自然排序规则
        TreeSet treeSet = new TreeSet<>();
        treeSet.add(new Person("小红",22));
        treeSet.add(new Person("小明",18));
        treeSet.add(new Person("张三",22));
        for(Person person : treeSet){
            //小明  小红  张三(多次遍历顺序是固定的)
            System.out.print(person.getName() + " ");
        }
    }
}

2.定义比较器类,实现Comparator接口,使用比较器对象创建集合(也可以直接使用匿名内部类)

    public static void main(String[] args) {
        //使用匿名内部类方式调用TreeSet带比较器对象的构造方法
        TreeSet treeSet = new TreeSet<>(new Comparator(){
            @Override
            public int compare(Person p1, Person p2) {
                // 先比较年龄
                int result = p1.getAge() - p2.getAge();
                // 如果年龄相同,再比较姓名
                return result == 0 ? p1.getName().compareTo(p2.getName()) : result;
            }}
        ); 
        treeSet.add(new Person("小红",22));
        treeSet.add(new Person("小明",18));
        treeSet.add(new Person("张三",22));
        for(Person person : treeSet){
            //小明  小红  张三(多次遍历顺序是固定的)
            System.out.print(person.getName() + " ");
        }
    }

Collections类

是java集合框架中,用来操作集合对象的工具类,常用方法如下

我们对之前实现排序规则的自定义类使用List存储并进行排序

    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add(new Person("小红",22));
        list.add(new Person("小明",18));
        list.add(new Person("张三",22));
        //排序
        Collections.sort(list);
        for(int i = 0; i < list.size(); i++){
            //小明  小红  张三
            System.out.print(list.get(i).getName() + " ");
        }
    }

List与数组间的转换

List集合转换为数组:调用List的toArray(T[] a)方法
数组转换为List集合:调用Arrays的asList(T... a)方法

    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add(4);
        list.add(2);
        list.add(1);
        list.add(3);
        //排序
        Collections.sort(list);
        //1.List集合转换为数组
        Integer[] array = new Integer[list.size()];
        list.toArray(array);
        //打印数组:[1, 2, 3, 4]
        System.out.println(Arrays.toString(array));
        //2.数组转换为List
        List transfList = Arrays.asList(array);
        //反转顺序
        Collections.reverse(transfList);
        //[4, 3, 2, 1]
        System.out.println(transfList);
    }

Map简介

  • 双列集合,Map中的数据是以键值对( key-value)的形式存储的
  • key-value以Entry类型的对象实例存在
  • 可以通过key值快速地查找value
  • 一个映射不能包含重复的键,每个键最多只能映射到一个值
常用方法
Entry接口:(Map.Entry)
  • 映射项(键-值对),是Map接口的一个内部接口,常用方法:
  • K getKey() 获取键
  • V getValue() 获取值
  • V setValue(V value) 修改值

HashMap类

  • 基于哈希表的Map接口的实现,对键进行无序唯一约束,是最常用的Map集合
  • 允许使用null值和null键,线程不安全
  • 适合插入、删除和定位元素
简单示例
    public static void main(String[] args) {
        //创建Map集合对象
        Map map = new HashMap<>();
        //向集合对象中添加元素
        map.put("white", "红色");
        map.put("white", "白色");
        map.put("blue", "蓝色");
        map.put("yellow", "黄色");
        map.put("black", "黑色");
        //根据key获取值
        String color = map.get("white");
        System.out.println(color); //白色
        //根据key删除元素
        map.remove("yellow");
        //判断key是否存在
        boolean yellowFlag = map.containsKey("yellow");
        System.out.println(yellowFlag); //false
        //遍历map元素
        for (Entry entry : map.entrySet()) {
            System.out.println("key:" + entry.getKey() + " ,value:" + entry.getValue());
            //key:white ,value:白色
            //key:blue ,value:蓝色
            //key:black ,value:黑色
        }
    }
对键的唯一约束

HashMap对键的唯一性判断与HashSet相同,先判断键的hashCode()方法,如果相同则继续调用equals方法。更确切的说HashSet判断元素唯一性其实引用的是HashMap的判断机制

TreeMap类

  • 能够对键进行排序的双列集合,要求存储的键必须是可比较的。
  • 其排序原理与TreeSet相同。更确切的说TreeSet的排序其实是通过TreeMap完成的
构造方法

public TreeMap(Comparator comparator) 传入指定的比较器进行排序
public TreeMap() 根据元素的自然顺序排序(必须实现Comparable接口)

示例

对于给定的字符串"aabcbdeeeeedbddcc",要求获取字符串中每一个字母出现的次数。
输出结果格式:a(2)b(3)c(3)d(4)e(5)

    public static void main(String[] args) {
        String str = "aabcbdeeeeedbddcc";
        //由于字母与次数存在一一对应关系,并且输出的字母是有序的,所以可以使用TreeMap
        TreeMap map = new TreeMap();
        //1,将字符串变成字符数组,因为要操作字符串中的每一个字母
        char[] chars = str.toCharArray();
        //2,遍历数组,将每一个字母都作为键去map集合中获取值
        for (char key : chars) {
            Integer value = map.get(key);
            // 3,如果map中没有该字母,就加入该字母1次
            if (value == null) {
                map.put(key, 1);
            } else {
                //4, 如果有该字母,就将之前的次数+1
                map.put(key, ++value);
            }
        }
        // 5,将集合中的数据变成字符串打印
        StringBuilder sb = new StringBuilder();
        for (Entry entry : map.entrySet()) {
            sb.append(entry.getKey()).append("(").append(entry.getValue()).append(")");
        }
        System.out.println(sb);
        //a(2)b(3)c(3)d(4)e(5)
    }

Map遍历的四种方式

  1. 通过entrySet方法遍历Entry对象,大多数情况下推荐使用这种方式
Map map = new HashMap<>();
for (Entry entry : map.entrySet()) {
    System.out.println("key:" + entry.getKey() + " ,value:" + entry.getValue());
}
  1. 在for-each循环中单独遍历keys或values
Map map = new HashMap<>();
//遍历键 
for (String key : map.keySet()) { 
  System.out.println("Key: " + key); 
} 
//遍历值 
for (String value : map.values()) { 
  System.out.println("Value: " + value); 
}
  1. 通过Iterator对Entry对象进行遍历
Map map = new HashMap<>();
Iterator> it = map.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry entry = it.next();
    System.out.println("key: " + entry.getKey() + " ,value: " + entry.getValue());
}
  1. 对键集合遍历,通过get方法获取值(耗时),效率低,不推荐使用
Map map = new HashMap<>();
for (String key : map.keySet()) {
    String value = map.get(key);
    System.out.println("Key: " + key + " ,Value: " + value);
}

你可能感兴趣的:(集合框架)