数据结构
和集合相关的数据结构
-
栈:先进后出(类似于AK的弹夹,先放进去的子弹后打出来)
-
队列:先进先出(类似于卫生间排队,先来先去上厕所)
-
数组:查询快、增删慢
查询快:数组的地址是连续的,可以通过数组的首地址来找到数组并通过索引进行元素查找
增删慢:数组的长度是固定的,增加或者删除一个元素都需要建立一个新的数组后复制原数组
-
链表:查询慢、增删快
查询慢:每次查询元素都是要从头开始查询
增删快:链表结构,增加或者删除一个元素对链表的整体结构没有影响
- 链表中的每一个元素也叫做一个节点,一个节点又包含了一个数据源(存储数组)和两个指针域(存储地址)
-
红黑树
二叉树、排序树(在二叉树的基础上元素有大小顺序:左子树小、右子数大)、平衡树(左孩子和右孩子的数目是相等的)、不平衡树(左右孩子不相等)
红黑树:特点趋近于平衡树,查询的速度非常快,查询叶子节点最大次数和最小次数不能超过2倍
约束:
- 节点可以是红色的或者是黑色的
- 根节点是黑色的
- 叶子节点(空节点)是黑色的
- 每个红色的节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
List集合
特点:
- 存取有序的集合。存元素的顺序是11、22、33,取11、22、33
- 带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
四种常用方法:
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。public E get(int index)
:返回集合中指定位置的元素。public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
常见方法的使用以及三种不同的遍历方式:
package Demo05;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class DemoList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a"); //允许重复
//在b、c之间插入Hello
list.add(2,"Hello");
//返回指定索引的值
String i = list.get(0);
System.out.println(i); //a
//移除索引值为3的元素(c),返回值是这个被移除的元素
String b = list.remove(3);
System.out.println(b); //cc
//将索引为3的元素替换为Worlcd,返回的是替换前的元素
String c = list.set(3,"World");
System.out.println(c); //d
System.out.println(list); //直接打印[a, b, Hello, World, a]
//遍历list集合
System.out.println("==普通的for循环==");
for (int j = 0; j < list.size(); j++) {
System.out.println(list.get(j));
}
System.out.println("==使用迭代器==");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("==增强for循环==");
for (String s:list) {
System.out.println(s);
}
}
}
List的子类(ArrayList、LinkedList、Vector)
ArrayList集合(底层是一个数组,数组的特点查询快、增删慢,所以对于查询多的集合建议使用ArrayList,但是增删多的不建议使用)
LinkedList集合(LinkedList
是一个双向链表)
特点:
- 底层是链表结构:查询慢、增删快
- 包含大量操作首尾元素的方法
常用方法:
-
public void addFirst(E e)
:将指定元素插入此列表的开头。 -
public void addLast(E e)
:将指定元素添加到此列表的结尾。 -
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。
package Demo05;
import java.util.LinkedList;
public class DemoLinkedList {
public static void main(String[] args) {
//show01();
//show02();
show03();
}
private static void show03() {
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// public E removeFirst():移除并返回此列表的第一个元素。
String a = list.removeFirst();
System.out.println(a); // a
// public E removeLast():移除并返回此列表的最后一个元素。
String b = list.removeLast();
System.out.println(b); // e
// public E pop() :从此列表所表示的堆栈处弹出一个元素。与removeFirst()方法作用相同
String c = list.pop();
System.out.println(c); //b
}
private static void show02() {
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
if (!list.isEmpty()){ //判断是否为空集合
// public E getFirst():返回此列表的第一个元素
String first = list.getFirst();
System.out.println(first); // a
// public E getLast():返回此列表的最后一个元素
String last = list.getLast();
System.out.println(last); // c
}
}
private static void show01() {
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
//public void addFirst(E e):将指定元素插入此列表的开头。list.push("First");push方法与addFirst相同
list.addFirst("First"); // [First, a, b, c]
//public void addLast(E e):将指定元素添加到此列表的结尾。等同于list.add()方法
list.addLast("last"); // [First, a, b, c, last]
System.out.println(list);
}
}
Vector集合(知道有即可,不做过多了解)
底层也是数组
Set接口
- 不允许有重复元素
- 没有索引,没有带索引的方法,不能通过普通的for循环进行遍历
HashSet:
它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致),底层是一个Hash表结构,查询特别快
package Demo05;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class DemoSet {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(3);
set.add(2);
set.add(1); //不能有重复元素
System.out.println(set); // 存和取的顺序不一致,[1, 2, 3]
//由于没有索引,所以通过迭代器进行遍历
System.out.println("==迭代器循环遍历==");
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//使用增强for循环进行遍历
System.out.println("==增强for循环遍历==");
for (Integer i : set){
System.out.println(i);
}
}
}
哈希值:是一个十进制的整数,由系统随机给出(就是对象的逻辑地址值,但是是模拟出来的,不是数据实际存储的地址)
hashCode()可以重写,没有重写系统给多少就是多少,重写了你想返回多少就多少
package Demo05;
public class DemoPerson_HashCode {
public static void main(String[] args) {
Person person1 = new Person(18,"迪丽热巴");
Person person2 = new Person(20,"古力娜扎");
int HashCode1 = person1.hashCode();
//hashCode()可以重写,没有重写系统给多少就是多少,重写了你想返回多少就多少
int HashCode2 = person2.hashCode();
System.out.println(HashCode1); //356573597
System.out.println(HashCode2); //1735600054
System.out.println(person1); //Demo05.Person@1540e19d
System.out.println(person2); //Demo05.Person@677327b6
//356573597与1540e19d,1735600054与677327b6两两对应,一个十进制一个十六进制
}
}
HashSet集合存储数据的结构(哈希表)
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
而JDK1.8中,哈希表存储底层采用数组+链表+红黑树实现,简单来说就是数组用来存放哈希值,然后数组的下方加入链表,具有相同哈希值的元素放在链表中;当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
set集合存储元素不重复的原理:
Set<String> set = new HashSet<>();
set.add("abc");
set.add("重地");
set.add("通话");
set.add("abc");
System.out.println(set); //[重地, 通话, abc]
-
set集合在调用add方法时会调用元素的hashCode方法和equals方法判断是否重复
-
当加入字符串“abc”时,add方法会先调用hashCode方法生成一个哈希值------96354,然后在集合当中寻找有没有这个哈希值在,当发现没有的时候,就会把“abc”存入集合当中
-
当第二个“abc”加入的时候,也会产生哈希值------96354,但是这个时候集合中已经有这个哈希值了,这个时候就会调用euqals方法将两个元素进行比较,返回true,发现两个元素一样,就不会存入第二个“abc”
-
而对于“重地”和“通话”,存入“重地”后,会有一个哈希值为------1179395的元素,当存入“通话”时,产生哈希冲突,这个时候同样调用equals方法,发现两个元素不同,就会把元素“通话”存入
重点是:先调用hashCode方法,产生哈希值然后判断,是否存在相同哈希值的元素,不存在,直接存入;如果存在,则再调用equals方法,判断两个元素是否相同,不同则存入,相同则不存
HashSet存储自定义类型元素
在使用系统自定义的hashCode和equals方法时会出现存入哈希值相同的情况,例如“重地”和“通话”,如果需要存放入自定义的类型元素时,就需要重写hashCode和equals方法,建立自己的比较方式,保证HashSet集合中的对象唯一
package Demo05;
import java.util.HashSet;
public class HashSetSelf {
public static void main(String[] args) {
HashSet<HashSetSelfPerson> set = new HashSet<>();
HashSetSelfPerson person1 = new HashSetSelfPerson("迪丽热巴",18);
HashSetSelfPerson person2 = new HashSetSelfPerson("迪丽热巴",18);
HashSetSelfPerson person3 = new HashSetSelfPerson("迪丽热巴",19);
System.out.println(person1.hashCode()); //重写前:356573597 后:289834393
System.out.println(person2.hashCode()); //重写前:1735600054 后:289834393
set.add(person1);
set.add(person2);
set.add(person3);
System.out.println(set);
}
}
package Demo05;
import java.util.Objects;
public class HashSetSelfPerson {
private String name;
private int age;
public HashSetSelfPerson() {
}
public HashSetSelfPerson(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "HashSetSelfPerson{" +
"name='" + name + '\'' +
", 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;
}
//重写的equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HashSetSelfPerson that = (HashSetSelfPerson) o;
return age == that.age &&
Objects.equals(name, that.name);
}
//重写的hashCode方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
LinkedHashSet
HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。
LinkedHashSet底层是一个哈希表,保证元素有序。可以有序输出,但是仍不允许重复
package Demo05;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class DemoLinkedHashSet {
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
可变参数
当方法的参数列表数据已经确定,但是参数的个数没有确定时,就可以使用可变参数
格式:修饰符 返回值类型 方法名(参数类型... 形参名){ }
底层是一个数组:根据传入参数的不同,会创建不同长度的数组,来存储这些参数
package Demo05;
public class DemoVarArgs {
public static void main(String[] args) {
int i = add(10,20,30,40,50); //add(x),x传递几个数据,就创建多少长度的数组
System.out.println(i); //150
int j = add(10,20);
System.out.println(j); //30
}
/*
计算(0-n)整数方法的和
已知:计算整数的和,数据类型已经知道了,但是数据的个数不清楚
*/
public static int add(int...arr){
int sum = 0;
for (int i:arr) {
sum = sum + i;
}
return sum;
}
}
注意事项:
-
一个方法的参数列表,只能是有一个可变参数
public static int add(int...arr,String ...b){} //不行
-
如果方法的参数有多个,那么可变参数必须卸载参数列表的末尾
public static int add(int a,int b,String c,int...arr){} //可变参数在末尾
Collections
常用功能:
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:
-
public static <T> boolean addAll(Collection<T> c, T... elements)
:往集合中添加一些元素。 -
public static void shuffle(List<?> list) 打乱顺序
:打乱集合顺序。package Demo05; import java.util.ArrayList; import java.util.Collections; public class DemoCollections { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); ArrayList<String> list1 = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); Collections.addAll(list1,"a","b","c","d","e"); //添加 System.out.println(list); //[a, b, c, d, e] System.out.println(list1); //[a, b, c, d, e] Collections.shuffle(list); //打乱顺序 System.out.println(list); //[e, b, d, a, c] } }
-
public static <T> void sort(List<T> list)
:将集合中元素按照默认规则(升序)排序。 -
public static <T> void sort(List<T> list,Comparator<? super T> )
:将集合中元素按照指定规则排序。package Demo05; import java.util.ArrayList; import java.util.Collections; /* Comparable排序规则 this-参数(升序) 参数-this(降序) */ public class DemoCollections { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); Collections.addAll(list,2,4,7,5,1); //添加 System.out.println(list); //[2, 4, 7, 5, 1] Collections.sort(list); //默认升序排列 System.out.println(list); //[1, 2, 4, 5, 7] ArrayList<String> list1 = new ArrayList<>(); Collections.addAll(list1,"a","e","b","d","c"); System.out.println(list1); //[a, e, b, d, c] Collections.sort(list1); System.out.println(list1); //[a, b, c, d, e] ArrayList<Person> list2 = new ArrayList<>(); list2.add(new Person(18,"张三")); list2.add(new Person(20,"李四")); list2.add(new Person(16,"王五")); System.out.println(list2); //[Person{age=18, name='张三'}, Person{age=20, name='李四'}, Person{age=16, name='王五'}] Collections.sort(list2); System.out.println(list2); } } package Demo05; public class Person implements Comparable<Person>{ private int age; private String name; public Person() { } public Person(int age, String name) { this.age = age; this.name = name; } @Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int hashCode() { return 1; } @Override public int compareTo(Person o) { return this.getAge()-o.getAge(); } }
简述Comparable和Comparator两个接口的区别。
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。