Loading

集合框架的一个小总结

简介

概述

集合就像一种容器,可以把多个对象放进容器内

特点:提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变

集合按照其存储结构可以分为两大类:

  • 单列集合 Collection
  • 双列集合 Map

集合体系结构

  • Collection 单列
    • List 可重复、有序(存储顺序)
      • ArrayList(数组)
      • LinkedList(双向链表)
    • Set 不可重复、无序
      • HashSet(哈希表)
        • LinkedHashSet(哈希表、链表) 有序(存储顺序)、不可重复
      • TreeSet(有序:大小顺序、不可重复)
  • Map 双列
    • HashMap
    • TreeMap

集合与数组的区别

  • 数组的长度是固定的,集合的长度是可变的

  • 数组长度在数组初始化时就已经确定,只能保存定长的数据;
    而集合可以保存数量不确定的数据,同时可以保存具有映射关系的数据(key - value)

  • 数组元素既可以是基本类型的值,也可以是对象;
    而集合只能保存对象(保存对象的引用变量),基本数据类型变量需要转换为对应的包装类才能放入集合类中

Collection集合

概述

Collection:单列集合类的 根接口,用于存储一系列符合某种规则的元素

两个子接口:

  • List:有序、可重复
  • Set:无序、不可重复

常用方法

方法名 说明
boolean add(E e) 添加元素
boolean remove(Object o) 移除指定元素
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含给定的对象
boolean isEmpty() 判断集合是否为空
int size() 返回集合元素个数
Collection<String> collection = new ArrayList<>();

collection.add("蓝天");
collection.add("白云");

System.out.println(collection);

System.out.println(collection.contains("蓝天"));

int len = collection.size();

// 集合的遍历
Object[] objects = collection.toArray();

for (int i = 0; i < objects.length; i++) {
    System.out.println(objects[i]);
}

for (String s : collection) {
    System.out.println(s);
}

集合的遍历

Iterator:迭代器,集合的专用遍历方式,是一个接口,它是Collection接口的父接口

迭代器是通过集合的 iterator() 方法得到的,所以说它是依赖于集合而存在的

常用方法:

方法 说明
E next() 返回迭代的下一个元素
boolean hasNext() 如果仍有元素可以迭代,则返回true
//创建集合
Collection<Student> students = new ArrayList<Student>();
students.add(new Student("张三", 23));

// 获得集合的迭代器
Iterator<Student> iterator = students.iterator();

// 遍历
while (iterator.hasNext()) {
    Student s = iterator.next();
    System.out.println(s.toString());
}

使用迭代器进行元素的迭代,集合元素的值传递给了迭代变量,,仅仅传递了对象引用,保存的是指向对象内存空间的地址

实现原理:在遍历集合时,首先调用集合的 iterator() 方法获得迭代器对象,然后使用 hashNext() 方法判断集合中是否存在下一个元素,如果存在,则调用 next() 方法将元素取出,否则说明已到达了集合末尾,停止遍历元素

并发修改问题

使用Iterator迭代器遍历元素的时候,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致,如果使用add方法添加元素,会造成并发修改异常

解决:使用for、get方法遍历

List集合

概述

List接口继承Collection接口,是单列集合的一个重要分支

特点:

  • 有序:存储和取出顺序一致

  • 可重复

  • 所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素

List接口的实现类:

  • ArrayList
  • LinkedList

特有方法

方法 说明
void add(int index, E element) 添加指定元素到指定索引位置
E remove(int index) 删除指定索引处元素,并返回被删除的值
E set(int index, E element) 修改指定索引处元素,并返回被修改的值
E get(int index) 返回指定索引处元素
List<String> list = new ArrayList<>();

list.add("蓝");
list.add("蓝天");
list.add("蓝天空");
System.out.println(list);

System.out.println(list.remove(1));

list.set(0, "宇");

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

for (String s : list) {
    System.out.println(s);
}

ListIterator

ListIterator:列表迭代器

通过List集合的 listIterator() 方法得到

作用:运行沿任一方向遍历列表的列表迭代器

常用方法

方法 说明
E next() 返回下一个元素
boolean hasNext()
E previous() 返回上一个元素
boolean hasPrevious()
void add(E e) 插入

ArrayList

底层数据结构:数组

特点:查询快、增删慢

LinkedList

底层数据结构:链表

特点:查询慢、增删快

缺点:随机访问效率较低

LinkedList是一个双向链表,且LinkedList提供了大量首尾操作的方法:

方法 说明
void addFirst(E e) 在列表开头插入指定元素
void addLast(E e) 在列表末尾插入指定元素
E getFirst() 返回列表第一个元素
E getLast() 返回列表最后一个元素
E removeFirst() 删除并返回第一个元素
E removeLast() 删除并返回最后一个元素
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("1");
linkedList.add("2");
linkedList.add("3");

System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());


System.out.println(linkedList.removeFirst());
System.out.println(linkedList.removeLast());

while (!linkedList.isEmpty()) {
    System.out.println(linkedList.pop());
}

System.out.println(linkedList);

Set集合

概述

特点:

  • 不包含重复元素
  • 元素无序
  • 没有带索引的方法,所以不能使用普通for遍历

Set集合的实现类:

  • HashSet
  • TreeSet

遍历

Set<String> set = new HashSet<String>();
set.add("1");
set.add("2");

// 方式一
for (String s : set) {
    System.out.println(s);
}

// 方式二:迭代遍历
Set<String> set = new HashSet<String>();
Iterator<String> it = set.iterator();  
while (it.hasNext()) {  
    String str = it.next();  
    System.out.println(str);  
}

HashSet

底层数据结构:哈希表(实际上是一个HashMap实例)

特点:

  • 无序
  • 不可重复
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能
HashSet<String> strings = new HashSet<>();
strings.add("123");
strings.add("1234");
strings.add("12345");

for (String name : strings) {
    System.out.println(name);
}

元素唯一分析

哈希表,在JDK8之前底层采用 数组+链表 实现

要保证元素唯一性,需要重写hashCode()和equals()方法

HashSet集合添加一个元素的过程:

  • 调用对象的hashCode()方法获取对象的哈希值
  • 根据对象的哈希值计算对象的存储位置
  • 判断该位置是否有元素存在
    • 有:遍历该位置的所有元素,和新存入的元素比较哈希值是否相同
      • 有相同的:调用equals()方法比较对象内容是否相等
        • 相同:说明元素重复,不存储
        • 不同:将元素存储到该位置
      • 都不相同:将元素存储到该位置
    • 没有:将元素存储到该位置

存储自定义对象

  • 自定义Student类(需要重写hashCode、equals方法)
public class Student {
    private String name;
    private int age;
    public Student() {
    }

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

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

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

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
            
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
   
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class HashSetTest {
    public static void main(String[] args) {
        
        HashSet<Student> students = new HashSet<>();
        students.add(new Student("蓝天", 100));
        students.add(new Student("星月", 1000));
        students.add(new Student("镇天", 10000));

        for (Student student : students) {
            System.out.println(student);
        }
        
    }
}

LinkedHashSet

特点:

  • 底层:哈希表和链表
  • 有序
  • 元素不重复

TreeSet

特点:

  • 元素有序(排序),排序方式取决于构造方法
  • 无带索引的方法,不能使用普通for遍历
  • 不可重复
TreeSet() // 自然排序(从小到大)
TreeSet<Integer> t = new TreeSet<>();
    
TreeSet(Comparator comparator); // 根据指定的比较器进行排序

如果要对对象进行排序,对象需要实现Comparable接口,例如(使用自然排序方式)

public class Student implements Comparable<Student> {
    private String name;
    private int age;
    
    @Override
    public int compareTo(Student s) {
        //return 0;   说明元素重复
        //return 1;   按照存储顺序
        //return -1;  按照倒序
        
        // 按照年龄从小到大排序,年龄相同时,按照姓名
        int num = this.age - s.age;
        // 从大到小
        // int num = s.age - this.age;
        int t = num == 0 ? this.name.compareTo(s.name) : num;
        return t;
    }
}

除了使用自然排序方式实现排序,还可以使用比较器排序Comparator

这里需要有带参构造方法

TreeSet<Student> t = new TreeSet<Student>(new Comparator<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
        int num = s1.getAge() - s2.getAge();
        int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
        return num2;
    }
})

新增方法

方法 功能描述
first() 返回此Set中当前第一个(最低)元素
last() 返回此Set中当前最后一个(最高)元素
comparator() 返回比较器,如果使用的是自然顺序,返回null
headSet(E toElement) 返回一个新的Set集合,包含toElement之前所有对象
subSet(E fromElement, E fromElement) 返回[)之间的对象
tailSet(E fromElement) 返回fromElement(包含)之后的所有对象

泛型

泛型自JDK5引入

本质:参数化类型

参数化类型:将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型

这种参数类型可以用正在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口

好处:

  • 把运行时期的问题提前到了编译期间
  • 避免了强制类型转换

泛型类

格式:

修饰符 class 类名<类型> { }
public class Generic<T> {

    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

使用

Generic<String> g = new Generic<String>();

泛型方法

使用泛型类改进

 public class Generic<T> {
    public void show(T t) {
        System.out.println(t);
    }
}

泛型方法改进

public class Generic {
    public <T> void show(T t) {
        System.out.println(t);
    }
}

使用

Generic g = new Generic();
g.show("ni");
g.show(1);

泛型接口

定义格式

修饰符 interface 接口名<类型> { }

类型通配符

为了表示各种泛型List的父类,可以使用类型通配符 <?>

List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型

类型通配符上限:<? extends 类型>

例如:List<? extends Number>,表示的类型是Number或者其子类型

类型通配符下限:<? super 类型>

例如:List<? super Number>,表示的类型是Number或者其父类型

Map集合

概述

Map集合没有继承Collection接口,其提供的是key到value的映射

特点:

  • 不能包含重复的键(对象重写equals和hashCode方法)
  • 每个键可以映射最多一个值

Map接口的实现类:

  • HashMap
  • TreeMap

由HashMap类实现的Map集合添加和删除映射关系效率更高

常用方法

方法 说明
V put(K kye, V value) 添加元素
V get(Object key) 获取指定键的键值
V remove(Object key) 根据键删除键值对象
void clear() 移除所有键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合长度
Set keySet() 获取所有键的集合
Collection values() 获取所有值的集合
Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合

Map集合的遍历

方式一:

  • 获取所有 键的集合
  • 遍历键的集合,获取键值
  • 根据键找键值
Set<String> keySet = map.keySet();
for (String key : keySet) {
    String value = map.get(key);
    ...
}

方式二:

  • 获取所有 键值对对象的集合
  • 遍历键值对对象的集合,得到每一个键值对对象
  • 根据键值对对象获取键和值
Map<String, String> map = new HashMap<String, String>();

Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> me : entrySet) {
    String key = me.getKey();
    String value = me.getValue();
}

HashMap

底层:哈希表的Map接口,此实现提供所有可选的映射操作,并允许使用null值和null键,但必须保证键的唯一性

HashMap通过哈希码对其内部的映射关系进行快速查找

常用子类:

  • HashMap<K,V>
    • 存储数据采用的哈希表结构
    • 无序
    • 由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法
  • LinkedHashMap<K,V>
    • HashMap的子类
    • 存储数据采用的哈希表结构+链表结构
    • 有序
    • 通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法

Map接口中的集合都有两个泛型变量<K,V>

在使用时,要为两个泛型变量赋予数据类型

两个泛型变量<K,V>的数据类型可以相同,也可以不同

Map集合不能直接使用迭代器或者foreach进行遍历,但是转成Set之后就可以使用了

Collections

Collections类是针对集合操作的工具类

常用方法:

  • public static <T extends Comparable<? super T>> void sort(List list):将指定列表升序排序
  • public static void reverse(List<?> list):反转列表元素顺序
  • public static void shuffle(List<?> list):使用默认的随机源随机排序指定的列表
public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(30);
    list.add(20);
    list.add(50);
    list.add(40);
    list.add(10);

    System.out.println(list);
    // 升序排序
    Collections.sort(list);
    System.out.println(list);

    // 反转
    Collections.reverse(list);
    System.out.println(list);

    // 洗牌
    Collections.shuffle(list);
    System.out.println(list);
}

如果要对对象进行排序,需要添加比较器排序Comparator

Collections.sort(array, new Comparator<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
        int num = s1.getAge() - s2.getAge();
        int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
        return num2;
    }
})

见LinkedHashSet

posted @ 2021-04-04 15:55  qinuna  阅读(87)  评论(0编辑  收藏  举报