Java笔记 -Collection集合

集合类体系结构

在这里插入图片描述

知识体系前览

在这里插入图片描述

特点

  • 集合只能存引用数据类型,不能存基本数据类型
  • 长度不固定

Collection接口(单例集合)

Collection定义了所有集合要有的功能(抽象方法)

泛型

  • 在使用集合时,在JDK5以前是没有泛型这么一说的,集合中可以存储任意类型的元素,但是,这并不是一件好事。因为在编写代码时,无法控制往集合中添加什么类型的元素。一旦有非法的数据进入集合,那么就会导致集合无法进行统一的操作
  • 使用泛型,就可以在编译时让编译器来检查类型是否一致

List接口

元素要有索引,可以重复,有序(存取顺序一致)

ArrayList类

特点

底层数据结构是数组,查询快,增删慢

ArrayList集合的底层原理
  1. 当我们创建一个 ArrayList 集合时,底层会创建一个长度为 0 的数组
  2. 当我们往 ArrayList 集合中添加第一个元素时,底层会将数组的长度扩容为10
  3. 当我们继续往 ArrayList 集合中添加更多元素时, 如果到了 10 这和容量, 底层会把这个数组扩容为原来的 1.5
  4. 然后把原先数组中的元素复制到新数组中,老数组被回收机制回收
  • 所以,可以不断地往 ArrayList 集合中添加元素,添加的元素越多,底层数组扩容就越大

LinkedList类

特点: 底层数据结构是链表,查询慢,增删快

Set接口

元素无索引,不可以重复,无序

特点

  • 没有索引
  • 不可重复
  • 存取顺序不一致

HashSet类

JDK8 之前使用 数组 + 链表 的形式,JDK8 以后使用 数组 + 链表 + 红黑树

特点
  • 底层数据结构是哈希表
  • 不能保证存储和取出的顺序完全一致
  • 没有带索引的方法,所以不能使用普通 for 遍历
  • 由于是 Set 集合,所以元素唯一
底层原理
  • JDK8 之前

    1. 创建 HashSet 集合,创建一个长度为 16 的数组,加载因子为 0.75 加载因子: (当集合中元素超过容量的 0.75 时进行扩容,扩容大小为原先的 1 倍)
    2. 添加元素时,底层会自动调用元素的 hashCode 方法,获取 Hash
    3. 接着根据 Hash 值和数组的长度做一个类似取余的操作,计算出一个索引位置
    4. 判断该索引位置的元素是否为 null,如果这个位置为 null,说明这个位置没有元素,此时直接把元素添加到这个索引为值;如果这个位置的元素不为 null,说明这个位置已经有元素存在,此时就拿着新添加的元素,和这个位置已经存在的每一个元素使用 equals 进行比较,如果 equals 返回 false,就说明新添加的元素和已有的元素不相同,此时以链表的形式连接在一起,如果某一次返回true,说明该位置已经有该元素,不存
  • JDK8 之后

    • 大体和 JDK8 之前相同,区别是链表长度超过 8 时会转换为红黑树
Hash值
  • JDK 根据对象的地址值或者属性值,算出来的 int 类型整数
  • Object 类有 hashCode() 方法可以根据对象的地址值获取 Hash
  • Hash
    • 如果一个类没有重写 hashCode() 方法,那么该对象的 Hash 值与地址有关,此时每个对象的 Hash 值都不同
    • 如果一个类重写了 hashCode() 方法, 那么如果两个对象的属性值相同,则 Hash 值相同

TreeSet类

特点
  • 不包括重复元素的集合
  • 没有带索引的方法
  • 可以将元素按照规则进行排序, 如果是 API 中提供的元素类型,他们有默认的排序规则, 如果存储的是自定义元素类型,没有排序规则是不能排序的
Compareable自然排序
  • 使用空参构造创建 TreeSet 集合
  • 自定义的类实现 Compareable 接口
  • 重写里面的 compareTo 方法
方法1
public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return this.name.compareTo(o.name);
    }
}

public class Test {
    public static void main(String[] args) {
        Set<Student> ts = new TreeSet<>();
        ts.add(new Student("abc", 12));
        ts.add(new Student("abd", 10));
        ts.add(new Student("abb", 14));
        ts.add(new Student("abe", 10));
        ts.add(new Student("abe", 9));

        System.out.println(ts);
    }
}
方法2
public class Test {
    public static void main(String[] args) {
        Set<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int res = o1.getAge() - o2.getAge();
                if (res == 0) {
                    return o1.getName().compareTo(o2.getName());
                }
                return res;
            }
        });

        ts.add(new Student("abc", 12));
        ts.add(new Student("aaa", 12));
        ts.add(new Student("abb", 13));
        ts.add(new Student("abd", 11));
        System.out.println(ts);
    }
}

如果重写 compareTo比较器 同时存在,使用 比较器 来比较,所以使用 比较器 的方式比较好

Map接口(双列集合)

概述

  • Interface Map<K, V> (是一个接口) K: 键的数据类型 V: 值的数据类型
  • 键不能重复,值可以重复
  • 键和值是一 一对应的,每一个键只能找到自己对应的值
  • (键 + 值)这个整体我们称之为 键值对 或者 键值对对象,在 Java 中叫做 Entry对象
  • Map 集合是用来存储键值对的集合

特点

  • 需要明确键和值的数据类型,以及键和值是用来表示什么的
  • 如果键重复,用新的值替换旧的值

基本方法

  • put: 如果重复,用新的值替换旧的值,返回被替换的值
  • remove: 返回被删除的值
  • get: 获取值
  • clear: 清空 Map
  • isEmpty: 判断是否为空
  • containsKey: 判断键是否存在
  • containsValue: 判断值是否存在
  • size: 获取 Map 的大小

遍历方法

方法1: keySet()
Map<String, Integer> map = new HashMap<>();

map.put("Test1", 1);
map.put("Test2", 2);
map.put("Test3", 3);

for (String s : map.keySet()) {
    System.out.println("key: " + s + " -- value: " + map.get(s));
}
方法2: entrySet()
Map<String, Integer> map = new HashMap<>();

map.put("Test1", 1);
map.put("Test2", 2);
map.put("Test3", 3);

Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
    System.out.println("key: " + entry.getKey() + " -- value: " + entry.getValue());
}
方法3: forEach()
Map<String, Integer> map = new HashMap<>();

map.put("Test1", 1);
map.put("Test2", 2);
map.put("Test3", 3);

map.forEach((String s, Integer i) -> {
    System.out.println(s + "..." + i);
});

如果想在遍历过程中结束循环

  • forEach 不可中途结束,反例:
HashMap<String, Integer> hashMap = new HashMap<>();
{
    hashMap.put("Test1", 1);
    hashMap.put("Test2", 2);
    hashMap.put("Test3", 3);
    hashMap.put("Test4", 4);
    hashMap.put("Test5", 5);
    hashMap.put("Test6", 6);
    hashMap.put("Test7", 7);
    hashMap.put("Test8", 8);
    hashMap.put("Test9", 9);
    hashMap.put("Test10", 10);
    hashMap.put("Test11", 11);
    hashMap.put("Test12", 12);
    hashMap.put("Test13", 13);
    hashMap.put("Test14", 14);
}

hashMap.forEach((key, value) -> {
    if (java.util.Objects.equals("Test1", key)) {
        return;
    }
    System.out.println(key + "..." + hashMap.get(value));
});

这样子并不能结束循环,它结束的实际上是 Lambda 内部的函数,完整程序如下:

hashMap.forEach(new BiConsumer<String, Integer>() {
    @Override
    public void accept(String s, Integer integer) {
        if (java.util.Objects.equals("Test1", s)) {
            return;
        }
        System.out.println(s + "..." + integer);
    }
});
  • 增强 for 方法:
for (String s : hashMap.keySet()) {
    if (java.util.Objects.equals("Test1", s)) {
        break;
    }
    System.out.println(s + "..." + hashMap.get(s));
}

这样子是可以结束的

HashMap

本质上和 HashSet 是一个东西,HashSet 本质上也是想链表的节点属性值中添加东西,JDK8 之前使用 数组 + 链表JDK8 之后使用 数组 + 链表 + 红黑树

TreeMap

  • 排序只关心键,不关心值
  • 实际上 TreeMapTreeSet 一样, 都是 红黑树 结构,当往 TreeSet 集合中添加数据时,本质上是往 TreeMap 的键位置添加一个数据

HashMap和HashTable的区别

  • HashMapHashTable 使用起来一模一样,HashTable 是线程安全的,HashMap 线程不安全
  • 类似的还有 StringBuilderStringBuffer 也是使用起来一模一样,StrigBuffer 是线程安全的,StringBuilder 线程不安全

MultiValueMap

value是一个集合,put相同的key值时,原value不会被覆盖,会添加到集合中

遍历

迭代器遍历集合

  • Iterarot<E> iterator = ...: 集合中的(Collection Set 通用)迭代器对象,该迭代器对象默认指向当前集合的 0 索引

常用方法

  • hasNext(): 当前位置是否有元素可以取出
  • next(): 取出当前位置元素并且迭代器往后移动一个位置

Set<String> hs = new HashSet<>();

hs.add("aaa");
hs.add("bbb");
hs.add("ccc");
Iterator<String> it = hs.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

增强for

  • 格式: for (元素数据类型 变量名 : 数组或者Collection集合)
  • forEach 方法底层调用了 增强for, 本质上也是 迭代器

注意事项

  • 数据类型一定是集合或者数组中元素的类型
  • 每一次循环时的变量名仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素

创建不可变集合

JDK9 中对 List、Set、Map 提供了可以快速创建集合,并添加元素的方法,但是这里创建出来的集合不可变(只能查询、不能添加、不能删除、不能修改)

特点

  • .of() 创建的集合是不可变的
  • Set.of() 创建的集合不可变,所以不能重复
  • Map.ofEntries: 先把数据变为 entry 对象,再放到 Map
    例:
List<String> testList = List.of("test1", "test2", "test3", "test4");
System.out.println(testList);

Set<String> testSet = Set.of("test1", "test2", "test3");
System.out.println(testSet);

Map.ofEntries(Map.entry("test1", 1), Map.entry("test2", 2)).forEach((key, value) -> {
    System.out.println(key + "..." + value);
});

Map<String, Integer> map = Map.of("test1", 1, "test2", 2);
map.entrySet().stream().forEach(e -> {
    System.out.println(e.getKey() + "..." + e.getValue());
});

  • 可以使用 Collections.addAll() 向集合中批量添加元素
    例:
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "test1", "test2", "test3");
System.out.println(list);

HashSet<String> set = new HashSet<>();
Collections.addAll(set,  "test1", "test2", "test3");
System.out.println(set);
tln(e.getKey() + "..." + e.getValue());
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值