集合和数组的区别对比
对象数组
数组的定义格式:
数据类型[] 数组名 = new 数据类型[数组长度];
数据类型是对象的数据类型,就是对象数组
存储元素:
数组名[索引] = 对象的地址(使用对象名保存)
取出元素:
对象的数据类型 变量名 = 数组名[索引];
此时变量名保存的是当前对象的地址
变量名.成员方法(set/get...)
集合和数组的区别
数组: 长度不可变
集合: 长度可变
ArrayList集合底层是数组,有索引
int[] arr = {1,2,3};
//存储数据类型
数组: 基本数据类型 + 引用数据类型
集合: 引用数据类型 + 基本数据类型的包装类
数组扩容
数组扩容:
数组在内存中是占用一段连续的存储空间,当数组初始化后,数组的长度就会固定不变,
需要增加数组的长度时,由于数组的存储空间附近可能被其他数据存储的空间占用,
所以只能创建一片新的存储空间用来存储数组。
//数组的长度是固定的,如果需要扩充,必须创建新的数组,原数组的元素要复制到新的数组中。
1 定义方法,形参是一个数组,返回值也是一个数组(输入一个数组,返回一个新数组)
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int[] b=new int[arr.length*2];
for(int i=0;i<arr.length;i++){
b[i]=arr[i];
}
System.out.println(Arrays.toString(b));
}
返回结果:
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
2 使用方法System.arraycopy( 数组1 , 起始下标 , 数组2 , 目标位置 , 要拷贝的数据长度)
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int[] b=new int[arr.length*2];
System.arraycopy(arr,0,b,2,arr.length);
System.out.println(Arrays.toString(b));
}
返回结果:
[0, 0, 1, 2, 3, 4, 5, 0, 0, 0]
3 使用方法Arrays.copyof( 数组1 , 新数组长度)
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int[] b = Arrays.copyOf(arr, arr.length * 2);
System.out.println(Arrays.toString(b));
}
数组的高级操作
二分查找法
前提条件:
数组元素要有顺序
思路分析:
1.查找范围索引[min,max],如果要是查询整个数组,范围[0,数组长度-1]
2.查找的结束条件
循环时,当min>max 结束循环,返回-1,表明不存在该元素
当min<=max,继续循环
3.可以查找的情况下(min<=max)
1.计算中间索引位置 mid = (min + max)/2 -- 注意:mid表示的是索引位置不是元素
2.使用要查找的元素和mid索引位置的元素进行比较
1.查找的元素 > mid位置元素 在mid位置的右边下次查找范围[mid+1,max]
2.查找的元素 < mid位置元素 在mid位置的左边下次查找范围[min,mid-1]
3.相等 找到了 返回当前mid值
代码实现:
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int a = 10;
int index = binarySearch(arr, a);
System.out.println("index = " + index);
}
private static int binarySearch(int[] arr, int a) {
int min = 0;
int max = arr.length - 1;
while (min <= max) {
int mid = (min + max) / 2;
if (arr[mid] > a) {
max = mid - 1;
} else if (arr[mid] < a) {
min = mid + 1;
} else {
return mid;
}
}
return -1;
}
冒泡排序
效果:
数值从小到大进行排序
思路分析:
1.相邻元素两两进行比较,大的放在右边,小的放在左边进行交换
2.每一轮确定当前轮次的最大值,放在最右边
3.下一轮比较会少一个元素,少的是上一轮得到的最大值
4.最后一次自己和自己比较,不需要进行的
结论:
n 个数据参与比较,比较 n-1 次
每一轮会得到一个最大值,不参与下一轮的比较
代码实现:
1.外层循环作用:控制交换的轮次,长度为n的数组交换n-1次
2.内层循环作用:获取元素进行交换操作
-1:为了防止索引越界
-i:每一轮少一个最大值参与下一次比较
public static void main(String[] args) {
int[] arr = {3, 2, 1, 6, 5};
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
pringArr(arr);
}
private static void pringArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
递归***
定义:
递归在程序中体现为方法调用自身
作用:
解决复杂问题,复杂问题可以拆解为解决思路相同的小问题,以极少的代码实现需求
注意事项:
必须要有出口,否则栈内存溢出(参考死循环)
小问题解决思路要与大问题相同
代码实现:
//1.求1-100的和
public static void main(String[] args) {
int sum = getSum(100);
System.out.println("sum = " + sum);
}
private static int getSum(int i) {
if (i == 1) {
return 1;
} else {
return i + getSum(i - 1);
}
}
//2.求5的阶乘
public static void main(String[] args) {
int num = getJc(5);
System.out.println("num = " + num);
}
private static int getJc(int i) {
if (i == 1) {
return 1;
} else {
return i * getJc(i - 1);
}
}
快速排序(快排)****
思路分析:
基准数归位的过程
1.基准数的选取
一般使用数组最左侧数字作为基准数
注意:选取基准数之后,该数字不动,从另外一侧开始查找
2.数据的交换
从右侧找比基准数小的,从左侧找比基准数大的
交换
3.基准数归位
效果:
基准数左侧都是比自己小的
右侧都是比自己大的
代码实现:
1.核心代码实现:递归方法第一次执行,第一个基准数归位操作
2.完整代码实现:添加递归及递归出口
public static void main(String[] args) {
int[] arr = {6, 1, 7, 4, 2, 3, 5, 9, 10, 8};
quiteSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void quiteSort(int[] arr, int left, int right) {
System.out.println("基准数归为前的顺序为:" + Arrays.toString(arr));
if (left > right) {
return;
}
int left0 = left;
int right0 = right;
int baseNumber = arr[0];
while (left != right) {
while (arr[right] >= baseNumber && right > left) {
right--;
}
while (arr[left] <= baseNumber && right > left) {
left++;
}
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
//基准数归位
int temp = arr[left];
arr[left] = arr[left0];
arr[left0] = temp;
System.out.println("基准数归为后的顺序为:" + Arrays.toString(arr));
System.out.println("------------------------------");
quiteSort(arr, left0, left - 1);
quiteSort(arr, left + 1, right0);
}
Arrays数组工具类
1.toString(数组名):
思考:如果存储的元素是自定义类对象,想要打印的数组格式中是元素内容,怎么做?
1.数组中存储的是基本数据类型
打印出来的是[数据值, 数据值, ...]
2.数组中存储的是引用数据类型
如果重写Object的toString(),打印[属性值, 属性值, ...]
没有重写,[地址值, 地址值, ...]
2.sort(数组名):
对数组中元素进行快速排序
目前为止只能排序基本数据类型,以及String字符串
3.int binarySearch(数组名,查找的元素):
先sort,再使用
二分查找法
使用前提:数组元素有顺序
返回值:元素存在,返回索引位置
元素不存在,返回[-插入点-1]
插入点:元素如果存在,应该在数组中的哪个索引位置上
-1:如果元素应该出现在0索引位置,返回-0证明出现在0索引,返回的数据是错误的,此时-1变成-1,避免该问题产生
4.copyOf(原始数组名,新数组长度)
int[] arr = {1, 2, 3, 4, 5};
int[] ints = Arrays.copyOf(arr,10);
//底层根据反射原理,利用参数1的数据类型,结合传递的长度,创建新数组,并将元素进行存储
System.out.println(Arrays.toString(ints));
//打印结果:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
int[] ints1 = Arrays.copyOf(arr, 2);
System.out.println(Arrays.toString(ints1));
//打印结果:[1, 2]
对比System.arrayCopy()
System.arrayCopy()
//必须先指定目标数组,必须要求目标数组长度 >= 原始数组
//目标数组必须手动指定,长度过小,会报索引越界异常
总结:
1. Arrays.copyOf( arr , newArr.length )会根据newArr.length从左到右拷贝数据,不会有索引越界异常报告
2. System.arrayCopy()必须手动指定新数组长度,且新数组长度必须 >= 原始数组长度,否则控制台打印索引越界异常
集合
单列集合
集合与数组的区别:
1. 数组长度不可变,集合长度可变。
2. 数组可以存储基本数据类型和引用数据类型。
集合只能存储引用数据类型,若要存储基本数据类型,需要存储对应的包装类。
集合体系结构:
--- Collection 单列集合接口(如: 张三,李四,王五)
-- List 可重复存储接口
-- ArrayList
-- LinkedList
-- Set 不可重复集合接口
-- HashSet
-- TreeSet
--- Map 双列集合接口(如: 张三,上海; 李四,北京; 王五,南京)
-- HashMap
-- TreeMap
Collection
概述:
1.单列集合的顶层接口,表示一组对象,这些对象也成为Collection的元素。
2.JDK不提供此接口的任何直接实现,提供更具体的子接口(如Set和List)实现。
创建Collection集合对象:
1.多态的方式进行创建
2.具体的实现类ArrayList
常用方法:
boolean add(E e) 添加元素,返回布尔类型,可查看是否添加成功
boolean remove(Object o) 从集合中移除指定的元素
/*
底层:
1.传递参数是元素本身,在底层数组中查找元素第一次出现的索引位置
2.使用System.arraycopy方法进行删除
*/
boolean removeif(Object o) 根据条件进行删除
/*
底层:
removeIf方法内部会遍历集合(迭代器),获取每一个元素元素传递给Predicate<? super E> filter重写的test方法 (可以改进为lambda表达式) 重写的test方法的功能:根据传递的元素,进行是否删除的判断方法返回值true:可以删除false:不能删除
*/
void clear() 清空集合
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中元素的个数
/*
底层使用size成员变量
底层:
1.返回集合中存储的有效数据个数
例如:集合扩容长度为10,但是只存储2个元素,返回值就是2
*/
代码实现:
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
// boolean add(E e) 添加元素,返回布尔类型,可查看是否添加成功
collection.add("aaa");
collection.add("bbb");
collection.add("ccc");
System.out.println(collection); //[aaa, bbb, ccc]
// boolean remove(Object o) 从集合中移除指定的元素
//删除成功,返回true
//删除失败,返回false
boolean result1 = collection.remove("aaa");
System.out.println(result1); //true
boolean result2 = collection.remove("ddd");
System.out.println(result2); //false
System.out.println(collection); //[bbb, ccc]
// boolean removeif(Object o) 根据条件进行删除
/*
removeif底层会遍历集合,得到集合中的每一个元素。
s依次表示集合中的每一个元素
返回true,则删除,false则不删
*/
collection.removeIf(
(String s) -> {
return s.length() == 3;
//删除 长度为 3 的 字符串
}
);
System.out.println(collection); //[]
// void clear() 清空集合
collection.clear();
System.out.println(collection); //[]
// boolean contains(Object o) 判断集合中是否存在指定的元素
boolean result1 = collection.contains("a");
System.out.println(result1); //false
boolean result2 = collection.contains("aaa");
System.out.println(result2); //true
// boolean isEmpty() 判断集合是否为空
//为空返回true, 不为空返回false
boolean empty = collection.isEmpty();
System.out.println(empty); //false
// int size() 集合的长度,也就是集合中元素的个数
int size = collection.size();
System.out.println(size); //3
}
迭代器
Iterator : 迭代器,集合的专用遍历方式
Iterator<E> iterator() : 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引。
常用方法:
boolean hasNext() : 判断当前位置是否有元素可以被取出
E next() : 获取当前位置的元素 且 将迭代器对象移向下一个索引位置
//如果没有元素还进行取出操作,会出现异常NoSuchElementException
操作步骤:
1.获取迭代器对象
2.循环判断是否有元素,使用hasNext()
3.循环内取出元素,使用next()
原理分析:
1.Iterator<E> iterator():迭代器创建完成,默认指向0索引位置
2.boolean hasNext():判断当前位置是否有元素
3.E next():1.取出元素 2.向后移动索引位置
注意事项: //并发修改异常ConcurrentModificationException
产生原因:
使用迭代器遍历集合的同时使用集合自己的增删方法
解决方案:
1.直接使用普通for循环
2.使用迭代器自己的方法(删除)
代码练习:
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("a");
c.add("d");
c.add("c");
c.add("d");
c.add("b");
//集合中有特有计数器值进行对集合变化次数进行记录
Iterator<String> iterator = c.iterator();
//迭代器创建时根据原有集合添加元素初始化迭代器中的计数器值
//此计数器值会作为iterator.next()输出依据
//后续更改集合元素会导致集合中计数器数值改变
//集合中计数器值和迭代器中计数器值不相等,会返回ConcurrentModificationException(并发修改异常)异常报错
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
总结:
iterator() : 创建迭代器对象,默认指向集合0索引
hasNext() : 判断当前指向索引有没有元素
next() : 打印当前索引元素,并将迭代器移动至下一位
/*
在循环遍历过程中,不能添加或删除元素,导致计数器值变化,控制台会打印ConcurrentModificationException 异常
*/
//迭代器删除方法
普通for循环:
public static void main(String[] args) {
ArrayList<String> c = new ArrayList();
c.add("a");
c.add("d");
c.add("c");
c.add("d");
c.add("b");
//集合中有特有计数器值进行对集合变化次数进行记录
for (int i = 0; i < c.size(); i++) {
String s = c.get(i);
if ("d".equals(s)){
c.remove(i);
i --;
}
}
System.out.println(c); //[a, c, b]
}
迭代器删除:
public static void main(String[] args) {
ArrayList<String> c = new ArrayList();
c.add("a");
c.add("d");
c.add("c");
c.add("d");
c.add("b");
Iterator<String> iterator = c.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if ("d".equals(s)){
iterator.remove();
}
}
System.out.println(c); //[a, c, b]
}
增强for循环
简化数组和Collection集合的遍历
使用前提:
容器的类/接口需要和Iterable有关(继承/实现关系)
特点:
1.JDK5之后出现,内部原理是一个Iterator迭代器
2.实现Iterable接口的类才可以使用迭代器和增强for
3.单列集合可以直接使用迭代器和增强for,双列集合不可以直接使用
格式:
for(元素数据类型 变量名 : 数组或者Collection集合){
//使用元素
}
快捷键:
容器对象名.for
注意事项:
1.格式中'变量名' 依次表示集合/数组中的每一个元素
//元素是通过增强for底层的迭代器获取的
2.增强for底层是迭代器,要注意并发修改异常
三种遍历方式的使用场景
1.普通for:
使用方法需要操作索引
2.迭代器:
遍历的同时进行删除操作 -- 使用removeIf方法
3.增强for:
单纯遍历容器
4.forEach
集合容器名.forEach(new Consumer(){
...
})
使用forEach遍历集合同时,不能使用集合自己的增删方法,否则会抛出并发修改异常
范例:
public static void main(String[] args) {
ArrayList<String> c = new ArrayList();
c.add("a");
c.add("b");
c.add("c");
c.add("d");
c.add("e");
for (String s : c) {
System.out.println(s);
}
}
总结:
1.数据类型一定是集合或者数组中元素的类型
2.s 仅仅是变量名,在循环的过程中,依次表示集合或者数组中的每一个元素
3.c 就是便利的集合或者数组
代码展示:
public static void main(String[] args) {
ArrayList<String> c = new ArrayList();
c.add("a");
c.add("b");
c.add("c");
//底层通过迭代器选择索引,默认 0 索引
for (String s : c) {
s = "q";
System.out.println(s); //q q q
}
System.out.println(c); //[a, b, c]
}
注意事项:
1.修改第三方变量值,不会影响原集合中元素
使用场景:
1.需要操作索引,使用普通 for 循环
2.在遍历的过程中需要删除元素,使用迭代器
3.仅仅需要遍历,使用增强 for
List集合
概述:
1.有序集合,有序指的是存取顺序
2.用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
3.允许重复的元素
list使用方法:
void add(int index,E element) : 在指定位置添加元素
E remove(int index) : 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) : 修改指定索引处的元素,返回被修改的元素
E get(int index) : 返回指定索引处的元素
补充方法:
int lastIndexOf (Object o):获取指定元素在集合中最后一次出现的索引位置,存在返回索引,不存在返回-1
static <E> List<E> of (E... elements):使用同种数据类型元素构建List集合
List<E> subList (int fromIndex, int toIndex):截取[fromIndex,toIndex)的元素,返回新集合
<T> T[] toArray (T[] a):将集合转换为存储相同数据类型的数组
代码展示:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//在bbb之前添加ddd元素
list.add(1, "ddd");
System.out.println(list);
//删除最后一个元素
String remove = list.remove(2);
System.out.println(remove);
System.out.println(list);
//删除多个元素
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("bbb");
list.add("bbb");
list.add("ccc");
list.removeIf(s -> "bbb".equals(s));
System.out.println(list);
//更改bbb为ddd
String s = list.set(1, "ddd");
System.out.println(s);
System.out.println(list);
//获取第一个位置的元素
System.out.println(list.get(0));
//获取集合的前两个元素(括号中索引左闭右开) (起始元素索引,目标元素索引+1)
List<String> s = list.subList(0, 2);
System.out.println(s);
System.out.println(list);
}
特有方法***(实用)
List list1 = new ArrayList<>(List.of(1,2,3,4,5,6,7,8));
//List.of : 批量添加元素(JDK12可用,8不可用)
System.out.println(list1);
//[1, 2, 3, 4, 5, 6, 7, 8]
//将集合乱序
Collections.shuffle(list1);
System.out.println(list1);
//[5, 4, 3, 8, 6, 7, 2, 1]
//集合排序
Collections.sort(list1);
System.out.println(list1);
//[1, 2, 3, 4, 5, 6, 7, 8]
LinkedList
与ArrayList区别:
1. ArrayList : 底层数据结构是数组,查询快,增删慢
2. LinkedList : 底层数据结构是链表,查询慢,增删快
底层:
是双向链表 -- 火车车厢
是List接口的实现类,可以使用List接口中的方法(在LinkedList内部重写的)
所有操作索引相关的方法都可以进行使用
但是:索引只是表示当前节点对象在链表中位置,查找还是要从头/尾进行查找
LinkedList 核心成员变量:
Node<E> first:用来记录链表第一个Node对象的地址
Node<E> last:用来记录链表最后一个Node对象的地址
内部类Node<E>: -- 真正存储数据的节点对象
封装三个属性:
1.前一个节点地址
2.元素值
3.后一个节点地址
代码展示:
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("--------------------------");
Iterator<String> it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println("--------------------------");
for (String s : list) {
System.out.println(s);
}
}
使用方法:
头结点记录下一个节点地址
尾结点记录上一个节点地址
void addFirst(E e) : 在该列表开头插入指定的元素
void addLast(E e) : 将指定的元素追加到此列表的末尾
E getFirst() : 返回此列表中的第一个元素
E getLast() : 返回此列表中的最后一个元素
E removeFirst() : 从此列表中删除并返回第一个元素
E removeLast() : 从此列表中删除并返回最后一个元素
代码展示:
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.addFirst("ddd");
System.out.println(list);
//[ddd, aaa, bbb, ccc, ddd]
list.addLast("ccc");
System.out.println(list);
//[ddd, aaa, bbb, ccc, ddd, ccc]
System.out.println(list.getFirst() + ":" + list.getLast());
//ddd:ccc
list.removeFirst();
list.removeLast();
System.out.println(list);
//[aaa, bbb, ccc, ddd]
}
ArrayList集合
ArrayList构造方法和添加方法
构造方法
ArrayList()
集合的泛型
<引用数据类型> 表示限定集合存储的数据类型
<>中只能是对象类型或者基本数据类型的包装类
基本数据类型的包装类:
例如 int ---> Integer
添加元素方法
方法1: 对象名.add(元素)
方法2: 对象名.add(索引,元素)
方法2是指插入索引位置,若不存在,索引越界异常(比如,一个数据都没有,直接插入1索引,会报错)
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(121);
list.add(0,122);
System.out.println(list);
}
直接打印集合对象名,有自己的打印格式[元素1,元素2,......]
ArrayList常用方法
容器基本操作: 增删改查
删除方法:
1.remove(元素) 存在返回true 不存在返回false
2.remove(索引) 索引必须存在,不存在报错索引越界异常
修改方法:
set(索引,元素) 使用该元素替换指定索引位置的元素
获取方法:
get(索引)
size() 获取集合元素个数
数组长度:数组名.length 是属性
字符串长度:对象名.length() 是方法
集合元素个数:对象名.size() 是方法
集合存储字符串并遍历
1.创建集合对象
ArrayList<String> list = new ArrayList<>()
2.添加元素
list.add(元素)
3.遍历
list.fori
4.获取元素
get(索引)
举例:
public static void main(String[] args) {
Random random = new Random();
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int a = random.nextInt(11);
list.add(a);
}
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
}
集合删除元素
1.正向遍历会存在缺少或漏掉等情况
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("a");
list.add("c");
list.add("a");
list.add("a");
for (int i = 0; i < list.size(); i++) {
list.remove("a"); //[b, c, a]
list.remove(i); //[a, a, a]
}
System.out.println(list);
}
2.反向遍历
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("a");
list.add("c");
list.add("a");
list.add("a");
for (int i = list.size() - 1; i >= 0; i--) {
list.remove("a"); // [b, c]
}
System.out.println(list);
}
3.使用list.contains方法 //如果此列表中包含指定的元素,则返回 true。(将此作为while循环判断条件,从而判定,只要我的list中存在"a",那就一直返回true,循环一直进行)
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("a");
list.add("c");
list.add("a");
list.add("a");
while (list.contains("a")){
list.remove("a"); //[b, c]
}
System.out.println(list);
}
Set集合
特点:
不可重复(去重)
存取顺序不一致
没有带索引的方法,不能通过索引获取元素
代码展示:
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("aaa");
set.add("bbb");
set.add("ccc");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println("------------------------");
for (String s : set) {
System.out.println(s);
}
}
TreeSet
不用于Set特点 : 可以将内部元素按照/*规则*/进行排序
底层:
红黑树
代码展示:
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<>();
ts.add(5);
ts.add(2);
ts.add(3);
ts.add(1);
ts.add(2);s
System.out.println(ts);
}
基本使用:
根据[指定的排序规则]完成去重和排序
Integer类和String类内部带有自己的排序规则,所以可以直接进行排序操作
Integer:按照自然排序规则 -- 数字从小到大排序
String:自然排序规则 -- 按照字符的字典顺序(比较每一个字符值,从小到大)
自定义类对象:自己制定排序规则
两种比较方式:
1.自然排序 :
1.1自定义类实现 Comparable<T> 接口,T就是自定义对象的数据类型
1.2重写int compareTo(Object o)
1.3根据返回值进行排序
返回值三种情况:
1.3.1 负数:即将存入的数值小,存左边
1.3.2 0 : 重复,不存(去重)
1.3.3 正数:即将存入的数值大,存右边
//注意:实现Comparable<T>接口的类,如何更改排序规则?
(1)重新书写compareTo();改写内部的排序规则(只针对于自定义类有效),不能改写 Integer , String 这些 Java 提供好的类(底层源代码是不可改变的)
(2)使用比较器排序
2.比较器排序 :
创建 TreeSet 对象的时候传递 Comparator 的实现类对象,重写 compare 方法,根据返回值进行排序
使用步骤:
1.使用带参数构造方法创建TreeSet集合对象
2.在构造方法内传递Comparator<要排序的数据类型>
3.重写compare方法,方法内有两个参数
o1和o2
o1表示即将存入的数据
o2表示集合中存在的
4.比较元素属性值
返回值三种情况:
负数:即将存入的小,存左边
0:重复,不存
正数:即将存入的大,存右边
//使用中,默认使用自然排序,当自然排序不满足现状时,使用比较器排序
关于返回值规则:
1.如果返回值为负数,表示当前存入的元素是较小值,存左边
2.如果返回值为0,表示当前存入的元素跟 集合中元素重复,不存
3.如果返回值为正数,表示当前存入的元素是较大值,存右边
3.两种排序方式对比及使用建议
1.自然排序:
1.1使用空参构造创建TreeSet集合对象
1.2类实现Comparable<T>接口,重写compareTo方法
2.比较器排序:
2.1使用带参构造创建TreeSet集合对象
2.2在构造方法内传递Comparator<T>比较器,重写compare方法
3.compareTo()和compare()方法的返回值
负数:即将存入的小,存左边
0:重复,不存
正数:即将存入的大,存右边
4.使用建议
4.1如果自然排序的规则不满足条件,使用比较器排序去覆盖自然排序规则
4.2在书写的时候,先按照一个方向熟悉一套排序规则,如果不满足,再进行修改
进阶展示:
//Student:
public class Student implements Comparable<Student> {
/*
2022/01/10
14:44
I am Iron man
*/
private String name;
private Integer age;
public Student() {
}
public Student(String name, Integer 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(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
int result = this.getAge() - o.getAge();
//使用Age元素进行排序
result = result == 0 ? this.getName().compareTo(o.getName()) : result;
//Age元素相等时,使用Name元素进行先后顺序排序
return result;
}
}
//StudentTest:
public class StudentTest {
/*
2022/01/10
14:45
I am Iron man
*/
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
ts.add(new Student("zhangsan", 25));
ts.add(new Student("lisi", 25));
ts.add(new Student("wangwu", 25));
ts.add(new Student("zhangsan", 27));
for (Student t : ts) {
System.out.println(t);
}
}
}
/*
Student{name='lisi', age=25}
Student{name='wangwu', age=25}
Student{name='zhangsan', age=25}
Student{name='zhangsan', age=27}
*/
HashSet*******
特点:
1.底层数据结构是哈希表
2.不能保证存储和取出的顺序完全一致
3.没有带索引的方法,所以不能使用普通for循环遍历
4.由于是Set集合,所以元素唯一
代码展示:
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("java");
hs.add("java");
Iterator<String> it = hs.iterator();
while (it.hasNext()){
System.out.println(it.next());
//world java hello
}
}
哈希值 : 是JDK根据对象的地址或者属性值,运算得出的int类型的整数
public int hashCode() :
1.没有重写Object的hashCode(),根据对象的地址值计算出哈希值
不同对象调用hashCode()得到的哈希值不同
2.重写后,根据对象的属性值计算哈希值,和地址值无关,不同对象的属性值相同,计算的哈希值是相同的
//哈希冲突问题
不同对象属性值不同,但是计算得到的哈希值可能是相同的/或者根据哈希值计算出的存入索引位置相同
此时需要使用重写的equals方法比较对象的属性值进行区分是否为同一个对象(看具体内容)
JDK7与JDK8底层比较
//JDK7底层原理分析
1.数据结构:数组+链表(组成哈希表)
2.创建对象:默认长度为16,加载因子(扩容因子)为0.75,当元素个数达到16*0.75--12个时扩容为原来的2倍
3.首次添加元素:
根据对象的哈希值和数组长度计算存入的索引位置,直接添加
索引位置: 哈希值%数组长度,导致存储不是连续的
4.再次添加:
根据哈希值计算存入的索引位置,判断当前位置是否为null
为null,直接存储
不为null:
1.哈希冲突 -- 不同对象属性值也不同,此时计算得到的哈希值相同的,存储的索引位置相同的
2.计算的索引位置相同 -- 不同对象属性值不同,计算得到的哈希值不同,计算得到的索引位置相同
数组长度为16时 %16 17 -- 余数为 1 -- 索引位置相同
数组长度为32时 %32 321 -- 余数为 1 -- 索引位置相同
证明有元素,需要通过equals方法比较属性值
1.属性值相同,不存
2.属性值不同,新元素添加到数组,老元素挂在下面,形成链表
//JDK8底层优化
优化:
哈希表:数组 + 链表 + 红黑树
链表长度足够长,查询效率变低
当链表长度达到8转换为红黑树,将红黑树的根节点存储在数组中
如果转换为红黑树,此时再次添加元素时,需要先和根节点进行比较大小
小的存左,大的存右
此时还是使用 equals() 比较对象的内容
Map集合
1.概述
interface Map<K,V> :
K : 键的数据类型
V : 值的数据类型
//键是不能重复的,值可以重复,并且一个键只能对应一个值
//在Map的内部有一个类,将K和V封装成Entry<K,V>的对象(键值对数据对象)
2.常用方法
V put(K key,V value):添加元素,返回(被添加)的元素
1.如果键不存在,直接添加键值对,返回的是默认初始化值null
2.如果键存在,此时覆盖原来的值(修改),返回是修改之前的值
V remove(Object key):删除元素,返回被删除的值
删除键(删除Entry对象)
clear():清空集合
boolean containsKey(Object key):判断是否包含键
理解:精确查询(键不重复的)
boolean containsValue(Object value):判断是否包含值
理解:模糊查询(值可以是重复的)
boolean isEmpty():判断集合是否为空
int size():获取集合键值对(Entry对象)的个数
3.Map集合的遍历
1.Map接口没有实现Iterable,不能使用迭代器/增强for
2.没有索引,也不能使用普通for
需要进行转换 Map - Collection:
第一种:
获取所有的键的集合 map集合对象名.keySet(),返回Set<K>集合,使用迭代器/增强for遍历Set集合获取每一个键,map集合对象名.get(K k),返回的是键对应的值
第二种:
获取所有的键值对对象Entry对象的Set集合,Set<Map.Entry<K,V>> entrySet(),遍历集合,获取每一个Entry对象,通过对象调用getKey()/getValue()获取键/值
第三种 forEach():
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
System.out.println(key+"---"+value);
}
});
//可转为Lambda表达式:
/*
map.forEach(key,value -> System.out.println(key+"---"+value));
*/
HashMap集合
HashSet底层使用的是HashMap
将HashSet的元素当做HashMap的键进行添加并实现去重
依赖重写hashCode和equals方法
原理解析
将数据封装为Entry对象(键和值)
根据键计算哈希值,结合数组长度计算索引位置
为null,直接添加
不为null
equals方法比较[键]的属性值
相同:覆盖旧值
不同:新的Entry对象添加到数组,老的挂在下面形成链表
JDK8版本开始,链表长度达到8,转换为红黑树
使用建议:
如果有必要使用自定义类对象作为键,一定要重写hashCode和equals方法
建议一般使用Integer/String作为键,去重性能更好
TreeMap集合
TreeSet集合底层就是TreeMap
TreeSet集合添加元素的时候,使用的本质上是TreeMap的put方法
将元素作为Map集合的键进行添加
底层也是红黑树,排序要制定排序规则
原理解析
底层是红黑树
对键进行排序和去重,依赖自然排序规则/比较器排序规则
注意:
如果有必要使用自定义类对象作为键,要给类制定自然排序规则或者是创建TreeMap时传递比较器规则
泛型
<E> 称为 泛型, E 表示 数据类型 , 也可写为 T V K Q
提供了编译时类型安全检测机制
优点:
1.将运行时期的问题提前到编译时期
2.避免强制类型转换
使用范围:
1.类名后 --- 泛型类 如: ArrayList<E>
-- 定义格式 : 修饰符 class 类名<类型>{}
2.方法声明 --- 泛型方法
-- 定义格式 : 修饰符 <类型> 返回值类型 方法名(类型 变量名){}
3.接口名后 --- 泛型接口
-- 定义格式 : 修饰符 interface 接口名 <类型> {}
-- 使用方式 : 1.实现类不给泛型,如:ArrayList<E>只实现List<E>
2.实现类确定具体的数据类型,如:普通类
3.接口继承接口,指定父接口泛型的具体类型
4.接口继承接口,不指定父接口泛型具体类型,此时子接口需要定义父接口泛型类型
代码展示:
public class Demo05 {
/*
2022/01/10
10:44
I am Iron man
*/
public static void main(String[] args) {
GenericImpl1<String> genericImpl1 = new GenericImpl1<>();
genericImpl1.method("I Love You");
GenericImpl2 genericImpl2 = new GenericImpl2();
genericImpl2.method(555);
}
}
/**
* 创建一个泛型接口 Generic
* 泛型方法 void method(T t)
* @param <T>
*/
interface Generic<T>{
void method(T t);
}
/**
* 泛型实现类1 GenericImpl1 继续使用泛型
*/
class GenericImpl1<T> implements Generic<T> {
@Override
public void method(T t) {
System.out.println(t);
}
}
/**
* 泛型实现类2 GenericImpl2 是定类型Integer
*/
class GenericImpl2 implements Generic<Integer>{
@Override
public void method(Integer integer) {
System.out.println(integer);
}
}
类型通配符
类型通配符: <?>
-- ArrayList<?> : 表示元素类型位置的ArrayLis,它的元素可以匹配任何的类型
-- 不能把元素添加至ArrayList中,获取出来的也是父类类型
好处: 简化泛型使用
弊端: 例如集合使用<?>
---不能添加,只能获取,并且获取得到的还是Object
类型通配符上限: <? extends 类型>
-- ArrayList<? extends Number> :
上限限定: 当前类型及其子类类型
表示: Number 或者其子类
类型通配符下限: <? super 类型> :
下限限定: 当前类型及其父类类型
表示: 当前类型及其父类类型
代码实现:
public class Demo06 {
/*
2022/01/10
11:13
I am Iron man
*/
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
List<Number> list2 = new ArrayList<>();
List<Object> list3 = new ArrayList<>();
method1(list1);
method1(list2);
method1(list3);
method2(list1);
method2(list2);
//method2(list3);
//method3(list1);
method3(list2);
method3(list3);
}
public static void method1(List<?> list) {
System.out.println(list);
}
public static void method2(List<? extends Number> list) {
System.out.println(list);
}
public static void method3(List<? super Number> list) {
System.out.println(list);
}
}
数据结构
数据结构是计算机存储、组织数据的方式.是指相互之前存在一种或多种特定关系的数据元素的集合。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
常见的数据结构:
1.栈
2.队列
3.数组
4.链表
树结构
树结构是由节点构成
每一个节点中维系变量:
1.父节点地址
2.值
3.左子节点地址
4.右子节点地址
1.二叉树
1.1特点:
任意节点的子节点数量(度)不超过2
根节点:树结构最顶层的节点
树高:从根节点开始到最远的子节点的层高
注意:此时元素没有顺序(没有排序规则),只要满足二叉树的规则即可,此时元素查询效率就很低
2.二叉查找树
2.1特点:
2.1.1是二叉树
2.1.2添加规则:
任意节点左子节点值比自己小,右子节点的值比自己大
2.2添加节点:
2.2.1首次添加的元素作为根节点
2.2.2再次添加的元素会先和根节点比较大小
小,存在左面
大,存在右面
相同,不存
2.2.3存在多个节点的情况下,依次使用添加的元素和多个节点进行比较
小,存在左面
大,存在右面
相同,不存
2.3注意:
如果元素有从小到大的顺序
1 2 3 4 5 6 7 8 9
得到而二叉查找树数据都在一侧排列,树是畸形树(不平衡),影响查询效率
3.平衡二叉树
3.1特点:
3.1.1是二叉查找树(任意节点左侧都比自己小,右侧都比自己大)
3.1.2新增规则:
任意节点左右子树高度差<=1
3.2左旋
3.2.1触发机制
根节点的右子树添加节点打破平衡(右边比左边多)
3.2.2如何左旋
(1)原根节点降级成为左子节点
(2)原根节点的右子节点提升为新的根节点
(3)原右子节点的左子节点出让给降级的根节点当做右子节点
(根节点调节完毕之后,多出来的部分放在合适的位置上)
3.3右旋
3.3.1触发机制
根节点的左子树添加节点打破平衡(左边比右边多)
3.3.2如何右旋
(1)原根节点降级成为右子节点
(2)原根节点的左子节点提升为新的根节点
(3)原左子节点的右子节点出让给降级的根节点当做左子节点
3.4小结
整体旋转:
添加节点打破平衡
左侧多向右旋转,右侧多向左旋转
4.左左和左右
4.1触发机制
左左:
左子树的左子树添加节点打破平衡
左右:
左子树的右子树添加节点打破平衡
4.2如何旋转
左左:
直接右旋
左右:
先局部左旋(恢复到左左的情况),再整体右旋
5.右右和右左
5.1触发机制
右右:
右子树的右子树添加节点打破平衡
右左:
右子树的左子树添加节点打破平衡
5.2如何旋转
右右:
直接左旋
右左:
先局部右旋(恢复到右右的情况),再整体左旋
红黑树**
1.红黑规则
1.1任意节点的颜色是红色/黑色
1.2根节点是黑色
1.3一个节点没有左子节点/右子节点/父节点,记录为Nil(叶子节点),颜色为黑色
//没有记录地址就记录黑色叶子Nil
1.4两个红色节点不能相连
1.5任意节点到其后代的Nil节点的简单路径(一条路走到头,不能反复)上均包含相同数目的黑色节点
2.红黑树添加节点
2.1 默认颜色
默认添加节点的颜色是红色
2.2 添加节点后如何保证红黑规则
添加位置:
根节点 -- 变黑
非根节点 :
看父节点颜色:
黑色: 不操作
红色:
看叔叔节点:
叔叔节点是红色:
1.将父节点和叔叔节点都变成黑色
2.将祖父节点变成红色
3.如果祖父节点是根节点变成黑色
叔叔节点是黑色:
1.将父节点变成黑色
2.将祖父节点变成红色
3.以祖父节点为支点进行旋转
栈
一端开口 : 栈顶
一端闭合 : 栈底
数据进入栈模型的过程称为 : 压/进栈
数据退出栈模型的过程称为 : 弹/出栈
//先入栈的元素为栈底元素,后入栈的元素为栈顶元素
特点 : 先进后出
队列
一端开口 : 后端
一端开头 : 前端
数据从后端进入队列的过程为 : 入队列
数据从前端离开队列的过程为 : 出队列
特点 : 先进先出
数组
相当于一个容器,用来存储数据
查询数据通过地址值和索引定位,查询任意数据耗时相同,查询速度快
删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
添加数据时,添加位置后的每个数据后移,在添加元素,添加效率极低
特点 : 查询快,增删慢
链表
每个元素称之为 结点 , 每个结点都是独立的对象
存储内容:
1.结点的地址值
2.存储的具体数据
3.下一个结点的地址
特点(对比数组) : 增删快,查询慢
从前往后 : 单向链表
双向查询 : 双向链表 (查询某个地址元素,首先判断离头近或尾近,从最近结点进行查找)