Java集合之List与Set学习总结

文章目录

  • 1、List
    • 1.1、List接口常用方法
    • 1.2、List接口的实现类
      • 1.2.1、ArrayList集合
      • 1.2.2、LinkedList集合
      • 1.2.3、Vector集合
  • 2、Set
    • 2.1、HashSet
      • 2.2.1、哈希值
      • 2.2.2、HashSet集合存储数据的结构(哈希表)
      • 2.2.3、HashSet存储自定义类型元素
    • 2.2、LinkedHashSet

1、List

  java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了 List 接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。

List接口特点:
(1)元素存取有序,即存储元素和取出元素的顺序是一致的。
(2)带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
(3)允许存储重复的元素,通过元素的equals方法,来比较是否为重复的元素。

1.1、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) 用指定元素替换集合中指定位置的元素,返回值的更新前的元素

Tips:操作索引时,一定要防止索引越界异常。

import java.util.ArrayList;
import java.util.List;

public class ListDemo {
    public static void main(String[] args) {
        // 创建List集合对象
        List<String> list = new ArrayList<>();
        // 添加元素
        list.add("Atlantis");
        list.add("Olivia");
        list.add("长安");
        System.out.println(list);

        // add(int index,String s):往指定位置添加元素
        list.add(1, "Andersen");
        System.out.println(list);

        // String remove(int index):删除指定位置元素,返回被删除元素
        // 删除索引为3的元素
        String remove = list.remove(3);
        System.out.println("删除索引为3的元素:" + remove);
        System.out.println(list);

        // String set(int index,String s):在指定位置进行元素替换
        list.set(1, "安徒生");
        System.out.println(list);

        // String get(int index):获取指定位置元素
        // 常和size()方法以前用来遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }

        // 使用增强for循环遍历
        /*for (String str : list) {
            System.out.println(str);
        }*/
        
        // 使用迭代器遍历
        /*Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            System.out.println(str);
        }*/
    }
}
结果:
[Atlantis, Olivia, 长安]
[Atlantis, Andersen, Olivia, 长安]
删除索引为3的元素:长安
[Atlantis, Andersen, Olivia]
[Atlantis, 安徒生, Olivia]
Atlantis 安徒生 Olivia 

1.2、List接口的实现类

1.2.1、ArrayList集合

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

1.2.2、LinkedList集合

  java.util.LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。LinkedList是一个双向链表,那么双向链表是什么样子的呢,我们用个图了解下:
Java集合之List与Set学习总结_第1张图片

Tips:当使用LinkedList集合特有的方法时,不能使用多态。例如使用LinkedList中pop()或push()。

  实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而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() 从此列表所表示的堆栈处弹出一个元素,等效于removeFirst()方法
public void push(E e) 将元素推入此列表所表示的堆栈,等效于addFirst(E e)方法
public boolean isEmpty() 如果列表不包含元素,则返回true

  LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。

1.2.3、Vector集合

  Vector单列集合是 JDK1.0 版本开始就使用的,它可以实现可增长的对象数数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector的大小可以根据需要增大或缩小,以适应创建Vector后进行添加或移除项的操作。
  Vector与ArrayList和LinkedList不同之处在于,Vector是同步的,即单线程,意味着处理速度慢。自 JDK1.2 后,ArrayList就渐渐取代了Vector。因此大家了解该集合即可,具体使用方法可以查看API学习。

2、Set

  java.util.Set接口和java.util.List接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。与 List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

Set接口的特点:
  (1)不允许存储重复的元素。
  (2)没有索引,即没有带索引的方法,所以不能使用传统for循环遍历。

  Set 集合有多个子类,下面介绍其中的 java.util.HashSetjava.util.LinkedHashSet这两个集合。

Tips:Set集合取出元素的方式可以采用:迭代器、增强for。

2.1、HashSet

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

Tips:哈希表结构查询的速度非常快。

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

  我们先来使用一下Set集合存储,看下现象:

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo {
    public static void main(String[] args) {
        // 创建Set集合
        HashSet<String> set = new HashSet<>();
        // 添加元素
        set.add("abc");
        set.add("bcd");
        set.add("cde");
        // 使用迭代器遍历
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            System.out.println(str);
        }
        // 增加for循环遍历
        /*for (String str : set) {
            System.out.println(str);
        }*/
    }
}
结果:
bcd
abc
cde

  观察输出结果,说明HashSet集合不能存储重复的元素,且顺序无序。

2.2.1、哈希值

  哈希值,就是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,不是数据实际存储的物理地址)。而在Object类中有一个方法hashCode()可以获取对象的哈希值。

/* hashCode()方法的源码 */
// native:表示该方法调用的是本地操作系统的方法
public native int hashCode();

/* toString()方法的源码 */
public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

  下面是该方法的使用演示:

public class HashDemo {
    public static void main(String[] args) {
        // User继承Object类,所以可以使用Object类中的hashCode方法
        User user1 = new User();
        int hashCode1 = user1.hashCode();
        System.out.println("user1:" + user1);
        System.out.println("user1HashCode:" + hashCode1);

        User user2 = new User();
        int hashCode2 = user2.hashCode();
        System.out.println("user2:" + user2);
        System.out.println("user2HashCode:" + hashCode2);
    }
}

Java集合之List与Set学习总结_第2张图片
  这里要注意的是,哈希值仅仅是一个逻辑地址,而不是物理地址。意思说,如果创建了的两个对象,哪怕它们的hashCode相等,也不代表它们是同一个对象。请看下面演示:

  接下来,我们来了解一下特殊的哈希值:

        /* String类的哈希值 */
        String str1 = new String("Atlantis");
        String str2 = new String("Atlantis");
        System.out.println("str1HashCode:" + str1.hashCode());
        System.out.println("str2HashCode:" + str2.hashCode());

        System.out.println("重地HashCode:" + "重地".hashCode());
        System.out.println("通话HashCode:" + "通话".hashCode());

结果:
str1HashCode:374688376
str2HashCode:374688376
重地HashCode:1179395
通话HashCode:1179395

  可以发现,str1和str2字符串因为是一样的,hashCode一致不奇怪。但是下面“重地”和“通话”两个字符串明显不一样,但它们的hashCode值一致。是不是很神奇,这同样说明了哈希值只是一个逻辑值(地址),而不是真正意义上的物理地址。

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

什么是哈希表呢?
  在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8之后,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

  哈希表就是在数组的结构上,把元素进行了分组(相同哈希值的元素为一组),而链表或红黑树则把相同哈希值的元素连接到一起。

  简单的来说,哈希表就是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
Java集合之List与Set学习总结_第3张图片
  看到这张图就有人要问了,这个是怎么存储的呢?为了方便大家的理解我们结合一个存储流程图来说明一下:
Java集合之List与Set学习总结_第4张图片
Java集合之List与Set学习总结_第5张图片
  总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

2.2.3、HashSet存储自定义类型元素

  给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。比如下面的案例:同名同年龄视为同一个人。

import java.util.Objects;

public class User {
    private String name;
    private int age;

    // 省略无参和有参构造方法
    // 省略get、set和toString方法

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

    // 重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
import com.atlantis.domain.User;
import java.util.HashSet;

public class HashSetDemo {
    public static void main(String[] args) {
        // 创建Set集合,存储User类型对象
        HashSet<User> set = new HashSet<>();
        // 添加元素
        set.add(new User("Atlantis",23));
        set.add(new User("Atlantis",23));
        set.add(new User("Atlantis",22));
        set.add(new User("Olivia",23));
        set.add(new User("Olivia",22));

        for (User user : set) {
            System.out.println(user);
        }
    }
}
结果:
User{name='Olivia', age=23}
User{name='Olivia', age=22}
User{name='Atlantis', age=23}
User{name='Atlantis', age=22}

2.2、LinkedHashSet

  我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?在HashSet下面有一个子类java.util.LinkedHashSet,底层是链表和哈希表组合的一个数据存储结构。

  官方说明:LinkedHashSet具有可预知迭代顺序的Set接口的哈希表和链表实现。此实现与HashSet的不同之处在于,后者维护着一个运行所有条目的双重链表。此链表定义了迭代顺序,即按照将元素插入到Set中的顺序进行迭代。注意,插入顺序不受在Set中重新插入的元素的影响。

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

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

你可能感兴趣的:(JavaSE,List,Set,Collection,Java,Java集合)