数据结构、List、Set、Collections
一、数据结构:
数据结构 : 数据用什么样的方式组合在一起。
Java中因为业务的需要,可能存在数据的不同组合方法,不同的方式操作这些数据的性能是不一样的。
程序应该关注数据结构,以便选择合适的数据结构来解决业务问题。
Java中常见的数据结构有哪些?
数据存储的常用结构有:栈、队列、线性表、链表和红黑树。我们分别来了解一下:
1.栈
栈的特点:数据是先进后出,后进先出。
栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在栈的一端进行插入和删除操作,不允许在其
他任何位置进行添加、查找、删除等操作。
栈的应用场景:手枪的弹夹。
2.队列
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,
而在表的另一端进行删除。
队列的特点:先进先出 ,后进后出。
队列的场景: 各种排队。
3.线性表(数组)
是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出
租屋,有100个房间,从000到99每个房间都有固定编号,通过编号就可以快速找到租房子的人。
线性表的特点:连续的内存区域,每个区域的大小是固定的。查询快,增删慢!
查询快的原因:是可以通过起始位置和索引快速的定位出目标索引元素的位置。、
增删慢:创建新的数组,然后要对元素进行迁移。
4.链表
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每
个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的
链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。
链表的特点:元素是游离在内存中存储的,不是连续的区域。
查询慢:从链表的第一个元素开始一个一个的去访问看是否是自己要找的元素。
增删快:无需创建新的结构,只需要把被删除元素的前后元素衔接即可!
5.红黑树(二叉树)
二叉树:binary tree ,是每个结点不超过2的有序树(tree)
二叉树的特点:只有一个根节点,每个节点最多只能有2个子节点。
我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然
是一颗二叉查找树。
红黑树可以用来做数据排序:
大的数据往结点的右边放置,小的数据往节点的左边放置。
红黑树可以提高数据查询的效率。
总结:
后面的不同集合底层是基于不同的数据结构设计的。导致不同集合的数据操作性能是不一样的。
二、Collection集合的体系:
1.List系列集合:
学习List集合第一个实现类:ArrayList集合。
- 特点:元素是有序,有索引,可重复的。基于数组实现的。查询快,增删慢!
- List系列集合因为都是继承自Collection所以之前Collection的功能全部可以使用。
- List集合存在自己的独有功能
- 因为List系列集合存在索引,所以他有很多与索引相关的功能
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo01 {
public static void main(String[] args) {
// 实现类对象赋值给了接口类型。
List<String> lists = new ArrayList<>();
lists.add("Java");
lists.add("Java");
lists.add("Oracle");
lists.add("Mysql");
lists.add("Mybatis");
System.out.println(lists);
}
}
- List集合的常用的独有功能API:
- public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。
public E get(int index) :返回集合中指定位置的元素。
public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo02 {
public static void main(String[] args) {
// 创建一个List集合对象:实现类是ArrayList集合
List<String> lists = new ArrayList<>();
lists.add("Java");
lists.add("JavaEE");
lists.add("Java");
lists.add("Oracle");
lists.add("Mysql");
lists.add("Mybatis");
// 1.根据索引获取对应位置的元素
String ele = lists.get(3);
System.out.println(ele);
// 2.删除元素
//lists.remove(0); // 根据索引删除对应位置的元素
// lists.remove("Java");// 直接删除该元素,默认只删除最前面出现的这个元素。
// System.out.println(lists);
// 3.插入元素
/**
System.out.println(lists);
lists.add(2,"徐干");// 在指定索引位置处加入一个新元素,原来位置的元素会后移
System.out.println(lists);*/
// 4.修改元素
System.out.println(lists);
lists.set(3,"ORACLE");// 修改指定索引位置处的元素值!
System.out.println(lists);
}
}
总结:
在开发中如果用到了List集合应该关注这些与索引相关的功能。
2.List系列集合的遍历方式
- 研究List集合的遍历方式。
- List系列集合继承自Collection所以他有Collection的三种遍历方式。
- 因为List系列集合存在索引,他还本身还可以通过索引来实现遍历。
- 所以List集合总共有四种遍历方式:
- 集合的迭代器遍历
- 增强for循环
- JDK 1.8之后的新技术 : Lambda
- for循环 结合索引遍历,类似与数组的for循环遍历(List集合独有的遍历方式)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListDemo01 {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("Java");
lists.add("JavaEE");
lists.add("Java");
lists.add("Oracle");
lists.add("Mysql");
lists.add("Mybatis");
// (1) 集合的迭代器遍历
// 获取当前集合的迭代器
Iterator<String> it = lists.iterator();
// 开始遍历
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("----------------------------------------------");
// (2) 增强for循环
for(String ele : lists){
System.out.println(ele);
}
// (3) JDK 1.8之后的新技术 : Lambda了解即可
lists.forEach(ele -> {
System.out.println(ele);
});
// (4) for循环 结合索引遍历,类似与数组的for循环遍历
for(int i = 0 ; i < lists.size() ; i++) {
// i = 0 1 2 3 4 5
// list集合可以根据索引获取对应的元素值
String ele = lists.get(i);
System.out.println(ele);
}
}
}
- 总结:
- 如果使用List集合,因为他存在索引,所以可以选择使用for循环来遍历。
3.List系列集合的实现类_LinkedList
- 各种集合的特点:
- List系列集合的特点: 元素是有序,有索引,可重复的。
- – ArrayList : 元素是有序,有索引,可重复的。基于数组实现的。查询快,增删慢!
- – LinkedList : 元素是有序,有索引,可重复的。基于链表实现的。查询慢,增删快!
- Set系列集合的特点: 元素是无序,无索引,不可重复的。
- – HashSet: 元素是无序,无索引,不可重复的。
- – LinkedHashSet: 元素是有序,无索引,不可重复的。
- LinkedList : 元素是有序,有索引,可重复的。基于链表实现的。查询慢,增删快!
- List的功能,LinkedList的使用是完全一样的。
- LinkedList基于链表实现的,链表的首尾元素性能是比较好的(双链表)
- 链表结构的首尾元素是可以直接定位的,所以LinkedList的首尾元素操作的性能是比较好的。为此:LinekedList提供了很多首尾元素操作的功能API:
- 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。
import java.util.LinkedList;
import java.util.List;
public class LinkedListDemo01 {
public static void main(String[] args) {
// 用LinkedList集合做一个栈!!! 先进后出,后进先出
// 开发中大部分时候都是使用List定义集合,除非要用独有功能!!
LinkedList<String> lists = new LinkedList<>();
// 压栈 入栈
lists.push("第一颗子弹");
lists.push("第二颗子弹");
lists.push("第三颗子弹");
lists.push("第四颗子弹");
System.out.println(lists);
// 出栈 弹栈
System.out.println(lists.pop());
System.out.println(lists.pop());
System.out.println(lists);
// 做队列
LinkedList<String> lists1 = new LinkedList<>();
// 入队
lists1.addLast("徐干");
lists1.addLast("徐冰");
lists1.addLast("林文勇");
lists1.addLast("戴冰");
System.out.println(lists1);
// 出队
System.out.println(lists1.removeFirst());
System.out.println(lists1.removeFirst());
System.out.println(lists1.removeFirst());
}
}
总结:
如果经常需要对数据的前后元素进行增删操作,可以选择LinkedList集合
如果需要进行大量的元素查询或者遍历,而增删比较少的情况应该选择ArrayList集合
开发中用的最多的集合是:ArrayList集合
4.Set系列集合的使用
- HashSet: 元素是无序,无索引,不可重复的。
- 明确三个问题:
- a.为什么没有出现重复,为什么不可重复了。
- b.为什么Set集合无序了?
- c.为什么无索引? 因为元素是无序了,所以Java认为设计索引无意义!!
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo01 {
public static void main(String[] args) {
Set<String> sets = new HashSet<>();
sets.add("Java");
sets.add("Java EE");
sets.add("Oracle");
sets.add("Oracle");
sets.add("Mybatis");
System.out.println(sets);
}
}
-
Set系列集合元素不重复的原因:
- 对于基本数据类型:
- Set系列集合可以直接进行比较是否重复。
- 对于基本数据类型:
-
对于引用数据类型:
-
Set集合是如何去判断元素是否重复呢?
-
判断引用数据类型是否重复的步骤:
-
a.每次添加进入一个引用数据类型对象的时候,Set集合会先让对象与之前的对象进行比较
- 会先让两个对象调用自己的hashCode()方法(来自与Object)来获取哈希值(所谓的内存地址,其实是一个整数)进行比较.
- 如果两个对象的哈希值不一样,则认为两个对象是没有重复的。
- 如果两个对象的哈希值一样,集合会继续让两个对象进行equals比较(程序员可能自己重写了equals)。
- – 如果此时equals比较也是true,则认为真的重复了。
- – 如果此时equals比较返回false,则认为两个对象没有重复。
-
b.写一个流程图 * 提取两个对象调用自己的hashCode()方法比较返回的哈希值。 * / \ * false true * / \ * 不重复 继续让两个对象equals比较 * / \ * false true * / \ * 不重复 重复
-
-
-
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo02 {
public static void main(String[] args) {
Set<Double> scores = new HashSet<>();
scores.add(98.2);
System.out.println(scores.add(98.2));
scores.add(98.2);
scores.add(91.2);
scores.add(91.99);
System.out.println(scores);
// 用Set集合存放引用数据类型。
Set<Student> stus = new HashSet<>();
// 创建一些学生对象放入到集合stus中去!!
Student xg = new Student("徐干",21,'男');
Student xg1 = new Student("徐干",21,'男');
/*
System.out.println(xg.hashCode());
System.out.println(xg.hashCode());
System.out.println(xg1.hashCode());*/
stus.add(xg);
stus.add(xg);
stus.add(xg1);
System.out.println(stus);
}
}
-
需求:
- 集合认为只要两个对象的名字,年龄,性别一样那就认为是重复的对象。
- 这个时候程序员必须自己来重写hashCode()和equals方法,以便自己来指定重复规则!!!!
测试类:
public class HashSetDemo03 {
public static void main(String[] args) {
Set<Student> stus = new HashSet<>();
// 创建一些学生对象放入到集合stus中去!!
Student xg = new Student("徐干",21,'男');
Student xg1 = new Student("徐干",21,'男');
stus.add(xg);
stus.add(xg1);
System.out.println(stus);
}
}
Student类:
import java.util.Objects;
public class Student {
private String name ;
private int age ;
private char sex ;
//已省无参满参构造器set get方法
@Override
public int hashCode() {
System.out.println("==hashCode==");
// xg Objects.hash(徐干, 21, 男)
// xg1 Objects.hash(徐干, 21, 男)
// Objects.hash(name, age, sex)
return Objects.hash(name, age, sex);
}
@Override
public boolean equals(Object o) {
System.out.println("==equals==");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
sex == student.sex &&
Objects.equals(name, student.name);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
-
Set系列集合元素无序的原因?
- Set系列集合元素无序的根本原因是因为选择了哈希表存放数据。
- 哈希表:
- 数组 + 链表 + 哈希算法组成的:JDK1.8之前的形式。
- 数组 + 链表 + 红黑树(查询快) + 哈希算法组成的:JDK1.8之侯的形式,显著提高数据的查询性能。原因是因为使用了红黑树。
import java.util.HashSet; import java.util.Set; public class HashSetDemo04 { public static void main(String[] args) { Set<String> sets = new HashSet<>(); sets.add("Java"); sets.add("JavaEE"); sets.add("Oracle"); sets.add("Oracle"); sets.add("Mybatis"); System.out.println(sets); } }
-
总结:Hashset集合的性能是比较好的,增删改查性能都挺好的。
-
如果用Set系列集合,你要注意他是无序不重复的。
LinkedHashSet: 元素是"有序",无索引,不可重复的。
因为他的元素使用了一个链来记录添加顺序。所以有序!!
三、Collections类的详细使用
- Collections类:
- 与集合本身没有半毛钱关系,人家只是一个操作集合的工具类。
- Collections与Collection是没有关系的。
- Collections类提供了很多的静态方法来操作集合:
- 1.给Collection系列集合添加元素。
- 2打乱集合元素的顺序
- 3.给集合中的元素进行排序
- public static void shuffle(List<?> list) :打乱集合顺序。 只能打乱List系列集合的顺序。
- public static void sort(List list) :将集合中元素按照默认规则排序。升序!
- public static void sort(List list,Comparator<? super T> ) :将集合中元素按照指定规则排
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsDemo01 {
public static void main(String[] args) {
// 1.给Collection系列集合添加元素。
List<String> lists = new ArrayList<>();
// 添加元素
Collections.addAll(lists,"Java", "Java" , "Oracle" ,"Mysql" , "Mybatis" );
System.out.println(lists);
// 2.打乱集合顺序。 只能打乱List系列集合的顺序。
Collections.shuffle(lists);
System.out.println(lists);
// 3.排序!!
List<Double> dbs = new ArrayList<>();
// 添加元素
Collections.addAll(dbs,98.9 , 78.9 , 22.22, 110.2 );
System.out.println(dbs); // 有序:添加顺序
Collections.sort(dbs);
System.out.println(dbs); // 排序:大小顺序
}
}
- Collections类的使用 :
- public static void sort(List list) :将集合中元素按照默认规则排序。升序!支持数值类型的集合。基本数据类型。
- public static void sort(List list,Comparator<? super T> ) :将集合中元素按照指定规则排 ,比较器的
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class CollectionsDemo02 {
public static void main(String[] args) {
Student xb = new Student("徐彬",22, '男');
Student xg = new Student("徐干",21 , '男');
Student swk = new Student("孙悟空",600, '男');
Student zzj = new Student("蜘蛛精",1600, '妖');
List<Student> lists = new ArrayList<>();
// 添加元素
Collections.addAll(lists,swk,xb,xg,zzj);
System.out.println(lists);
// 排序
// Collections.sort(lists); // 默认不能对引用数据类型的集合进行排序,人家不知道怎么排!
/**
* 参数一:被排序的集合。
* 参数二:程序员申明比较器,制定比较规则。 告诉集合应该怎么排序,根据什么排序!
* */
Collections.sort(lists, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 集合会自动的每次传入两个学生进来让你自己来比较!!
/*// 开始制定规则:
if(o1.getAge() > o2.getAge()){
return 1 ; // 如果o1大于o2 ,返回正整数即可!集合会自动把较大的o1放在后面去
}else if(o1.getAge() < o2.getAge()){
return -1 ; // 如果o1小于o2 ,返回负整数即可!集合会自动把较大的o1放在前面
}
// 相等返回0
return 0;*/
return o2.getAge() - o1.getAge(); // 降序!
//return o1.getAge() - o2.getAge(); // 升序!
}
}); // 可以自己来制定一个比较规则:比较器!
System.out.println(lists);
}
}
- 总结:
- 自带值属性的集合直接用sort(List list)排序即可!
- 引用数据类型的集合应该自己写比较器和规则来排序:必须用 sort(List list,Comparator<? super T> ) :