目录
1、Collection集合
1.1、什么是集合?
集合:集合是java中提供的一种容器,可以用来存储多个数据,并且可以存储任意类型的数据!
1.1.1、集合和数组的区别?
1、集合只能存储对象,并且对象的类型可以不一致,不能存储基本数据类型。(集合之所以可以存储基本类型数据的值是因为基本数据类型自动装箱为包装类)。
2、数组可以存储基本数据类型值并且是同一种数据类型。
3、数组的长度是固定的,集合的长度是可变的。
1.1.2、集合体系图
java集合体系可以分为两大部分:Collection和Map。本文章主要讲述Collection接口下的集合子类。Collection接口下常见的子接口有List、Set接口。List接口下有ArrayList、LinkedList、Vector等实现类,Set接口下有HashSet、LinkedHashSet、TreeSet等实现类,而Map接口下有HashMap、TreeMap、Hashtable等实现类。
2、List集合
2.1、什么是List集合?
java.util.List
接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List
接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
2.1.1、List集合的特点
1、它是一个元素存取有序的集合。
2、它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
3、集合中可以有重复的元素。
2.1.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) | 用指定元素替换集合中指定位置的元素,返回值的更新前的元素。 |
2.1.3、代码演示
public class ListDemo {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
System.out.println(list);
//删除指定索引元素或者删除指定元素
list.remove("world");
list.remove(0);
System.out.println(list);
//获取集合的元素个数
int size = list.size();
System.out.println(size);
//查看集合中是否包含指定元素
boolean hello = list.contains("hello");
System.out.println(hello);
//list集合获取指定索引的元素
String s = list.get(0);
System.out.println(s);
System.out.println("-------------------");
//list集合遍历
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String next = iterator.next();
System.out.println(next);
}
System.out.println("-------------------");
//普通for遍历
for (int i = 0; i <list.size() ; i++) {
String s1 = list.get(i);
System.out.println(s1);
}
System.out.println("-------------------");
//增强for
//iter
for (String s1 : list) {
System.out.println(s1);
}
}
}
2.2、ArrayList集合
2.2.1、ArrayList集合特点
底层数据结构是数组,查询快,增删慢 线程不安全,效率高。
2.2.2、ArrayList集合常用方法
方法名 | 说明 |
---|---|
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 boolean add(E e) | 将指定的元素添加到此列表的尾部。 |
2.2.3、代码演示
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> arrayList=new ArrayList<>();
//添加元素
arrayList.add("hello");
arrayList.add("world");
//在指定索引添加元素替换原有的元素
arrayList.add(0,"rose");
//指定索引,在此索引处进行设置值
arrayList.set(1,"java");
System.out.println(arrayList);
//获取指定索引的元素
String s = arrayList.get(1);
System.out.println(s);
//删除指定索引的元素
String remove = arrayList.remove(1);
System.out.println(remove);
System.out.println(arrayList);
}
}
2.3、LinkedList集合
2.3.1、LinkedList的特点
1、底层数据结构是链表,查询慢,增删快
2、线程不安全,效率高
2.3.2、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是List的子类,List中的方法LinkedList都是可以使用,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。
2.3.3、代码演示
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> linkedList=new LinkedList<>();
//添加元素
linkedList.add("hello");
linkedList.add("world");
linkedList.add("java");
//添加头部元素
linkedList.addFirst("head");
//添加尾部元素
linkedList.addLast("last");
System.out.println(linkedList);
System.out.println("-------------------");
//获取头部元素
String first = linkedList.getFirst();
System.out.println(first);
//获取尾部元素
String last = linkedList.getLast();
System.out.println(last);
System.out.println("-------------------");
//删除头部元素并返回该元素值
String s = linkedList.removeFirst();
//删除尾部元素并返回该元素值
String s1 = linkedList.removeLast();
System.out.println(s);
System.out.println(s1);
System.out.println(linkedList);
System.out.println("-------------------");
//循环集合 如果不为空就弹出一个元素
while(!linkedList.isEmpty()){
linkedList.pop();
}
System.out.println(linkedList);
}
}
2.4、Vector集合
2.4.1、Vector集合特点
1、底层数据结构是数组,查询快,增删慢。
2、线程安全,效率低。
2.4.2、代码演示
public class VectorDemo {
public static void main(String[] args) {
Vector<String> vector=new Vector<>();
vector.addElement("hello");
vector.addElement("world");
vector.addElement("java");
//集合遍历
for (int i = 0; i <vector.size() ; i++) {
String s = vector.elementAt(i);
System.out.println(s);
}
System.out.println("-------------------");
//vector集合迭代器遍历
Enumeration<String> elements = vector.elements();
while(elements.hasMoreElements()){
String s = elements.nextElement();
System.out.println(s);
}
System.out.println("--------------------");
//增强for遍历
for (String s : vector) {
System.out.println(s);
}
}
}
2.5、List集合子类特点总结
1、ArrayList:
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高。
2、LinkedList:
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高。
3、Vector:
底层数据结构是数组,查询快,增删慢。
线程安全,效率低。
4、List集合特点:有序可重复。
3、Set集合概述
java.util.Set
接口和java.util.List
接口一样,同样继承自Collection
接口,它与Collection
接口中的方法基本一致,并没有对Collection
接口进行功能上的扩充,只是比Collection
接口更加严格了。与List
接口不同的是,Set
接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set
集合有多个子类,这里主要介绍其中的java.util.HashSet
、java.util.LinkedHashSet
这两个集合。
3.1、HashSet集合
java.util.HashSet
是Set
接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。java.util.HashSet
底层的实现其实是一个java.util.HashMap
支持。HashSet
是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode
与equals
方法。
3.1.1、HashSet集合的特点
1、HashSet集合中的元素不可重复。
2、HashSet集合没有索引。
3、HashSet集合是无序的(存储元素的顺序与取出元素顺序可能不一致)。
总结:无序,唯一。
3.1.2、代码演示
public class HashSetDemo {
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);
}
}
}
3.1.3、如何保证HashSet集合唯一?
首先比较哈希值,先看hashCode()值是否相同,如果相同则继续执行equals()方法,若返回true,说明元素重复,就不添加,若返回false,说明元素不重复,就添加到集合,如果hashCode()值不同,就直接把元素添加到集合。而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。
注意:如果自定义类没有重写这两个方法,默认使用的Object类的,这时会出现HashSet里面可以存在相同数据。如Student类中没有重写hashCode()和equals()方法,在HashSet中添加学生类数据,这个时候,他们的哈希值是不会一样的,根本就不会继续判断,执行了添加操作。
3.1.4、HashSet集合存储数据的结构
JDK的版本不同,HashSet集合的数据结构有所不同:
1、JDK8之前:数组+链表。
2、JDK8之后:数组+链表+红黑树。
3.1.5、哈希表
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
3.2、LinkedHashSet集合
3.2.1、什么是LinkedHashSet?
HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么要保证有序,怎么办呢?在HashSet下面有一个子类
java.util.LinkedHashSet
,它是 链表 和 哈希表 组合的一个数据存储结构。
3.2.2、LinkedHashSet集合的特点?
1、LinkedHashSet集合中的元素不可重复。
2、LinkedHashSet集合没有索引。
3、LinkedHashSet集合是有序的(存储元素的顺序与取出元素顺序一致)。
总结: 有序,唯一
public class LinkedHashSetDemo {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<String>();
//添加元素
set.add("bbb");
set.add("aaa");
set.add("abc");
set.add("bbc");
Iterator<String> it = set.iterator();
//迭代器遍历
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
3.3、TreeSet集合
3.3.1、TreeSet自然排序
public class TreeSetDemo {
public static void main(String[] args) {
//使用元素的自然顺序对元素进行排序,唯一
TreeSet<Integer> ts = new TreeSet<>();
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(22);
ts.add(17);
ts.add(24);
ts.add(19);
ts.add(18);
ts.add(24);
for(Integer i : ts){
System.out.println(i);
}
System.out.println("=================");
TreeSet<String> ts2 = new TreeSet<>();
ts2.add("ab");
ts2.add("e");
ts2.add("r");
ts2.add("y");
ts2.add("c");
ts2.add("ac");
for(String s : ts2){
System.out.println(s);
}
}
}
3.3.2、内部比较器Comparable<T>
public class Demo {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
Student s1 = new Student("b",23);
Student s2 = new Student("b",23);
Student s3 = new Student("a",23);
Student s4 = new Student("jack",27);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
//增强for遍历TreeSet集合
for(Student s : ts){
System.out.println(s);
}
}
}
如果自定义一个类,定义了几个属性,通过TreeSet想去实现自定义类的属性内容排序,直接调用TreeSet的add()方法添加对象是会报错的,如下图,报错类型为ClassCastException,这是强制类型转换错误,因为Student类并没有实现Comparable接口,这个就是比较器接口,默认的比较器是不会比较对象的,需要去重写比较器接口里的方法,自定义比较功能。
为了解决上面的报错,我们需要在Student类中实现Comparable接口,然后重写Comparable里的比较方法,自定义比较功能。
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 s) {
//先比较姓名是否相同
int num = this.name.compareTo(s.name);
//如果姓名相同 则比较年龄 姓名如果不相同 直接返回num2值
int num2=num==0?this.age-s.age:num;
return num2;
}
}
通过Student类实现Comparable接口,重写ComparaTo方法,自定义比较功能后,再去执行程序,发现程序正常执行并输出排序好的结果,可以发现姓名a的ASCII码值是比姓名b的ASCII码值小的,储存到左子树,所以排到了最前面,接着jack会先和a进行比较,因为j是比a大的所以存储到右子树。
这种形式虽然可以解决TreeSet在添加对象时强制类型转换报错,但是Comparable<T> 内部比较器,需要修改原代码,不符合OCP(对修改关闭,对护展打开)原则。所以我们需要借助外部比较器Comparator,好处是不用修改原代码直接实现。
3.3.3、外部比较器Comparator
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
}
public class TreeSetDemo2 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Student> t = new TreeSet<>(new MyComparator());
Student s1 = new Student("i", 23);
Student s2 = new Student("a", 22);
Student s3 = new Student("a", 24);
Student s4 = new Student("h", 26);
t.add(s1);
t.add(s2);
t.add(s3);
t.add(s4);
//增强for遍历TreeSet集合
for (Student s : t) {
System.out.println(s);
}
}
}
3.3.4、TreeSet集合比较原理
因为TreeSet底层是二叉查找树,会将存入的元素按照从小到大(或字典序)自动排列。TreeSet实现的是中序遍历: 先遍历左子数,再输出父结点,再遍历右子树 左-根-右。