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
集合数据存储的结构是链表结构。方便元素添加、删除的集合。
链表可分为单向链表和双向链表。
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。
一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。
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.HashSet
是 Set
接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序 不一致)。 java.util.HashSet
底层的实现其实是一个 java.util.HashMap
支持。
HashSet
是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode
与 equals
方法。
我们看一下代码
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
对象的hashCode
和equals
方法。
但当给HashSet
中存放自定义类型元素时,需要重写对象中的hashCode
和equals
方法,建立自己的比较方式,才能保证HashSet
集合中的对象唯一。
创建自定义的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 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]
由此可知,自定义对象重写hashCode
和equals
方法后,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