List、Set、数据结构、Collections
1 数据结构
1.2 常?的数据结构
数据存储的常?结构有:栈、队列、数组、链表和红?树。我们分别来了解?下:
栈
- 栈:stack,?称堆栈,它是运算受限的线性表,其限制是仅允许在标的?端进?插?和删除操作,不允许在其他任何位置进?添加、查找、删除等操作。
简单的说:采?该结构的集合,对元素的存取有如下的特点
- 先进后出(即,存进去的元素,要在它后?存进去的元素依次取出后,才能取出该元素)。例如,?弹压进弹夹,先压进去的?弹在下?,后压进去的?弹在上?,当开枪时,先弹出上?的?弹,然后才能弹出下?的?弹。
- 栈的??、出?的都是栈的顶端位置。
这?两个名词需要注意: - 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底?向移动?个位置。
- 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶?向移动?个位置。
队列
- 队列:queue,简称队,它同堆栈?样,也是?种运算受限的线性表,其限制是仅允许在表的?端进?插?,?在表的另?端进?删除。
简单的说,采?该结构的集合,对元素的存取有如下的特点:
- 先进先出(即,存进去的元素,要在后它前?的元素依次取出后,才能取出该元素)。例如,???过?洞,?头先进去,?尾后进去;?头先出来,?尾后出来。
- 队列的??、出?各占?侧。例如,下图中的左侧为??,右侧为出?。
数组
- 数组:Array,是有序的元素序列,数组是在内存中开辟?段连续的空间,并在此空间存放元素。就像是?排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房?的?。
简单的说,采?该结构的集合,对元素的存取有如下的特点:
- 查找元素快:通过索引,可以快速访问指定位置的元素
- 增删元素慢
- 指定索引位置增加元素:需要创建?个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
链表
- 链表:linked list,由?系列结点node(链表中每?个元素称为结点)组成,结点可以在
运?时动态?成。每个结点包括两个部分:?个是存储数据元素的数据域,另?个是存储下 ?个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这?给?家介绍 的是单向链表。
简单的说,采?该结构的集合,对元素的存取有如下的特点:
红?树
- ?叉树:binary tree ,是每个结点不超过2的有序树(tree),顶上的叫根结点,两边被称作“左?树”和“右?树”。
简单的理解,就是?种类似于我们?活中树的结构,只不过每个结点上都最多只能有两个?结点。
如图:
我们要说的是?叉树的?种?较有意思的叫做红?树,红?树本身就是?棵?叉查找树,将节点插?后,该树仍然是?颗?叉查找树。也就意味着,树的键值仍然是有序的。 红?树的约束:
- 节点可以是红?的或者??的
- 根节点是??的
- 叶?节点(特指空节点)是??的
- 每个红?节点的?节点都是??的
- 任何?个节点到其每?个叶?节点的所有路径上??节点数相同
红?树的特点: 速度特别快,趋近平衡树,查找叶?元素最少和最多次数不多于?倍
2 List集合
我们掌握了Collection接?的使?后,再来看看Collection接?中的?类,他们都具备那些特性呢?
接下来,我们?起学习Collection中的常??个?类( java.util.List 集合、 java.util.Set 集合)。
2.1 List接?介绍
java.util.List 接?继承? Collection 接?,是单列集合的?个重要分?,习惯性地会将实现了 List 接?的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以?种线性?式进?存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有?个特点就是元素有序,即元素的存?顺序和取出顺序?致。
看完API,我们总结?下:
List接?特点:
- 它是?个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
- 它是?个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是?个道理)。
- 集合中可以有重复的元素,通过元素的equals?法,来?较是否为重复的元素。
Tips:我们在基础班的时候已经学习过List接?的?类java.util.ArrayList类,该类中的?法都是来?List中定义。
2.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) :?指定元素替换集合中指定位置的元素,返回值的更新前的元素。
List集合特有的?法都是跟索引相关,我们在基础班都学习过,那么我们再来复习?遍吧:
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("图图");
list.add("?美");
list.add("不?兴");
System.out.println(list);
list.add(1, "没头脑");
System.out.println(list);
System.out.println("删除索引位置为2的元素");
System.out.println(list.remove(2));
System.out.println(list);
list.set(0, "三?");
System.out.println(list);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
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是List的?类,List中的?法LinkedList都是可以使?,这?就不做详细介绍,我们只需要了解LinkedList的特有?法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使?。
?法演示:
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
System.out.println(link);
System.out.println(link.getFirst());
System.out.println(link.getLast());
System.out.println(link.removeFirst());
System.out.println(link.removeLast());
while (!link.isEmpty()) {
System.out.println(link.pop());
}
System.out.println(link);
}
}
4 Set接?
java.util.Set 接?和 java.util.List 接??样,同样继承? Collection 接?,它与 Collection 接?中的?法基本?致,并没有对 Collection 接?进?功能上的扩充,只是?Collection 接?更加严格了。与 List 接?不同的是, Set 接?中元素?序,并且都会以某种规则保证存?的元素不出现重复。
Set 集合有多个?类,这?我们介绍其中的 java.util.HashSet 、 java.util.LinkedHashSet 这两个集合。
Tips:Set集合取出元素的?式可以采?:迭代器、增强for。
4.1 HashSet集合介绍
java.util.HashSet 是 Set 接?的?个实现类,它所存储的元素是不可重复的,并且元素都是?序的(即存取顺序不?致)。 java.util.HashSet 底层的实现其实是?个 java.util.HashMap ?持.
HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯?性的?式依赖于: hashCode 与 equals ?法。
我们先来使??下Set集合存储,看下现象,再进?原理的讲解:
public class HashSetDemo {
public static void main(String[] args) {
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
Tips:根据结果我们发现字符串"cba"只存储了?个,也就是说重复的元素set集合不存储。
4.2 HashSet集合存储数据的结构(哈希表)
什么是哈希表呢?
在JDK1.8之前,哈希表底层采?数组+链表实现,即使?链表处理冲突,同?hash值的链表都存 储在?个链表?。但是当位于?个桶中的元素较多,即hash值相等的元素较多时,通过key值依 次查找的效率较低。?JDK1.8中,哈希表存储采?数组+链表+红?树实现,当链表?度超过阈值 (8)时,将链表转换为红?树,这样??减少了查找时间。
简单的来说,哈希表是由数组+链表+红?树(JDK1.8增加了红?树部分)实现的,如下图所示。
为了?便?家的理解我们结合?个存储流程图来说明?下: 总??之,JDK1.8引?红?树?程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯?,其实就是根据对象的hashCode和equals?法来决定的。如果我们往集合中存放?定义的对象,那么保证其唯?,就必须复写hashCode和equals?法建?属于当前对象的?较?式。
4.3 HashSet存储?定义类型元素
给HashSet中存放?定义类型元素时,需要重写对象中的hashCode和equals?法,建???的?较?式,才能保证HashSet集合中的对象唯?。
创建?定义Student类
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);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class HashSetDemo2 {
public static void main(String[] args) {
HashSet<Student> stuSet = new HashSet<Student>();
Student stu = new Student("于谦", 43);
stuSet.add(stu);
stuSet.add(new Student("郭德纲", 44));
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("郭麒麟", 23));
stuSet.add(stu);
for (Student stu2 : stuSet) {
System.out.println(stu2);
}
}
}
运行结果:
Student{name='郭德纲', age=44}
Student{name='于谦', age=43}
Student{name='郭麒麟', age=23}
4.4 LinkedHashSet
我们知道HashSet保证元素唯?,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办?
在HashSet下?有?个?类 java.util.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());
}
}
}
结果:
bbb
aaa
abc
bbc
4.5 可变参数
在JDK1.5之后,如果我们定义?个?法需要接受多个参数,并且多个参数类型?致,我们可以对其简化成如下格式:
修饰符 返回值类型 ?法名(参数类型... 形参名) { }
其实这个书写完全等价与
修饰符 返回值类型 ?法名(参数类型[] 形参名) { }
只是后?这种定义,在调?时必须传递数组,?前者可以直接传递数据即可。
JDK1.5以后。出现了简化操作。… ?在参数上,称之为可变参数。
同样是代表数组,但是在调?这个带有可变参数的?法时,不?创建数组(这就是简单之处),直接将数组中的元素作为实际参数进?传递,其实编译成的.class?件,将这些元素先封装到?个数组中,在进?传递。这些动作都在编译.class?件时,?动完成了。
代码演示:
public class ChangeArgs {
public static void main(String[] args) {
int[] arr = {1, 4, 62, 431, 2};
int sum = getSum(arr);
System.out.println(sum);
int sum2 = getSum(6, 7, 2, 12, 2121);
System.out.println(sum2);
}
public static int getSum(int... arr) {
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
}
Tips:上述getSum?法在同?个类中,只能存在?个。因为会发?调?的不确定性 注意:如果在?法书写时,这个?法拥有多参数,参数中包含可变参数,可变参数?定要写在参数列表的末尾位置。
5 Collections
5.1 常?功能
java.utils.Collections 是集合?具类,?来对集合进?操作。部分?法如下:
- public static < T > boolean addAll(Collection< T > c, T… elements) :往集合中添加?些元素。
- public static void shuffle(List<?> list) :打乱集合顺序。
- public static < T > void sort(List< T > list) :将集合中元素按照默认规则排序。
- public static < T > void sort(List< T > list,Comparator<? super T> ) :将集合中元素按照指定规则排序。
代码演示:
public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.addAll(list, 5, 222, 1, 2);
System.out.println(list);
Collections.sort(list);
System.out.println(list);
}
}
结果:
[5, 222, 1, 2]
[1, 2, 5, 222]
代码演示之后 ,发现我们的集合按照顺序进?了排列,可是这样的顺序是采?默认的顺序,如果想要指定顺序那该怎么办呢?
我们发现还有个?法没有讲, public static < T > void sort(List< T > list,Comparator<? super T> ) :将集合中元素按照指定规则排序。接下来讲解?下指定规则的排列。
5.2 Comparator?较器
我们还是先研究这个?法 public static < T > void sort(List< T > list) :将集合中元素按照默认规则排序。 不过这次存储的是字符串类型。
public class CollectionsDemo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
Collections.sort(list);
System.out.println(list);
}
}
结果:
[aba, cba, nba, sba]
我们使?的是默认的规则完成字符串的排序,那么默认规则是怎么定义出来的呢?
说到排序了,简单的说就是两个对象之间?较??,那么在JAVA中提供了两种?较实现的?式,?种是?较死板的采? java.lang.Comparable 接?去实现,?种是灵活的当我需要做排序的时候在去选择的 java.util.Comparator 接?完成。
那么我们采?的 public static < T > void sort(List< T > list) 这个?法完成的排序,实际上要求了被排序的类型需要实现Comparable接?完成?较的功能,在String类型上如下:
public final class String implements java.io.Serializable,
Comparable<String>, CharSequence { }
String类实现了这个接?,并完成了?较规则的定义,但是这样就把这种规则写死了,那?如我想要字符串按照第?个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使?public static < T > void sort(List< T > list,Comparator<? super T> ) ?法灵活的完成,这个??就涉及到了Comparator这个接?,位于位于java.util包下,排序是comparator能实现的功能之?,该接?代表?个?较器,?较器具有可?性!顾名思义就是做排序的,通俗地讲需要?较两个对象谁排在前谁排在后,那么?较的?法就是:
- public int compare(String o1, String o2) :?较其两个参数的顺序。
两个对象?较的结果有三种:?于,等于,?于。 如果要按照升序排序, 则o1 ?于o2,返回(负数),相等返回0,o1?于o2返回(正数) 如果要按照降序排序 则o1 ?于o2,返回(正数),相等返回0,o1?于o2返回(负数)
操作如下:
public class CollectionsDemo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) - o1.charAt(0);
}
});
System.out.println(list);
}
}
结果如下:
[sba, nba, cba, aba]
5.3 简述Comparable和Comparator两个接?的区别。
Comparable:强?对实现它的每个类的对象进?整体排序。这种排序被称为类的?然排序,类 的 compareTo ?法被称为它的?然?较?法。只能在类中实现 compareTo() ?次,不能经常修 改类的代码实现??想要的排序。实现此接?的对象列表(和数组)可以通过Collections.sort(和Arrays.sort) 进??动排序,对象可以?作有序映射中的键或有序集合中的元素,?需指定?较器。
Comparator:强?对某个对象进?整体排序。可以将 Comparator 传递给 sort ?法 (如Collections.sort或 Arrays.sort) ,从?允许在排序顺序上实现精确控制。还可以使?Comparator 来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有?然顺序的对象 collection 提供排序。
5.4 练习
创建?个学?类,存储到ArrayList集合中完成指定排序操作。 Student 初始类
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类:
public class Demo {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("rose",18));
list.add(new Student("jack",16));
list.add(new Student("abc",16));
list.add(new Student("ace",17));
list.add(new Student("mark",16));
Comparable接?
for (Student student : list) {
System.out.println(student);
}
}
}
发现,当我们调? Collections.sort() ?法的时候 程序报错了。
原因:如果想要集合中的元素完成排序,那么必须要实现?较器 Comparable 接?。
于是我们就完成了 Student 类的?个实现,如下:
public class Student implements Comparable<Student> {
....
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
再次测试,代码就OK 了效果如下:
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}
Student{name='ace', age=17}
Student{name='rose', age=18}
5.5 扩展
如果在使?的时候,想要独?的定义规则去使? 可以采?Collections.sort(List list,Comparetor c)?式,??定义规则:
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getAge()-o1.getAge();
}
});
Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}
如果想要规则更多?些,可以参考下?代码:
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int result = o2.getAge() - o1.getAge();
if (result == 0) {
result = o1.getName().charAt(0) - o2.getName().charAt(0);
}
return result;
}
});
效果如下:
Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='abc', age=16}
Student{name='jack', age=16}
Student{name='mark', age=16}
|