【数据结构、List、Set、Collections】
数据结构
数据存储的常用结构有:栈、队列、数组、链表和红黑树。
栈
栈结构用四个字形容就是:后进先出。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGFBAOyp-1590548762944)(C:\Users\Ann\Desktop\java学习文档\笔记\picture\1559524949123931.png)]
队列
队列 结构简单地说就是:先进先出。即,存进去的元素,要在它前面的元素依次取出后,才能取出该元素。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vdNTAZXp-1590548762945)(C:\Users\Ann\Desktop\java学习文档\笔记\picture\timg.jpg)]
数组
数组Array,是有序的元素序列,数组 是在内存中开辟一段连续的空间,并在此空间存放元素。
简单地说,采用数组结构的集合,对元素的存取有如下的特点:
- 查找元素快:通过索引,可以快速访问指定位置的元素。
- 在内存中,数组的数据是连接存储的,数据长度固定,这样知道数组开头位置和偏移量就可以直接算出数据地址。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XRKmw3cD-1590548762946)(C:\Users\Ann\Desktop\java学习文档\笔记\picture\array1.png)]
- 增删元素慢:
- 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。并将新数组的地址值赋值给原变量,最后将源数组在内存中销毁(垃圾回收)。
- 在堆内存中,频繁的创建数组,赋值 数组中的元素,销毁数组,效率低下。
链表
链表linked list:由一系列结点node(链表中每一个元素成为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:**一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。**我们常说的链表结构有单向链表和双向链表。
特点:
- 查询慢:链表中地址不是连续的,每次查询元素,都必须从头开始查询。
- 增删快:链表结构,增加/删除一个元素,对链表的整体结构没有影响,所以增删快。
**单向链表:**链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序有可能不一样)。
**双向链表:**链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合。
红黑树
二叉树:binary tree,是每个结点不超过2的有序树(tree)。
简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点都最多只能有两个子节点。
二叉树是每个节点最多只能有两个子树的结构,顶上的叫根结点,两边被称作“左子树”和“右子树”。
在二叉树的基础上,元素是有大小顺序的,左子树小,右子树大,这称为顺序树/查找树。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XgCsCyMq-1590548762946)(C:\Users\Ann\Desktop\java学习文档\笔记\picture\红黑树.jpg)]
在二叉树中,左子树的数量与右子树的数量一样时,就是平衡树。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RpYovDEU-1590548762947)(C:\Users\Ann\Desktop\java学习文档\笔记\picture\平衡树.jpg)]
红黑树,特点:
- 趋近于平衡树,查询的速度非常的快,查询叶子节点最大次数和最小次数不能超过两倍。
- 约束:
- 1.节点可以是红色的或者黑色的;
- 2.根节点是黑色的;
- 3.叶子节点(空节点)是黑色的;
- 4.每个红色的节点的子节点都是黑色的;
- 5.任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahk2SoFV-1590548762948)(C:\Users\Ann\Desktop\java学习文档\笔记\picture\红黑树1.jpg)]
List集合
Collection接口有两个子接口:List接口、Set接口。
List接口的三大特点:
- 有序的集合,存储元素和取出元素的顺序是一致的(存储123,取出123);
- 有索引,包含了一些带索引的方法;
- 允许存储重复的元素。
List接口中带索引的方法:
方法 | 含义 |
---|---|
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.Iterator;
import java.util.List;
public class Demo01List {
public static void main(String[] args) {
//创建一个List对象,泛型为String,使用多态
List<String> list = new ArrayList<>();
//使用add方法往集合中添加元素
list.add("James");
list.add("Kobe");
list.add("Durant");
list.add("Harden");
list.add("Allen");
//打印集合
System.out.println(list);//[James, Kobe, Durant, Harden, Allen],重写了toString方法
//add(int index,E element)方法
//在Durant之后插入Curry
list.add(3,"Curry");
System.out.println(list);//[James, Kobe, Durant, Curry, Harden, Allen]
//remove(int index)方法,返回的是被移除的元素
//移除Allen
String allen = list.remove(5);
String kobe = list.remove(1);
System.out.println("已经退役的球员:"+allen+"、"+kobe);//已经退役的球员:Allen、Kobe
System.out.println(list);//[James, Kobe, Durant, Curry, Harden]
//set(int index,E element),返回的是被替换的元素
//将Harden替换为Leonard
String set = list.set(3, "Leonard");
System.out.println("被替换的球员是:"+set);//被替换的球员是:Harden
System.out.println(list);//[James, Durant, Curry, Leonard]
//get(int index),返回集合中当前位置的元素
//使用for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("----------------");
//使用迭代器遍历
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
System.out.println("----------------");
//使用增强for循环
for(String s:list){
System.out.println(s);
}
}
}
List接口的实现类
ArrayList集合
Java.util.ArrayList
集合数据存储的结构是数组结构的,元素增删慢,查找快,由于日常开发中使用的最多的功能为查询数据,遍历数据,所以ArrayList
是最常用的集合。
许多程序员开发时非常随意的使用ArrayList完成任何需求, 并不严谨,这种用法是不提倡的。
LinkedList集合
Java.util.LinkedList
集合数据存储的结构是链表结构,方便添加、删除元素。
LinkedList的特点:
- 底层是一个链表结构:查询慢,增删快;
- 里面包含了大量的操作首尾元素的方法;
- 注意:使用LinkedList集合的特有方法,不能使用多态创建对象
LinkedList特有的方法:
方法 | 含义 | 类型 |
---|---|---|
public void addFirst(E e); | 将指定元素插入此列表的开头 | 添加元素 |
public void addLast(E e); | 将指定的元素添加到此列表的末尾,等效于add | 添加元素 |
public void push(E e); | 将元素推入此列表所表示的堆栈,等效于addFirst | 添加元素 |
public E getFirst(); | 返回此列表的第一个元素 | 获取元素 |
public E getLast(); | 返回此列表的最后一个元素 | 获取元素 |
public E removeFirst(); | 移除并返回此列表的第一个元素 | 删除元素 |
public E removeLast(); | 移除并返回此列表的最后一个元素 | 删除元素 |
public E pop(); | 从此列表所表示的堆栈处弹出一个元素,等效于removeFirst | 删除元素 |
public boolean isEmpty(); | 如果列表不包含元素,则返回true | 查询列表状态 |
import java.util.LinkedList;
public class Demo01LinkedList {
public static void main(String[] args) {
//创建一个LinkedList集合对象
LinkedList<String> linked = new LinkedList<>();
//使用add方法往集合中添加元素
linked.add("a");
linked.add("b");
linked.add("c");
System.out.println(linked);//[a, b, c]
addElement(linked);
getElement(linked);
removeElement(linked);
//判断集合是否为空。
boolean emptyOrNot = linked.isEmpty();
System.out.println("集合是否为空:"+emptyOrNot);
}
public static void addElement(LinkedList<String> linked){
//addFirst(E e):将指定的元素插入此列表的开头
linked.addFirst("0");
System.out.println(linked);//[0, a, b, c]
//addLast(E e):将指定的元素插入此列表的末尾,此方法等效于add方法
linked.addLast("1");
System.out.println(linked);//[0, a, b, c, 1]
//push(E e):将元素推入此列表所表示的堆栈,等效于addFirst
linked.push("w");
System.out.println(linked);//[w, 0, a, b, c, 1]
}
public static void getElement(LinkedList<String> linked){
//getFirst():返回此列表的第一个元素
String first = linked.getFirst();
System.out.println(first);//w
//getLast():返回此列表的最后一个元素
String last = linked.getLast();
System.out.println(last);//1
}
public static void removeElement(LinkedList<String> linked){
//removeFirst():移除并返回此列表的第一个元素
String s = linked.removeFirst();
System.out.println("移除第一个元素"+s);
System.out.println(linked);
//removeLast():移除并返回列表的最后一个元素
String s1 = linked.removeLast();
System.out.println("移除最后一个元素"+s1);
System.out.println(linked);
//pop():从此列表所表示的堆栈处弹出一个元素,相当于removeFirst
String pop = linked.pop();
System.out.println("从堆栈中弹出一个元素"+pop);
System.out.println(linked);
}
}
Vector集合(了解)
Vector类集合可以实现可增长的对象数组。Vector类型是同步的,是单线程的,速度慢。在JDK 1.2以后被ArrayList类集合取代。
Set集合
Set接口的特点:
- 不允许存储重复的元素;
- 没有索引,没有带索引的方法,也不能使用普通的for循环遍历。
Set接口的实现类
HashSet集合
此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set的迭代顺序:特别是不保证该顺序恒久不变。此类允许使用null元素。
Java.util.HashSet集合 implements Set接口。
HashSet特点:
- 不允许存储重复的元素;
- 没有索引,没有带索引的方法,也不能使用普通的for循环遍历;
- 是一个无序的集合,存储元素和取出元素的顺序有可能不一致;
- 底层是一个哈希表结构(查询的速度非常的快)。
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo01HashSet {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
//使用add方法往集合中添加元素
set.add(3);
set.add(2);
set.add(5);
set.add(3);
//使用迭代器遍历set集合
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()){
Integer next = iterator.next();
System.out.println(next);//2,3,5
}
//使用增强for循环遍历set集合
for (Integer integer : set) {
System.out.println(integer);//2,3,5
}
}
}
哈希值
哈希值是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址。是模拟出来得到的地址,不是数据实际存储的物理地址。
在Object类有一个方法可以获取对象的哈希值。
int hashCode()
:返回该对象的哈希码值。
hashcode方法的源码:
public native int hashCode();
native:代表该方法调用的是本地操作系统的方法。
public class Demo01HashCode {
public static void main(String[] args) {
//Person类继承了Object类,所以可以使用Object类的hashCode方法
Person p1 = new Person();
int h1 = p1.hashCode();
System.out.println(h1);//0
Person p2 = new Person();
int h2 = p2.hashCode();
System.out.println(h2);//0
System.out.println(p1);//an.kaikeba.demo01.Person@0
System.out.println(p2);//an.kaikeba.demo01.Person@0
System.out.println(p1 == p2);//实际物理地址值不相等
//String类的哈希值
//String类重写了Object类的hashCode方法
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2);//false
System.out.println(str1.hashCode());//96354
System.out.println(str2.hashCode());//96354
//重地与通话的哈希值一样
System.out.println("重地".hashCode());
System.out.println("通话".hashCode());
}
}
哈希表
HashSet集合存储数据的结构(哈希表),特点是查询快。
jdk1.8版本之前,哈希表 = 数组 + 链表
jdk1.8版本之后:
- 哈希表 = 数组 + 链表
- 哈希表 = 数组 + 红黑树(提高查询的速度)
哈希表数组结构,把元素进行分组,相同哈希值的元素是一组,链表/红黑树结构把相同哈希值的元素连接到一起。
- 数组更利于元素的查找;
- 链表更利于元素的插入和删除;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-poIGp8YM-1590548762949)(C:\Users\Ann\Desktop\java学习文档\笔记\picture\哈希表.jpg)]
哈希值不同的元素,放入数组的不同位置。
两个元素不同,但是哈希值相同,成为哈希冲突。两个不同的元素,将会以链表的形式进行连接。
如果数组的同一位置存放的数据超过8位,会以红黑树的结构进行存储。
Set集合存储元素不重复的原理
Set集合在调用add方法的时候,add方法会调用元素的hashCode方法和equals方法,判断元素是否重复。
import java.util.HashSet;
public class Demo01HashSetSaveString {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet<String> set = new HashSet<>();
String str1 = new String("abc");
String str2 = new String("abc");
set.add(str1);
set.add(str2);
set.add("重地");
set.add("通话");
set.add("abc");
System.out.println(set);//[重地, 通话, abc]
}
}
对于set.add(str1);
add方法会调用str1的hashCode方法,计算字符串“abc”的哈希值,哈希值是96354。
在几个中找有没有96354这个哈希值的元素,发现没有,就会把str1存储到集合中。
对于set.add(str2);
add方法会调用str2的hashCode方法,计算字符串“abc”的哈希值,哈希值是96354。
在集合中找有没有96354这个哈希值的元素,发现有(哈希冲突)。
str2会调用equals方法和哈希值相同的元素进行比较,str2.equals(str1),返回true
两个元素的哈希值相同,equals方法返回true,认定两个元素相同。
就不会把str2存储到集合中。
对于set.add(“重地”);
add方法会调用”重地“的hashCode方法,计算字符串”重地“的哈希值,哈希值是1179395。
在几个中找有没有1179395这个哈希值的元素,发现没有,就会把”重地“存储到集合中。
对于set.add(“通话”);
add方法会调用"通话"的hashCode方法,计算字符串"通话"的哈希值,哈希值是1179395。
在几个中找有没有1179395这个哈希值的元素,发现有(哈希冲突)。
”通话“会调用equals方法和哈希值相同的元素进行比较,”通话“.equals(”重地“),返回false
两个元素的哈希值相同,equals方法返回false,认定两个元素不同。
就会把“通话”存储到集合中。
**【重点】**set集合存储元素,要保证元素不重复,前提是:
存储的元素必须重写hashCode方法和equals方法。
HashSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode方法和equals方法,建立自己的比较方式,才能保证HashSet集合中对象唯一。
创建自定义类Student类,对该类的hashCode方法和equals方法进行重写。
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int hashCode() {
return Objects.hash(name, 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);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
import java.util.HashSet;
public class Demo01HashSetSaveString {
public static void main(String[] args) {
//创建HashSet集合存储Student
HashSet<Student> set = new HashSet<>();
Student p1 = new Student("小美女",18);
Student p2 = new Student("小美女",18);
Student p3 = new Student("小野兽",22);
System.out.println(p1.hashCode());//734175839
System.out.println(p2.hashCode());//734175839
System.out.println(p1 == p2);//false
System.out.println(p1.equals(p2));//true
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(set);
//[Student{name='小美女', age=18}, Student{name='小野兽', age=22}]
}
}
LinkedHashSet集合
Java.util.LinkedHashSet集合 extends HashSet集合。
具有可预知迭代顺序的Set接口的哈希表和链接列表实现。此实现与HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入set的顺序(插入顺序)进行迭代。
LinkedHashSet集合特点:
- 底层是一个哈希表(数组+链表/红黑树)+ 链表:多了一条链表,记录元素的存储顺序,保证元素有序
import java.util.HashSet;
import java.util.LinkedHashSet;
public class Demo01LinkedHashSet {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("www");
set.add("www");
set.add("abc");
set.add("it");
System.out.println(set);//[abc, www, it],无序,不允许重复
LinkedHashSet<String> set1 = new LinkedHashSet<>();
set1.add("www");
set1.add("www");
set1.add("abc");
set1.add("it");
System.out.println(set1);//[www, abc, it],有序,不允许重复
}
}
可变参数
可变参数:jdk1.5之后出现的新特性。
使用前提:
- 当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数。
使用格式:定义方法是使用。
修饰符 返回值类型 方法名(数据类型 … 变量名){}
可变参数的原理:
- 可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数。
- 传递参数的个数,可以是0个(不传递),1,2,…多个
public class Demo01VarArgs {
public static void main(String[] args) {
int s = sum(10, 20, 30);
System.out.println(s);//60
}
//定义计算(0~n)整数和的方法
//已知:计算整数的和,数据类型已经确定int
//但是参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数
public static int sum(int... array) {
int s = 0;
for (int i : array) {
s += i;
}
return s;
}
}
可变参数的注意事项:
- 一个方法的参数列表,只能有一个可变参数;
- 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾。
public static void print(int a,double b,String c,char ...d){
//...
}
可变参数的终极用法:
public static void print(Object ...o){
//...
}
Collections
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:
方法 | 含义 |
---|---|
public static boolean addAll(Collections c,T …elements); | 往集合中添加一些元素 |
public static void shuffle(List list); | 打乱集合中元素顺序 |
public static void sort(List list); | 将集合中元素按照默认规则排序 |
public static void sort(List list,Comparator<? super T>) | 将集合中元素按照指定规则排序 |
addAll方法&shuffle方法
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class Demo01Collections {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"a","b","c","d");
System.out.println(list);//[a, b, c, d]
Collections.shuffle(list);
System.out.println(list);//[d, b, c, a]
}
}
sort(List)方法
public static <T> void sort(List<T> list);
:将集合中元素按照默认规则排序。
import java.util.ArrayList;
import java.util.Collections;
public class Demo01sort {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"A","a","0");
System.out.println(list);//[A, a, 0]
//将集合中的元素按照默认规则排序,默认是升序
Collections.sort(list);
System.out.println(list);//[0, A, a]
ArrayList<Integer> list2 = new ArrayList<>();
Collections.addAll(list2,2,78,33);
System.out.println(list2);//[2, 78, 33]
//将集合中的元素按照默认规则排序,默认是升序
Collections.sort(list2);
System.out.println(list2);//[2, 33, 78]
}
}
注意事项:如果采用sort方法对元素为自定义类的对象的集合进行排序,该类需要实现Comparable接口,并重写里面的compareTo方法,定义排序的规则。
Comparable接口的排序规则:
自己(this)- 参数:升序
参数 - 自己(this):降序
public class Person implements Comparable<Person> {
private String name;
private int age;
public String getName() {
return name;
}
@Override
public String toString() {
return "Person{name='" + this.name + "\'" + ",age=" + this.age + "}";
}
public Person() {
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person o) {
//自定义比较的规则,比较两个人的年龄(this,参数Person)
return (this.getAge() - o.getAge());//升序
//return (o.getAge() - this.getAge());//降序
}
}
import java.util.ArrayList;
import java.util.Collections;
public class Demo01sort {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
Person p1 = new Person("James",35);
Person p2 = new Person("Durant",30);
Person p3 = new Person("Liao",25);
Collections.addAll(list,p1,p2,p3);
System.out.println(list);
//[Person{name='James',age=35}, Person{name='Durant',age=30}, Person{name='Liao',age=25}]
//对对象元素进行排序,排序规则为按年龄升序
Collections.sort(list);
System.out.println(list);
//[Person{name='Liao',age=25}, Person{name='Durant',age=30}, Person{name='James',age=35}]
}
}
sort(List,Comparator)方法
Comparator和Comparable的区别
- Comparator:相当于找一个第三方的裁判,来比较两个对象,Comparator是一个接口,需要实现并重写其compare方法
- Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
Comparator接口的排序规则:
o1 - o2:升序
o2 - o1:降序
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Demo01sortComparator {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,2,3,5,7,1,2);
System.out.println(list);//[2, 3, 5, 7, 1, 2]
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;//升序
}
});
System.out.println(list);//[1, 2, 2, 3, 5, 7]
}
}
r是一个接口,需要实现并重写其compare方法
- Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
Comparator接口的排序规则:
o1 - o2:升序
o2 - o1:降序
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Demo01sortComparator {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,2,3,5,7,1,2);
System.out.println(list);//[2, 3, 5, 7, 1, 2]
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;//升序
}
});
System.out.println(list);//[1, 2, 2, 3, 5, 7]
}
}