集合和数组的区别对比
对象数组
数组的定义格式:
数据类型[ ] 数组名 = 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] + " " ) ;
}
}
递归***
定义:
递归在程序中体现为方法调用自身
作用:
解决复杂问题, 复杂问题可以拆解为解决思路相同的小问题, 以极少的代码实现需求
注意事项:
必须要有出口, 否则栈内存溢出( 参考死循环)
小问题解决思路要与大问题相同
代码实现:
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 ) ;
}
}
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 ) ;
System . out. println ( Arrays . toString ( ints) ) ;
int [ ] ints1 = Arrays . copyOf ( arr, 2 ) ;
System . out. println ( Arrays . toString ( ints1) ) ;
对比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) 从集合中移除指定的元素
boolean removeif ( Object o) 根据条件进行删除
void clear ( ) 清空集合
boolean contains ( Object o) 判断集合中是否存在指定的元素
boolean isEmpty ( ) 判断集合是否为空
int size ( ) 集合的长度, 也就是集合中元素的个数
代码实现:
public static void main ( String [ ] args) {
Collection < String > collection = new ArrayList < > ( ) ;
collection. add ( "aaa" ) ;
collection. add ( "bbb" ) ;
collection. add ( "ccc" ) ;
System . out. println ( collection) ;
boolean result1 = collection. remove ( "aaa" ) ;
System . out. println ( result1) ;
boolean result2 = collection. remove ( "ddd" ) ;
System . out. println ( result2) ;
System . out. println ( collection) ;
collection. removeIf (
( String s) -> {
return s. length ( ) == 3 ;
}
) ;
System . out. println ( collection) ;
collection. clear ( ) ;
System . out. println ( collection) ;
boolean result1 = collection. contains ( "a" ) ;
System . out. println ( result1) ;
boolean result2 = collection. contains ( "aaa" ) ;
System . out. println ( result2) ;
boolean empty = collection. isEmpty ( ) ;
System . out. println ( empty) ;
int size = collection. size ( ) ;
System . out. println ( size) ;
}
迭代器
Iterator : 迭代器, 集合的专用遍历方式
Iterator < E > iterator ( ) : 返回集合中的迭代器对象, 该迭代器对象默认指向当前集合的0 索引。
常用方法:
boolean hasNext ( ) : 判断当前位置是否有元素可以被取出
E next ( ) : 获取当前位置的元素 且 将迭代器对象移向下一个索引位置
操作步骤:
1. 获取迭代器对象
2. 循环判断是否有元素, 使用hasNext ( )
3. 循环内取出元素, 使用next ( )
原理分析:
1. Iterator< E > iterator ( ) : 迭代器创建完成, 默认指向0 索引位置
2. boolean hasNext ( ) : 判断当前位置是否有元素
3. E next ( ) : 1. 取出元素 2. 向后移动索引位置
注意事项:
产生原因:
使用迭代器遍历集合的同时使用集合自己的增删方法
解决方案:
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 ( ) ;
while ( iterator. hasNext ( ) ) {
System . out. println ( iterator. next ( ) ) ;
}
}
总结:
iterator ( ) : 创建迭代器对象, 默认指向集合0 索引
hasNext ( ) : 判断当前指向索引有没有元素
next ( ) : 打印当前索引元素, 并将迭代器移动至下一位
普通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) ;
}
迭代器删除:
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) ;
}
增强for循环
简化数组和Collection 集合的遍历
使用前提:
容器的类/ 接口需要和Iterable 有关( 继承/ 实现关系)
特点:
1. JDK5之后出现, 内部原理是一个Iterator 迭代器
2. 实现Iterable 接口的类才可以使用迭代器和增强for
3. 单列集合可以直接使用迭代器和增强for , 双列集合不可以直接使用
格式:
for ( 元素数据类型 变量名 : 数组或者Collection 集合) {
}
快捷键:
容器对象名. for
注意事项:
1. 格式中'变量名' 依次表示集合/ 数组中的每一个元素
2. 增强for 底层是迭代器, 要注意并发修改异常
三种遍历方式的使用场景
1. 普通for :
使用方法需要操作索引
2. 迭代器:
遍历的同时进行删除操作 -- 使用removeIf方法
3. 增强for :
单纯遍历容器
4.f orEach
集合容器名. 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" ) ;
for ( String s : c) {
s = "q" ;
System . out. println ( s) ;
}
System . out. println ( 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" ) ;
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) ;
String s = list. set ( 1 , "ddd" ) ;
System . out. println ( s) ;
System . out. println ( list) ;
System . out. println ( list. get ( 0 ) ) ;
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 ) ) ;
System . out. println ( list1) ;
Collections . shuffle ( list1) ;
System . out. println ( list1) ;
Collections . sort ( list1) ;
System . out. println ( list1) ;
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) ;
list. addLast ( "ccc" ) ;
System . out. println ( list) ;
System . out. println ( list. getFirst ( ) + ":" + list. getLast ( ) ) ;
list. removeFirst ( ) ;
list. removeLast ( ) ;
System . out. println ( list) ;
}
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" ) ;
list. remove ( i) ;
}
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" ) ;
}
System . out. println ( list) ;
}
3. 使用list. contains方法
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" ) ;
}
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 正数: 即将存入的数值大, 存右边
( 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 在书写的时候, 先按照一个方向熟悉一套排序规则, 如果不满足, 再进行修改
进阶展示:
public class Student implements Comparable < Student > {
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 ( ) ;
result = result == 0 ? this . getName ( ) . compareTo ( o. getName ( ) ) : result;
return result;
}
}
public class StudentTest {
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) ;
}
}
}
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 ( ) ) ;
}
}
哈希值 : 是JDK根据对象的地址或者属性值, 运算得出的int 类型的整数
public int hashCode ( ) :
1. 没有重写Object 的hashCode ( ) , 根据对象的地址值计算出哈希值
不同对象调用hashCode ( ) 得到的哈希值不同
2. 重写后, 根据对象的属性值计算哈希值, 和地址值无关, 不同对象的属性值相同, 计算的哈希值是相同的
不同对象属性值不同, 但是计算得到的哈希值可能是相同的/ 或者根据哈希值计算出的存入索引位置相同
此时需要使用重写的equals方法比较对象的属性值进行区分是否为同一个对象( 看具体内容)
JDK7与JDK8底层比较
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. 属性值不同, 新元素添加到数组, 老元素挂在下面, 形成链表
优化:
哈希表: 数组 + 链表 + 红黑树
链表长度足够长, 查询效率变低
当链表长度达到8 转换为红黑树, 将红黑树的根节点存储在数组中
如果转换为红黑树, 此时再次添加元素时, 需要先和根节点进行比较大小
小的存左, 大的存右
此时还是使用 equals ( ) 比较对象的内容
Map集合
1. 概述
interface Map < K , V > :
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) ;
}
} ) ;
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 {
public static void main ( String [ ] args) {
GenericImpl1 < String > genericImpl1 = new GenericImpl1 < > ( ) ;
genericImpl1. method ( "I Love You" ) ;
GenericImpl2 genericImpl2 = new GenericImpl2 ( ) ;
genericImpl2. method ( 555 ) ;
}
}
interface Generic < T > {
void method ( T t) ;
}
class GenericImpl1 < T > implements Generic < T > {
@Override
public void method ( T t) {
System . out. println ( t) ;
}
}
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 {
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) ;
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 ( 叶子节点) , 颜色为黑色
1.4 两个红色节点不能相连
1.5 任意节点到其后代的Nil 节点的简单路径( 一条路走到头, 不能反复) 上均包含相同数目的黑色节点
2. 红黑树添加节点
2.1 默认颜色
默认添加节点的颜色是红色
2.2 添加节点后如何保证红黑规则
添加位置:
根节点 -- 变黑
非根节点 :
看父节点颜色:
黑色: 不操作
红色:
看叔叔节点:
叔叔节点是红色:
1. 将父节点和叔叔节点都变成黑色
2. 将祖父节点变成红色
3. 如果祖父节点是根节点变成黑色
叔叔节点是黑色:
1. 将父节点变成黑色
2. 将祖父节点变成红色
3. 以祖父节点为支点进行旋转
栈
一端开口 : 栈顶
一端闭合 : 栈底
数据进入栈模型的过程称为 : 压/ 进栈
数据退出栈模型的过程称为 : 弹/ 出栈
特点 : 先进后出
队列
一端开口 : 后端
一端开头 : 前端
数据从后端进入队列的过程为 : 入队列
数据从前端离开队列的过程为 : 出队列
特点 : 先进先出
数组
相当于一个容器, 用来存储数据
查询数据通过地址值和索引定位, 查询任意数据耗时相同, 查询速度快
删除数据时, 要将原始数据删除, 同时后面每个数据前移, 删除效率低
添加数据时, 添加位置后的每个数据后移, 在添加元素, 添加效率极低
特点 : 查询快, 增删慢
链表
每个元素称之为 结点 , 每个结点都是独立的对象
存储内容:
1. 结点的地址值
2. 存储的具体数据
3. 下一个结点的地址
特点( 对比数组) : 增删快, 查询慢
从前往后 : 单向链表
双向查询 : 双向链表 ( 查询某个地址元素, 首先判断离头近或尾近, 从最近结点进行查找)