JAVA集合之List 和 Set

1. List概述

java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了 List 接口的对象称为List集合。

List接口的特点:

  • 它是一个元素存取有序的集合。
  • 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
  • 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

2. List接口常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操 作集合的特有方法。

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index) :返回集合中指定位置的元素。
  • public E remove(int index) :移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

代码示例:

public static void main(String[] args) {
    // 创建List集合对象
    List<String> list = new ArrayList<String>();
    // 往 尾部添加 指定元素
    list.add("张三");
    list.add("李四");
    list.add("王五");
    System.out.println(list);
    // add(int index,String s) 往指定位置添加
    list.add(1,"赵六");
    System.out.println(list);
    // String remove(int index) 删除指定位置元素 返回被删除元素
    // 删除索引位置为2的元素
    System.out.println("删除索引位置为2的元素");
    System.out.println(list.remove(2));
    System.out.println(list);
    // String set(int index,String s)
    // 在指定位置 进行 元素替代(改)
    // 修改指定位置元素
    list.set(0, "张三改");
    System.out.println(list);
    // String get(int index) 获取指定位置元素
    // 跟size() 方法一起用 来 遍历的
    for(int i = 0;i<list.size();i++){
    	System.out.println(list.get(i));
    }
    //还可以使用增强for
    for (String string : list) {
   	 	System.out.println(string);
    }
}

3. List接口的实现类

3.1 ArrayList

java.util.ArrayList 集合数据存储的结构是数组结构。

元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以 ArrayList 是最常用的集合。 许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

3.2 LinkedList

java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。

链表可分为单向链表和双向链表。

一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。

img

一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。

img

LinkedList就是是一个双向链表,实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。

  • public void addFirst(E e) :将指定元素插入此列表的开头。
  • public void addLast(E e) :将指定元素添加到此列表的结尾。
  • public E getFirst() :返回此列表的第一个元素。
  • public E getLast() :返回此列表的最后一个元素。
  • public E removeFirst() :移除并返回此列表的第一个元素。
  • public E removeLast() :移除并返回此列表的最后一个元素。
  • public E pop() :从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e) :将元素推入此列表所表示的堆栈。
  • public boolean isEmpty() :如果列表不包含元素,则返回true。

LinkedList实现的接口:

  • LinkedList 实现了 Queue 接口,可作为队列使用。

  • LinkedList 实现了 List 接口,可进行列表的相关操作。

  • LinkedList 实现了 Deque 接口,可作为队列使用。

  • LinkedList 实现了 Cloneable 接口,可实现克隆。

  • LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。

3.3 ArrayList 和 LinkedList 的选择

Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。

与 ArrayList 相比,LinkedList 的增加和删除对操作效率更高,而查找和修改的操作效率较低。

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

4. Set概述

java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方 法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。

List 接口的区别:

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

5. Set接口的实现类

5.1 HashSet

java.util.HashSetSet 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序 不一致)。 java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持。

HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCodeequals 方法。

我们看一下代码

public static void main(String[] args) {
    //创建 Set集合
    HashSet<String> set = new HashSet<String>();
    //添加元素
    set.add(new String("cba"));
    set.add("abc");
    set.add("bac");
    set.add("cba");
    //遍历
    for (String name : set) {
    	System.out.println(name);
    }
}

代码输出结果为:

cba
abc
bac

由结果可知,HashSet集合中不能存储重复元素且元素存取无序。

5.2 HashSet集合存储数据的结构(哈希表)

什么是哈希表呢?

在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。 但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。

而JDK1.8中,哈 希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找 时间。 简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

哈希表_副本

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一, 其实就是根据对象的hashCode和equals方法来决定的。

如果我们往集合中存放自定义的对象,那么为了保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

5.3 HashSet存储自定义类型元素

往HashSet集合存储String类元素时,由于String的不可变特性,我们无需重写String对象的hashCodeequals方法。

但当给HashSet中存放自定义类型元素时,需要重写对象中的hashCodeequals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。

创建自定义的Student类,并重写hashCodeequals方法:

public class 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 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);
    }
}

测试HashSet集合是否重复存储Student类:

public static void main(String[] args) {
    //创建集合对象 该集合中存储 Student类型对象
    HashSet<Student> stuSet = new HashSet<Student>();
    //存储
    Student stu = new Student("张三", 18);
    stuSet.add(stu);
    stuSet.add(new Student("张三", 18));
    stuSet.add(new Student("李四", 19));
    stuSet.add(new Student("王五", 20));
    stuSet.add(stu);
    for (Student stu2 : stuSet) {
    	System.out.println(stu2);
    }
}

代码输出结果为:

Student [name=张三, age=18]
Student [name=李四, age=19]
Student [name=王五, age=20]

由此可知,自定义对象重写hashCodeequals方法后,HashSet能判断自定义对象是否重复。

5.4 LinkedHashSet

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?

HashSet下面有一个子类 java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。

演示代码如下:

public static void main(String[] args) {
    Set<String> set = new LinkedHashSet<String>();
    set.add("a");
    set.add("d");
    set.add("c");
    set.add("b");
    Iterator<String> it = set.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

输出结果为:

a
d
c
b
posted @ 2020-11-04 15:30  渺渺孤烟起  阅读(303)  评论(0)    收藏  举报