JavaSE进阶
2020-11-10 09:44:55 0 举报
AI智能生成
javase基础笔记
作者其他创作
大纲/内容
关键字
package
定义包
import
导包
class
定义类
获取Class对象
new
void
基本数据类型
byte
short
char
int
long
float
double
boolean
null
return
流程控制
if
else
while
do
for
break
continue
布尔常量
true
false
static
修饰符
静态
被修饰的属于类 不属于对象被所有对象共享使用
静态内容优先于对象存在
修饰内容
成员变量
变量值属于类 可以被所有对象共享
成员方法
方法中不能使用this super
构造代码块
类加载的时候执行 只执行一次
extends
class 子类 extends 父类{...}
this
当前对象引用
作用
区分局部变量和成员变量
this.variable
成员方法中调用本类其他成员方法
this.method()
在本类构造方法第一行调用本类其他构造方法
this()
super
父类对象引用
作用
区分子类成员变量与父类成员变量
子类成员方法中调用父类成员方法
super.method()
在本类构造方法第一行调用父类构造方法
super()
abstract
修饰符
声明抽象
public abstract class 类名{ public abstract 返回值类型 方法名(形参列表);}
final
修饰符
最终
作用
修饰类
不能被继承
修饰方法
不能被重写
修饰变量
基本数据类型 值不能修改引用数据类型 地址不能修改
权限修饰符
public
所有都可以访问
protected
同一个包以及不同包的子类
默认
同一个包中
private
同一个类中
interface
声明接口
public interface 接口名{ 接口成员}
implements
实现接口
public class 类名 implements 接口名{...}
default
接口中声明默认方法
instanceof
检查给定的对象是否是一个特定的类或接口的实例
if(对象名 instanceof 类名){...}
异常相关
throws
throws
try
catch
finally
transient
修饰的成员不会被序列化
enum
设计模式
模板模式 Template
定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中
用法
单例模式 Singleton
作用
保证一个类能且仅能创建一个对象
使用场景
可用于共享数据
实现方式
程序员式
1.私有化构造2.维护一个public final static修饰的本类对象3.通过类名.对象名获取对象
饿汉式
1.私有化构造2.维护一个private static修饰的本类对象3.提供一个public static getInstance()静态方法返回本类对象4.通过类名.静态方法获取本类对象
懒汉式
1.私有化构造2.维护一个private static修饰的本类引用(声明不赋值,在方法中赋值)3.提供一个public static getInstance()静态方法返回本类对象, 方法体中判断对象是否为null,为null则创建对象,否则返回已有对象4.通过类名.静态方法获取本类对象
可能有线程安全问题,需要加锁
数据结构
栈
先进后出
队列
先进先出
数组
内存中顺序存储
链表
节点链接节点
树
二叉树
专业词语
节点
parent
left
right
data
根节点
左右子树
度
树高
度为2的树
二叉查找树
1.二叉树2.左子树的值比自身小3.右子树的值比自身大
平衡二叉树
1.二叉查找树2.左右子树高度差不超过13.任意节点左右子树都是平衡二叉树
旋转
添加一个节点时,出现不平衡,触发旋转
左旋
根节点的右侧往左拉原先的右子节点变成新的父节点并把多余的左子节点出让给已经降级的根节点当右子节点
右旋
根节点的左侧往右拉左子节点变成了新的父节点并把多余的右子节点出让给已经降级根节点当左子节点
左左
根节点左子树的左子树有节点插入
直接对整体进行右旋
左右
当根节点左子树的右子树有节点插入
左旋子树 后整体右旋
右右
当根节点右子树的右子树有节点插入
直接对整体进行左旋
右左
当根节点右子树的左子树有节点插入
右旋子树 后整体左旋
红黑树
Entry
parent
left
right
color
key&value
通过红黑规则达到平衡(非高度平衡)的二叉查找树
红黑规则
只有红色和黑色的节点 根节点始终为黑
叶子节点都为黑
叶子节点(节点不存在子节点或者为空节点被称作叶子节点)
不能红红相连
每一个节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点
添加节点
被添加的节点颜色为红
根节点
直接变黑
非根节点
父节点为黑
直接插入
父节点为红
叔叔节点为红
1.父变黑2.叔变黑3.祖父变红(根节点为黑)
叔叔节点为黑
1.父变黑2.祖父变红3.绕祖父旋转(根节点为黑)
杂
::运算符
引用方法
格式
<Class name>::<method name>
例子
stream.forEach( s-> System.out.println(s));//lambda写法stream.forEach( System.out::println); //::运算符写法
可以引用的方法
静态方法
例子
成员方法
构造函数
面向对象
包
作用
管理class
关键字
package
import
分层思想
三层架构
Controller
与用户交互
Service
业务控制
Dao
数据存取
辅助包
utils
第三方工具类
domain
模型类
entry
程序入口
继承
概念
类与类之间存在包含(is a)关系时 可以用继承描述这种关系
好处和弊端
继承使得所有子类的公共部分都放在了父类,使得代码得到了共享避免了重复,提升代码的复用性和维护性,同时也为多态提供了前提.
继承中,父类变化,子类不得不变.继承会破坏包装,父类的实现细节暴露给了子类降低了独立性,提升了耦合性.继承是一种类与类之间强耦合的关系.
关键字
extends
this
super
注意事项
类与类之间只能单继承
类的继承具有传递性
所有的类都直接或间接的继承了Object类
继承的时候,应该符合 is-a 的关系
子类加载的时候,会优先加载父类
构造方法特点
构造方法不能被继承
创建子类对象时默认一定会先创建父类对象
子类构造方法第一行默认调用父类空参构造
每个构造方法只能在第一行调用一次其他构造方法
方法重写
在子类中编写了与父类方法声明一模一样的方法包括返回值 方法名 参数列表
重写可以在不改变父类的方法声明前提下 对父类方法扩展增强
注意事项
子类不能重写父类的私有方法 静态方法 构造方法
子类重写的方法权限必须不小于父类方法权限
抽象类
包含抽象方法的类
对类的抽象
关键字
abstract
作用
强制子类重写抽象方法
注意事项
抽象类不能直接创建对象
子类要么重写全部抽象方法 要么继续抽象
可以有构造方法
抽象类不一定有抽象方法有抽象方法的类一定是抽象类
接口
行为的抽象
作用
制定规则
提升程序扩展性
关键字
interface
implements
接口成员
JDK7以前
常量
public static final 类型 常量名
默认添加 "public static final"
抽象方法
默认添加 "public abstract"
JDK8新增
静态方法
可以通过接口名直接调用
默认方法
通过实现类对象调用
如果有多个接口有相同的默认方法 则实现类必须重写该方法
关键字
default
JDK9新增
私有方法
为静态方法和默认方法服务 用private修饰 外界无法调用
注意事项
接口不能直接创建对象
接口没有构造方法
接口与接口之间可以多继承
一个类可以同时实现多个接口
实现类必须重写所有方法 否则只能是抽象类
代码块
用"{}"括起来的代码
局部代码块
方法中定义
限定变量生命周期
构造代码块
类中方法外定义
每次构造方法执行时 都会在构造方法第一行之后执行
可以将多个构造方法中相同的代码抽取到构造代码块中
静态代码块
类中方法外 用static修饰
只在类加载的时候执行一次
在类加载时做一些初始化操作
多态
一个对象 多种形态
好处与弊端
提升代码扩展性
不能使用子类特有内容
前提
有继承或接口实现
格式
父类引用指向子类对象使用父类类型变量 保存子类类型对象
成员访问特点
成员变量
编译和运行都看父类
成员方法
编译看父类 运行看子类
转型
向上转型
子转父
向下转型
为了使用子类特有的内容
使用强制类型转换
异常
ClassCastException
实际类型与目标类型不一致时
解决方法
使用instanceof 关键字判断 然后再转
if(对象名 instanceof 类名){ 类名 新对象名 = (类名)对象名;}
注意事项
多态转型只影响形态 本质(地址)没有变化
推荐只在方法形参和返回值类型使用多态
内部类
在类的内部定义的另一个类
成员内部类
成员变量位置
局部内部类
局部变量位置
匿名内部类
格式
new 父类或接口(){ 重写父类或接口的方法;};
lambda
概述
JDK8新增的一种语法
允许我们在调用带有接口类型的形参的方法时,使用lambda作为方法的实参使用
本质上是一个没有名字的方法(匿名方法)
前提
接口类型
接口中有且仅有一个抽象方法
语法
(参数列表)->{方法体;return 返回值;}
省略规则
形参的数据类型可以省略
当形参只有一个时 小括号可以省略
当语句只有一条时 分号 return 大括号都可以省略
lambda与匿名内部类关系
匿名内部类可以是接口 抽象类 具体类lambda只能是有且仅有一个抽象方法的接口
可以用lambda的地方一定可以用匿名内部类可以用匿名内部类的地方不一定能用lambda
lambda编译后不会产生.class文件 运行时动态生成字节码匿名内部类编译后会产生.class文件
泛型
泛型就是参数化类型,也可称为类型参数
格式
<类型>
类型用字母表示
常用字母
E:元素(Element),多用于java集合框架K:关键字(Key)N:数字(Number)T:类型(Type)V:值(Value)
好处
提供了编译时类型安全检测机制,把运行时期的问题提前到了编译期间
避免了强制类型转换
泛型类
如果一个类的后面有<类型>,表示这个类是一个泛型类
创建泛型类对象时,必须要给这个泛型确定具体的数据类型
格式
public class Class<E> {}
修饰符 class 类名<类型> { }
泛型方法
方法声明中有泛型的方法,就是一个泛型方法
格式
public <T> T[] method(T[] a, T b, T c){}
修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
泛型接口
如果一个接口的后面有<类型>,表示这个接口是一个泛型接口
格式
修饰符 interface 接口名<类型> { }
public interface Interface<E> {}
泛型通配符
格式
<?>
<? extends 类型>
是"类型"的或者其子类型
<? super 类型>
是"类型"的或者其父类型
多态+泛型
泛型尖括号里的类型不支持直接用多态
ArrayList<Animal> alist = new ArrayList<Dog>();//该代码不通过编译
API
常用API
Math
基本数字运算
常用方法
public static int abs(int a)
返回绝对值
舍入相关
public static double ceil(double a)
返回进一法舍入结果
public static double floor(double a)
返回去尾法舍入结果
public static int round(float a)
返回四舍五入结果
对于负数依旧"四舍五入"即-1.6=>-2,-1.5=>-1
最大最小值
public static int max(int a, int b)
public static int min(int a, int b)
public static double pow(double a, double b)
求幂
public static double random()
返回[0.0, 1.0)之间的随机值
System
几个有用的字段和方法
常用方法
public static void exit(int status)
终止当前运行的虚拟机 非零表示异常终止
public static native long currentTimeMillis()
返回当前系统时间毫秒值
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
拷贝数组
Object
所有类的基类
常用方法
比较 建议重写
public boolean equals(Object obj) { return (this == obj); }
转字符串 建议重写
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
Objects
对象工具类
常用方法
null值判断
public static boolean isNull(Object obj)
public static boolean nonNull(Object obj)
返回参数中对象的字符串形式
public static String toString(Object o)
public static String toString(Object o, String nullDefault)
BigDecimal
精确计算
对象创建
构造方法建议使用字符串
public BigDecimal(String val)
public static BigDecimal valueOf(double val)
常用方法
四则运算
加 减 乘
public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
除
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
如果无法计算出精确结果则抛出ArithmeticException因此建议使用上述参数的除法并指定小数位数(scale)
字符串相关
String
内容不可改变
字符串无法打印地址
构造方法
public String()
创建一个空白字符串对象
public String(char value[])
根据char数组内容创建String对象
public String(String original)
根据传入的字符串内容创建String对象
String s = "abc";
直接赋值创建字符串对象
细节问题
相同的字符串只存储一个在常量池中
直接赋值创建的String对象引用常量池内字符串的地址
new创建的String对象引用堆里的地址
直接赋值创建时 常量与常量拼接时存在常量优化机制
直接赋值创建时 变量与常量相加会创建一个新的字符串
字符串内容比较
str1.equals(str2)
equalsIgnoreCase忽略大小写比较
toUpperCase 返回大写字符串toLowerCase 返回小写字符串
字符遍历
charAt(int index)
toCharArray转换成char数组
字串截取
substring
字串替换
replace
如果匹配多个 会全部替换
字符串切割
split
子串相关
public int indexOf(String str)
返回此字符串的指定子串第一次出现处的索引
public boolean contains(CharSequence s)
当且仅当此字符串包含指定序列返回true
StringBuilder
可变长度的字符串 线程不安全
链式调用
方法返回对象本身(this)则可以链式调用
常用方法
append
拼接
reverse
反转
length
获取长度
toString
转换成字符串
StringBuilder效率高的原理
String拼接每次都要创建两个对象
StringBuilder只有一个对象
包装类
基本类型和包装类的对应关系
byte short char int long float double boolean
Byte Short Character Integer Long Float Double Boolean
Integer
int类型包装类包装一个对象中的原始类型 int 的值
对象创建
构造方法(已过时)
public Integer(int value)
public Integer(String s)
静态方法
public static Integer valueOf(int i)
public static Integer valueOf(String s)
自动装箱 自动拆箱 (JDK1.5)
自动装箱
把基本数据类型转换为对应的包装类类型
调用的静态方法valueOf(),将一个int类型变成Integer类型
自动拆箱
把包装类类型转换为对应的基本数据类型
调用的对象的intValue()方法 把Integer转换成int类型
与String类型转型
int -> String
int i = 0;String s = i+"";
String s = String.valueOf(0);
String -> int
int i = Integer.parseInt("0");
时间日期
计算机时间原点
1970-01-01 00:00:00 GMT1970-01-01 08:00:00 CST
Date
时间日期类
对象创建
public Date()
以当前时间创建
public Date(long date)
以输入的毫秒值创建
常用方法
public long getTime()
返回自原点开始的毫秒值
public void setTime(long time)
以毫秒值设置时间日期
SimpleDateFormat
时间日期格式化
1.formatting (date → text)2.parsing (text → date)3.normalization
常用pattern
yyyy-MM-dd HH:mm:ss
对象创建
public SimpleDateFormat(String pattern)
以指定pattern创建对象
常用方法
public Date parse(String source) throws ParseException
解析字符串
public final String format(Date date)
格式化成字符串
public void applyPattern(String pattern)
修改pattern
JDK8新增
时间日期类
LocalDate
参考LocalDateTime
LocalTime
参考LocalDateTime
LocalDateTime
对象获取
public static LocalDateTime now()
获取当前时间的LocalDateTime对象
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second)
获取指定时间的LocalDateTime对象
LocalDateTime.of方法的重载
public static LocalDateTime of(LocalDate date, LocalTime time)
对象转换
public LocalDate toLocalDate()
public LocalTime toLocalTime()
解析&格式化
public String format(DateTimeFormatter formatter)
格式化为字符串
public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter)
解析成时间日期对象
时间日期增减
以增减天为例 年月日时分秒类似
增减的方法参数均可使用负数
plus系列
增加
public LocalDateTime plusDays(long days)
minus系列
减少
public LocalDateTime minusDays(long days)
with系列
直接修改时间日期
以修改天为例 年月日时分秒类似
public LocalDateTime withDayOfMonth(int dayOfMonth)
get系列
获取时间日期
以获取天为例 年月日时分秒类似
public int getDayOfMonth()
格式化相关
DateTimeFormatter
对象获取
public static DateTimeFormatter ofPattern(String pattern)
常用pattern
yyyy-MM-dd HH:mm:ss
时间间隔相关
Period
计算年月间隔
对象获取
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)
参数使用LocalDate对象
常用方法
public long toTotalMonths()
计算两个日期一共相差几个月
get系列
get系列获取各个属性的值Period属性存储的是两个日期类的各个属性的差值
Duration
计算天 时间间隔
对象获取
public static Duration between(Temporal startInclusive, Temporal endExclusive)
参数要求实现Temporal接口并且要能获取秒 否则抛出异常
常用方法
to系列
获取两个时间之间一共相差多少
数组操作
二分查找
前提条件
数组已排序
思路
1.定义两个变量作为查找的边界2.计算中间索引3.判断中间索引指向的元素是否是要查找的元素4.若不是则根据大小比较结果修改查找边界5.直到查到元素或者查找范围缩小到不存在
具体实现参考
补充
概述
结构
冒泡排序
思路
对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,依次对所有的数据进行操作,直至所有数据按要求完成排序
具体实现参考
递归
自己调用自己,通过每次传入的参数不同,可以解决复杂的问题
递归算法可以把本身问题分解规模小的同类问题,通过求解规模小的同类问题的解
每次调用自身,把自身方法压栈,最终返回时逐一弹栈返回结果 可能会StackOverflowException
使用步骤
明确你这个方法想要干什么
寻找递归结束条件
找出方法的等价关系式(递归规则) 每次调用缩小范围
经验
如果用循环解决问题不困难得话,可以使用循环如果解决起来比较困难,可以考虑递归(递和归)
快速排序
核心思路
1.定义一个基准数2.从右开始找比基准数小的3.从左开始找比基准数大的4.交换两个值的位置5.回到第二步,直到两个变量指向同一个索引为止6.基准数归位,即基准数和最后的索引位置的元素交换
之后在基准数的左右两边递归调用
具体实现参考
Arrays
数组工具类
常用方法
public static void sort(int[] a)
排序
public static String toString(int[] a)
转换成字符串
public static int binarySearch(int[] a, int key)
二分查找 需要已经排序的数组
集合
体系结构
Collection
List
ArrayList
LinkList
Set
HashSet
TreeSet
Map
HashMap
TreeMap
集合 vs 数组
都是容器 可以存储多个数据
数组的长度是不可变的 集合的长度是可变的
数组可以存基本数据类型和引用数据类型集合只能存引用数据类型 基本数据类型使用包装类
Collection
单列集合容器的顶级接口
对象获取
接口通过多态获取对象
常用方法
boolean add(E e)
添加元素
boolean remove(Object o)
从集合中移除指定的元素
boolean removeIf(Predicate<? super E> filter)
根据条件进行移除元素
参数可用lambda
boolean contains(Object o)
判断集合中是否存在指定的元素
boolean isEmpty()
判断集合是否为空
int size()
获取集合的长度
void clear()
清空集合
迭代器
集合的专用遍历方式
对象获取
Iterable<T>接口的实例的iterator()方法返回返回的迭代器默认指向当前集合的0索引处
Iterator<T> iterator()
常用方法
boolean hasNext()
判断当前位置是否有元素可以被取出
E next()
获取当前位置的元素,同时将迭代器对象移向下一个索引位置
void remove()
删除迭代器对象当前指向的元素
遍历集合
参考格式
ArrayList<T> list = new ArrayList<>();Iterator<T> iterator = list.iterator();while (iterator.hasNext()) { T t = iterator.next();}
原理
1.Iterator<E> iterator(): 获取迭代器对象 默认指向0索引2.boolean hasNext(): 判断当前位置是否有元素可以被取出3.E next(): 获取当前位置的元素 将迭代器对象移向下一个索引位置
增强for
简化数组和Collection集合的遍历
格式
for(集合/数组中元素的数据类型 变量名 : 集合/数组名) { // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可}
原理
编译后对于数组是for 对于集合是一个迭代器Iterator
注意事项
实现Iterable接口的类才可以使用迭代器和增强for
在增强for循环中无法改变数组或集合中的元素
三种遍历方法应用场景对比
增强for
进行遍历时使用 遍历过程中若要增删元素则不能使用
普通for
遍历过程中需要增删元素或操作索引时使用
迭代器
注:遍历过程中需要选取出元素 然后再删除元素时使用 否则直接报错
类结构
Collection
Set
SortedSet(有序集)
NavigableSet
AbstractCollection
AbstractSet
TreeSet
HashSet
LinkedHashSet
AbstractList
Vector
Stack(后进先出)
ArrayList
AbstractSequentialList
LinkedList
List
Queue(队列)
Deque(双端队列)
AbstractQueue
PriorityQueue(优先队列)
ArrayBlockingQueue
LinkedBlockingDeque
BlockingQueue(阻塞队列)
List
Collection子接口
特点
存取有序
可重复
有索引
常用方法
void add(int index, E element)
在指定索引添加元素
E remove(int index)
移除并返回指定索引的元素
E get(int index)
返回指定索引的元素
E set(int index, E element)
修改指定索引的元素
ArrayList
底层原理
用数组存储的单列集合
基本使用
添加元素
可以添加任何类型
实际通过泛型限制集合的类型
add
在末尾添加元素
在指定索引处添加元素
remove
根据索引删除 返回被删除的元素
根据元素删除 返回布尔值表示是否成功
如果集合存储的全是数字 因为重载 所以删除按索引删除
set
修改元素 返回旧元素
get
根据索引获取元素
size
获取长度
遍历
使用for循环
循环中删除元素
倒序遍历删除
正序遍历删除会导致跳过元素需要配合索引减一
LinkedList
LinkedList vs ArrayList
ArrayList
底层是数组结构实现,查询快、增删慢
LinkedList
底层是链表结构实现,查询慢、增删快
常用方法
public void addFirst(E e)
在开头添加元素
public void addLast(E e)
在末尾添加元素
public E getFirst()
返回列表中的第一个元素
public E getLast()
返回列表中的最后一个元素
public E removeFirst()
列表中删除并返回第一个元素
public E removeLast()
列表中删除并返回最后一个元素
底层实现
用双向链表存储的单列集合
Set
Collection子接口
特点
不可重复
无索引
存取顺序不一致(但有序)
常用方法
并没有什么特殊方法
TreeSet
特点
不重复
没索引
按照规则排序元素
排序
自然排序
泛型类需要实现Comparable<T>接口中的compareTo方法
compareTo方法遍历集合时,与每个元素对比后,返回负数则添加到左侧,返回正数添加到右侧,返回零不添加
比较器排序
使用public TreeSet(Comparator<? super E> comparator) 构造对象传参可以使用匿名内部类(或lambda)重写int compare(T o1, T o2)方法,用于比较
自然 vs 比较器
不同
自然
自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
能满足大部分情况
比较器
创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
存储没有修改权限的类时可以使用(如:String 包装类等)
相同
返回规则一样
如果返回值为负数 表示当前存入的元素是较小值 存左边
如果返回值为0 表示当前存入的元素跟集合中元素重复了 不存
如果返回值为正数 表示当前存入的元素是较大值 存右边
底层原理
HashSet
特点
Set的所有特点
哈希表结构
哈希值
JDK根据对象的地址或者属性值,算出来的int类型的整数
获取方式
Object类中成员方法public int hashCode()根据对象的地址值计算出哈希值
特点
没有重写hashCode方法
同一对象多次调用,返回值一样
不同对象调用返回值不同
子类重写hashCode方法
一般重写后用属性值来计算哈希值只要属性值相同,不同对象返回哈希值相同
底层原理
JDK7及以前
哈希表=数组+链表
JDK8及以后
哈希表=数组+链表+红黑树链表节点个数超过8个,从链表变为红黑树
存储流程
计算元素哈希值
计算存入数组中的索引位置
判断存入点是否为空
为空
直接存入
不为空
用equals方法判断是否相同
相同元素
抛弃
不同元素
判断结构
存入链表
存入红黑树
使用hashSet记得重写hashCode和equals
Queue
BlockingQueue(阻塞队列)
Map
双列集合 键与值一一对应(键值对)
特点
key不能重复 value可以重复
key与value一一对应
"键值对"称为"Entry"对象
常用方法
int size()
集合的长度
boolean isEmpty()
判断集合是否为空
boolean containsKey(Object key)
判断集合是否包含指定的键
boolean containsValue(Object value)
判断集合是否包含指定的值
V put(K key, V value)
添加元素
V remove(Object key)
根据键删除键值对元素
void clear()
移除所有的键值对元素
遍历方式
1.通过获取所有key,然后遍历key根据key获取value遍历
Set<K> keySet()
获取所有键的集合
V get(Object key)
根据键获取值
2.通过获取所有的entry,遍历entry根据entry对象获取key和value
Set<Map.Entry<K, V>> entrySet()
Map.Entry<K, V>
K getKey()
V getValue()
3.forEach遍历
forEach方法需要传入一个BiConsumer<? super K, ? super V>接口的实现类对象该接口内只有一个需要重写的方法void accept(T t, U u)因此可以用lambda表达式书写forEach方法中给accept方法传参为(K k, V v)forEach方法底层实现参考遍历方式2
HashMap
底层原理
哈希表结构
默认创建一个长16的数组,增长因子0.75,每次扩容为原先的两倍
依赖hashCode和equals保证key唯一
key存储自定义对象需要重写hashCode和equals
TreeMap
底层原理
红黑树结构
依赖自然排序或比较器排序,对key排序
如果key存储的是自定义对象,需要实现Comparable接口或者在TreeMap中给出比较器排序规则
Properties
Properties extends Hashtable
默认编码ISO-8859-1
可以保存到流中(store方法)从流中加载(load方法)
只存字符串
常用方法
读
public synchronized void load(Reader reader)
public synchronized void load(InputStream inStream)
写
public void store(OutputStream out, String comments)
public void store(Writer writer, String comments)
public synchronized Object setProperty(String key, String value)
设置集合的键和值底层调用ConcurrentHashMap(并发HashMap)的put方法
public String getProperty(String key)
指定键搜索属性
public Set<String> stringPropertyNames()
返回一个不可修改的键集
HashTable
ConcurrentHashMap
不可变集合 JDK9
在整个生命周期中集合是不可被修改(增、删、改)的
创建方法
List,Set,Map接口中的of方法可以创建一个不可变集合
Map接口中ofEntries方法可以把键值对封装成一个Entry对象再把这个Entry对象添加到集合当中从而创建不可变集合
应用场景
可以结合集合的带参构造,实现集合的批量添加
不希望集合内容被修改时(Dao传数据给Service)
可变参数
参数个数不确定 个数范围[0,N]
格式
修饰符 返回值类型 方法名(数据类型... 变量名) { }
public void method(int... nums){}
注意事项
如果一个方法有多个参数,包含可变参数,可变参数放在最后
使用场景
当参数类型确定,个数不确定时使用,可以使用可变参数
Stream流
思想
函数式
流水线
方法
获取方法
Collection
default Stream<E> stream()
Map
先转换成Set集合,然后通过Collection的方法生成
数组
Arrays.stream(T[] array)
同种类型多个数据
Stream.of(T... values)
中间方法
filter
Stream<T> filter(Predicate<? super T> predicate)
Predicate接口中仅一个需要重写的方法boolean test(T t) 该方法返回true则保留,反之抛弃
过滤流中数据
limit
Stream<T> limit(long maxSize)
返回此流中的元素组成的流,截取前指定参数个数的数据
skip
Stream<T> skip(long n)
跳过指定参数个数的数据,返回由该流的剩余元素组成的流
concat
static <T> Stream<T> concat(Stream a, Stream b)
合并a和b两个流为一个流
distinct
Stream<T> distinct()
返回由该流的不同元素(根据Object.equals(Object))组成的流
终结方法
forEach
void forEach(Consumer action)
对此流的每个元素执行操作
count
long count()
返回此流中的元素数
收集方法
collect
R collect(Collector collector)
把结果收集到集合中
Collector对象使用工具类Collectors创建
public static <T> Collector toList()
把元素收集到List集合中
public static <T> Collector toSet()
把元素收集到Set集合中
public static Collector toMap (Function keyMapper,Function valueMapper)
把元素收集到Map集合中
Function接口是函数式接口
参考例子
Stream流不能直接修改原始数据,相当于复制了一份数据在流中处理
备注
集合不能在遍历的时候使用集合对象的add remove等方法修改集合
异常
体系结构
Throwable
Error
Exception
RuntimeException 及子类
其余的 Exception 子类
分类
运行时异常
无需显式处理(手动处理),也可以和编译时异常一样处理
编译时异常
必须显式处理(手动处理),否则程序就会发生错误,无法通过编译
JVM默认异常处理方式
1.打印栈追踪2.停止程序运行
处理异常方式
throws
格式
public void 方法() throws 异常类名 { //...}
在方法中 当传递的参数有误 没有继续运行的必要时采取抛出处理 抛给调用者处理异常
声明异常 调用方法时可能出现异常可以声明多个
编译时的异常 必须声明谁调用谁处理 最多让JVM来处理运行时异常可以不声明
throw
格式
throw new 异常();
在方法内 表示当前代码手动抛出一个异常 下面的代码无法再执行了
在方法中 当传递的参数有误 没有继续运行的必要时采取抛出处理 抛给调用者处理异常
一次只能抛一个异常一旦执行一定抛异常
try...catch
自己捕获并处理异常
格式
try { // 可能出现异常的代码} catch(异常类名 变量名) { // 异常的处理代码}
执行流程
1.执行try语句块中语句2.出现异常跳转到对应的catch语句块3.执行完后程序继续往下执行
细节
try中没有遇到问题
跳过catch代码继续执行
try 中遇到了问题
直接跳转到catch语句块 try中剩余代码不执行
出现的问题没有被捕获
异常抛给调用者处理
同时可能出现多个异常
用多个catch捕获
多个异常之间存在父子关系 父类一定写在子类下面
try...catch...finally
格式
try{ // 可能出现异常的代码}catch(异常类名 变量名){ // 异常的处理代码}finally{ // 执行所有清除操作}
被finally控制的语句一定会执行,除非JVM"直接"退出(任何情况下finally都会执行)
finally中的return会覆盖之前的return
Throwable
异常基类
常见方法
public String getMessage()
返回详细消息字符串
public String toString()
返回可抛出的简短描述
public void printStackTrace()
把异常的栈追踪输出在控制台
自定义异常
当Java中提供的异常不能满足需求时 可以使用自定义异常
格式
public class 类名 extends RuntimeException { public 类名() { } public 类名(String message) { super(message); }}
类名用 "异常名+Exception"
实际开发过程中使用异常的原则
Java类库中定义的可以预检测方式避免的RuntimeException异常不应该通过catch的方式来处理比如:NullPointerException IndexOutOfBoundsException等等
catch 时请分清稳定代码和非稳定代码 稳定代码指的是无论如何不会出错的代码对于非稳定代码的 catch 尽可能进行区分异常类型 再做对应的异常处理
捕获异常是为了处理它 不要捕获了却什么都不处理而抛弃之如果不想处理它 请将该异常抛给它的调用者最外层的业务使用者 必须处理异常 将其转化为用户可以理解的内容
捕获异常与抛异常 必须是完全匹配 或者捕获异常是抛异常的父类
避免直接抛出 new RuntimeException()更不允许抛出 Exception 或者 Throwable应使用有业务含义的自定义异常推荐业界已定义过的自定义异常 如:DAOException ServiceException等
IO
IO体系
流式部分
字节流
输入流
InputStream(顶级父类)
输出流
OutputStream(顶级父类)
字符流
输入流
Reader
输出流
Writer
非流式部分
File
File
操作文件或文件夹
通过路径表示某个文件或文件夹可以表示存在的,也可以表示不存在的
对象获取
构造方法
File(String pathname)
通过指定的路径字符串创建File对象
File(String parent, String child)
通过File表示的文件夹和子路径创建的File对象
File(File parent, String child)
通过File表示的文件夹和子路径创建的File对象
路径
绝对路径
一个完整的路径
public String getAbsolutePath() 获取绝对路径
相对路径
一个简化的路径,默认是相对当前项目下的路径
常用方法
创建
public boolean createNewFile() throws IOException
创建一个新的空文件
如果路径上的文件夹不存在的话,就抛异常:java.io.IOException: 系统找不到指定的路径.
public boolean mkdirs()
创建多级目录
1.可以创建单级文件夹,也可以创建多级文件夹2.若文件夹已经存在,则会创建失败
删除
public boolean delete()
删除File表示的文件或目录
1.不走回收站2.只能删除文件和空文件夹3.有权限问题的话,无法删除,并且无提示
判断
public boolean exists()
测试File是否存在
public boolean isDirectory()
测试File是否为目录
public boolean isFile()
测试File是否为文件
获取
public String getName()
返回File表示的文件名称或文件夹的名称
public File[] listFiles()
获取文件夹下的所有文件和文件夹对象,封装到File数组中返回
1.File指向不存在->返回null2.存在的是文件->返回null3.存在的是文件夹->正常输出4.存在的是空文件夹->返回长度为0的File数组5.权限问题,没有访问权限->返回null6.隐藏文件和隐藏文件夹都可以获取
public long length()
返回文件大小
创建一个文件的方法
1.创建指向目录的File对象2.检查目录是否存在,不存在则创建3.创建File对象指向目录下的文件 (用父子路径构造方法创建即可)4.创建File对象指向的文件
多级文件夹递归遍历
采用递归的方式遍历目录内循环遍历,检查是文件还是目录如果是目录则递归,如果是文件则直接处理在适当位置补上对目录文件夹的处理即可循环结束递归方法弹栈
统计文件种类个数
遍历文件夹依然采用多级文件夹递归遍历的方式统计可以使用Map对象存储出现次数
IO流
读写文件内容
字节流
以二进制操作文件,能操作所有文件
父类
输入流
InputStream(顶级父类)
输出流
OutputStream(顶级父类)
读写文件
读
FileInputStream
对象获取
构造方法
public FileInputStream(String name)
public FileInputStream(File file)
基本使用
1.创建对象2.读数据3.释放资源
常用方法
public int read(byte[] b)
从输入流读取最多b.length个字节的数据放入数组中,返回读取的个数
写
FileOutputStream
对象获取
构造方法
public FileOutputStream(String name)
public FileInputStream(File file)
基本使用
1.创建对象2.写数据3.释放资源
常用方法
void write(int b)
将指定的字节写入此文件输出流一次写一个字节数据
void write(byte[] b)
将 b.length个字节从指定的字节数组写入此文件输出流一次写一个字节数组数据
void write(byte[] b, int off, int len)
将 len个字节从指定的字节数组开始,从偏移量off开始写入此文件输出流一次写一个字节数组的部分数据
注意事项
创建对象时 若文件不存在则创建 若文件存在默认直接清空
传递整数实际写出的是整数对应的数据
每次用完必须释放资源
换行
- windows:\r\n- linux:\n- mac:\r
追加写入
public FileOutputStream(String name,boolean append)
public FileOutputStream(File file, boolean append)
使用上述两个参数的构造方法获取对象如果append参数为true,则字节将写入文件的末尾而不是开头
异常处理
try...catch...finally
字节缓冲流
输入
对象获取
BufferedInputStream(InputStream in)
常用方法
public int read()
从输入流中读出8192个字节到缓冲数组中,再从缓冲数组中取出一个字节
public int read(byte b[])
从输入流中读出8192个字节到缓冲数组中,再从缓冲数组中取出数组b.length个字节到数组b中
输出
对象获取
BufferedOutputStream(OutputStream out)
常用方法
public void write(int b)
将字节b写入到缓冲数组中,当缓冲数组满时,一次性写入目标文件
public void write(byte b[], int off, int len)
将数组b中的元素,从下标off开始,向缓冲数组中写入len个字节,当缓冲数组满时,一次性写入目的地
public void flush()
刷缓冲区,在缓冲区未满的时候把缓冲区里的内容写入目标文件(close方法虽然调用了这个方法,但建议在write之后一定要主动添加flush方法)
原理
各在底层创建了一个默认长度为8192的字节数组作为缓冲区每次读写数据到缓冲区内存中直接读写两个缓冲区的数据
使用原则
如果自定义的数组长度超过了8192,读写不再经过缓冲数组,直接使用自定义数组来进行读写
如果不自定义数组,使用缓冲流的效率比字节流要高.如果自定义数组,使用字节流的效率要比缓冲流的效率高.
缓冲流有字节流同样的功能,并新增了功能(装饰模式)
读写对象(序列化)
将对象信息以字节形式保存到文件中,或者在网络中传输
序列化
对象->字节流
反序列化
字节流->对象
输入流
ObjectInputStream
构造方法
ObjectInputStream(InputStream in)
创建从指定的InputStream读取的ObjectInputStream
反序列化方法
Object readObject()
从ObjectInputStream读取一个对象
输出流
ObjectOutputStream
构造方法
ObjectOutputStream(OutputStream out)
创建一个写入指定的OutputStream的ObjectOutputStream
序列化方法
void writeObject(Object obj)
将指定的对象写入ObjectOutputStream
Serializable接口
一个类的对象如果想被序列化,那么这个类必须要实现Serializable接口
一个标记性接口,里面没有任何的抽象方法
静态成员不会被序列化
transient
修饰的成员变量不会被序列化
serialVersionUID
如果没有定义,那么虚拟机会根据类中的信息会自动的计算出一个序列号
如果修改了类中的信息.那么虚拟机会再次计算出一个序列号.
如果序列号不一致会抛出InvalidClassException异常
注意事项
readObject()方法在访问到最后的时候会直接抛出一个EOFExceptionwhile(true)遍历时需要try...catch
字符流
只能操作纯文本文件
码表(字符集)
数字与字符对应关系的集合
常见字符集
ASCII字符集
GBXXX字符集
双字节编码中文
Unicode字符集
128个US-ASCII字符,只需一个字节编码拉丁文等字符,需要二个字节编码大部分常用字(含中文),使用三个字节编码其他极少使用的Unicode辅助字符,使用四字节编码
编码
按照某种规则,将字符转换成二进制,再存储到计算机中
解码
按照编码的规则,将计算机中的二进制解析显示出来,称为解码
常见的编码/解码方式
ASCII编码
ASCII字符集(码表)的编码方式,1个字节,最多能表示256个字符,适用于英文
GBK编码
GBK字符集(码表)的编码式,用1个字节表示英文,用2个字节表示中文
UTF-8编码
Unicode字符集(码表)的编码方式,用1个字节表示英文,用3个字节表示中文
乱码
编码和解码的规则不一致,导致乱码
字符串相关方法
编码
public byte[] getBytes()
使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中
public byte[] getBytes(String charsetName)
使用命名的字符集将这个String编码成一个字节序列,将结果存储到一个新的字节数组中
解码
String(byte[] bytes)
通过使用平台的默认字符集解码指定的字节数组来构造新的String
String(byte[] bytes, String charsetName)
构造一个新的String由指定用指定的字节的数组解码
字符流=字节流+编码表
解决字节流读取文本文件时出现乱码
抽象父类
输入流
Reader
输出流
Writer
读写文件
读
FileReader
构造方法
FileReader(File file)
在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(String fileName)
在给定从中读取数据的文件名的情况下创建一个新 FileReader
常用方法
int read()
一次读一个字符数据
int read(char[] cbuf)
一次读一个字符数组数据
使用步骤
1.创建对象2.读数据3.释放资源
注意事项
创建对象时 文件必须存在
用完必须关流释放资源
写
FileWriter
构造方法
FileWriter(File file)
根据给定的File对象构造一个FileWriter对象
FileWriter(File file, boolean append)
根据给定的File对象构造一个FileWriter对象append设定是否追加写入
FileWriter(String fileName)
根据给定的文件名构造一个FileWriter对象
FileWriter(String fileName, boolean append)
根据给定的文件名以及指示是否附加写入数据来构造FileWriter对象
常用方法
public void write(int c)
public void write(char cbuf[])
public void write(char cbuf[], int off, int len)
public void write(String str)
public void write(String str, int off, int len)
public void flush()
刷新流 还可以继续写
底层调用writeBytes()写出内存中的数据
public void close()
关闭流 释放资源
底层会先调用writeBytes()写出内存中的数据
使用步骤
1.创建对象2.写数据3.释放资源
注意事项
创建对象时 若文件不存在则创建(父路径要存在) 若文件存在默认直接清空
写整数实际写出的是整数对应在码表的字母
写字符传数据时 把字符串原样写出
每次用完必须释放资源
缓冲流
输入
BufferedReader
构造方法
BufferedReader(Reader in)
特有方法
String readLine()
读一行文字,结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null
输出
BufferedWriter
构造方法
BufferedWriter(Writer out)
特有方法
public void newLine()
写一行行分隔符,行分隔符字符串由系统属性定义
转换流
构造方法
InputStreamReader(InputStream in)
使用默认字符编码创建InputStreamReader对象
InputStreamReader(InputStream in,String chatset)
使用指定的字符编码创建InputStreamReader对象
OutputStreamWriter(OutputStream out)
使用默认字符编码创建OutputStreamWriter对象
OutputStreamWriter(OutputStream out,String charset)
使用指定的字符编码创建OutputStreamWriter对象
指定字符集
JDK11以前
使用转换流指定字符集
JDK11开始
字符流增加新构造可以指定字符集
打印流
输出流
PrintStream
System.out是一个打印流对象
对象获取
构造方法
public PrintStream(OutputStream out)
将输出流转换成打印流,可增强功能
常用方法
write系列
同输出流
特有方法
print系列
println系列
printf系列
PrintWriter
类似PrintStream
多线程
意义
提高程序执行效率
概念
进程
正在运行的软件
操作系统资源分配的最小单位
线程
进程里至少有一个线程
CPU调度的最小单元
并行
同时执行
并发
交替执行
实现方式
extends Thread
Thread类方法
void run()
子类需要重写,线程开启后,被调用执行用来封装被线程执行的代码直接调用相当于普通方法的调用
void start()
开启线程,JVM会调用run方法
使用步骤
1.定义类继承Thread2.在定义的类中重写run方法3.创建定义的类的对象4.对象调用start方法启动线程
implements Runnable
Thread构造方法
Thread(Runnable target)
分配一个新的Thread对象
使用步骤
1.定义类实现Runnable接口2.接口的实现类中重写run方法3.创建实现类的对象4.创建Thread类对象,把实现类对象作为构造方法参数5.调用Thread类对象的start方法启动线程
implements Callable
Callable<V>接口方法
V call()
计算结果,如果无法计算结果,则抛出一个异常
FutureTask<V>类方法
FutureTask(Callable<V> callable)
构造方法创建FutureTask对象一旦运行就执行给定的Callable
V get()
如有必要,等待计算完成,然后获取其结果
使用步骤
1.定义类实现Callable接口2.实现类重写call方法3.创建实现类对象4.创建Future的实现类FutureTask对象 并把Callable接口的实现类对象作为构造方法的参数5.创建Thread类对象,把FutureTask对象作为构造方法参数6.调用Thread类对象的start方法启动线程7.调用get方法可以获取线程结束后的返回值
注意
get()方法的调用一定要在Thread类对象调用start()方法之后
如果不需要线程的返回值就使用Runnable,需要返回值就使用Callable,一般不考虑使用Thread的子类
Thread类
常用方法
void setName(String name)
修改线程的名称
String getName()
返回此线程的名称
static Thread currentThread()
返回当前正在执行的线程对象的引用
static void sleep(long millis)
使当前正在执行的线程停留(暂停执行)指定的毫秒数
final int getPriority()
返回此线程的优先级
final void setPriority(int newPriority)
更改此线程的优先级,默认5范围1-10,越大越优先
void setDaemon(boolean on)
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
优先级
Java使用抢占式调度模型 多线程程序执行具有随机性
就算设置了优先级,也还是会随机执行
设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占.
在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定
守护线程
程序运行的时候在后台提供一种通用服务的线程
所有用户线程停止,进程才会停掉所有守护线程,然后退出程序
其他方法
boolean isAlive()
测试线程是否处于活动状态
void join()
使当前线程暂停执行,等待调用该方法的线程结束后再继续执行本线程
void yield()
暂停当前正在执行的线程对象,允许其他具有相同优先级的线程获得运行机会
系统选择其他或具有更高优先级的线程执行,若无其他相同或更高优先级的线程,则该线程继续执行
线程同步
数据安全问题
出现条件
多线程
共享数据
在Java中,所有实例域(对象的字段),静态域(静态字段)和数组元素都存储在堆内存中,堆内存在线程之间共享
不会共享的数据
局部变量(Local Variables),方法定义参数(局部变量(Local Variables)和异常处理器参数(ExceptionHandler Parameters)不会在线程之间共享)
多个线程操作共享数据
保证线程同步的方式
synchronized
同步代码块
格式
synchronized(锁对象){...}
特点
默认情况下是打开的只要有一个线程进去执行代码了,锁就会关闭当线程执行完出来时,锁才会自动打开
锁对象
可以是任意对象,但必须唯一,否则将会被认为是另一个锁
原理
从字节码文件中可以看出,对于同步块的实现使用了monitorenter和monitorexit指令其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞
对象、监视器、同步队列和执行线程之间的关系
分支主题
同步方法
格式
修饰符 synchronized 返回值类型 方法名(方法参数){...}
锁对象
this
静态同步方法
格式
修饰符 static synchronized 返回值类型 方法名(方法参数){...}
锁对象
类名.class
Lock接口
实现类ReentrantLock
构造方法
ReentrantLock()
常用方法
void lock()
加锁
boolean tryLock()
尝试加锁
void unlock()
释放锁
常在finally中调用
synchronized vs Lock
synchronized
Java的关键字,在jvm层面上
1.以获取锁的线程执行完同步代码,释放锁2.线程执行发生异常,jvm会让线程释放锁
假设A线程获得锁,B线程等待.如果A线程阻塞,B线程会一直等待
锁的细粒度和灵活度低
Lock
是一个接口,使用其实现类
在finally中必须释放锁,不然容易造成线程死锁
分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待
锁的细粒度和灵活度高
如何选择
除非synchronized不满足需要,才选择Lock
死锁
线程死锁
由于两个或者多个线程互相持有对方所需要的资源,导致这些线程一直处于等待状态,无法继续执行
可能产生的情况
资源有限
同步嵌套
可以通过查看堆信息来分析死锁产生的原因 cmd命令:jps,jstack
避免
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
线程通信
Object类方法
wait()
使当前线程等待同时释放锁直到另一个线程调用该对象的notify()方法或notifyAll()方法
notify()/notifyAll()
唤醒正在等待对象监视器的单个/所有线程并不立即释放锁
经典问题
生产者-消费者
消费者步骤
1.判断容器中是否有物品2.如果没有就等待3.如果有就消费4.消费完后,通知生产者继续生产
生产者步骤
1.判断容器中是否有物品2.如果有就等待3.如果没有就生产4.生产完后,通知消费者继续消费
其中访问共享数据时需要同步,可以通过线程通信的方法等待和唤醒线程
阻塞队列(BlockingQueue)
在一个队列的基础上又支持了两个附加操作的队列
附加方法
void put(E e)
将参数放入队列,如果放不进去会阻塞
E take()
取出第一个数据,取不到会阻塞
常用实现类
ArrayBlockingQueue
底层数组 有界
LinkedBlockingQueue
底层链表 无界 最大为int最大值
应用场景
生产者-消费者模式
线程状态
线程对象在不同的时期有不同的状态,同一时刻线程只能存于一种状态
Java线程状态
定义在java.lang.Thread.State枚举类中
线程池
存线程的容器
原理
创建一个容器,有任务需要执行时,如果容器里没有线程对象,那么创建线程对象,否则从容器中取出线程对象使用.当任务执行完毕时,线程对象归还容器
避免频繁创建线程,进而提高系统效率
相关方法
Executors
静态方法
static ExecutorService newCachedThreadPool()
创建一个默认的线程池
static ExecutorService newFixedThreadPool(int nThreads)
创建一个指定最多线程数量的线程池
ExecutorService接口
控制线程池
void shutdown()
关闭线程池,先前提交的任务将被执行,但不会接受任何新任务关闭后提交新任务会报错
AbstractExecutorService抽象类
Future<?> submit(Runnable task)
提交一个可运行的任务执行,并返回一个表示该任务的未来.未来的get方法将在成功完成后返回null
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
int corePoolSize -> 核心线程数量 不小于0int maximumPoolSize, -> 最大线程数 大于0 最大数量>=核心线程数量long keepAliveTime, -> 空闲线程最大存活时间 不小于0TimeUnit unit, -> 时间单位 使用TimeUnit枚举类指定BlockingQueue<Runnable> workQueue, -> 阻塞任务队列 不能为nullThreadFactory threadFactory, -> 创建线程工厂 不能为nullRejectedExecutionHandler handler, -> 任务拒绝策略 不能为null
参数一:核心线程数量,线程池中初始线程数,刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动 而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程 当线程数超过核心线程数时,会将请求的任务放入任务队列参数二:最大线程数,当任务队列满时,如果线程数没有超过最大线程数时,创建新的线程参数三:空闲线程最大存活时间参数四:时间单位---TimeUnit参数五:任务队列 --- 当线程数超过核心线程数量小于最大线程数时,让任务在队列中等着,等有线程空闲了 再从这个队列中获取任务并执行 可用new ArrayBlockingQueue<>(capacity)参数六:创建线程工厂 --- 按照默认的方式创建线程对象 可用Executors.defaultThreadFactory()参数七:任务的拒绝策略 --- 可用new ThreadPoolExecutor.AbortPolicy() 1.什么时拒绝? 当提交的任务 > 池子中最大的线程数量+队列的容量 2.如何拒绝? 拒绝策略
执行顺序
如果运行的线程不少于corePoolSize,则Executor始终首选添加新的线程,而不进入队列如果运行的线程等于或大于corePoolSize,则Executor始终首选将请求加入队列,而不是添加新的线程如果无法将请求加入队列,则创建新的线程,除非创建的线程数超出maximumPoolSize,在这种情况下,任务会被拒绝
线程池最多可执行的任务数 = 队列容量 + 最大线程数
最佳线程数目 = ((线程等待时间 + 线程CPU时间) / 线程CPU时间) * CPU数目
拒绝策略
ThreadPoolExecutor.AbortPolicy
丢弃任务并抛出RejectedExecutionException异常.默认的策略.
ThreadPoolExecutor.DiscardPolicy
丢弃任务,但是不抛出异常,不推荐
ThreadPoolExecutor.DiscardOldestPolicy
抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy
调用任务的run()方法绕过线程池直接执行
线程安全
JMM
Java Memory Model
JVM为了屏蔽各个硬件平台和操作系统对内存访问机制的差异化提出的概念
JMM 是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中共享变量(包括实例(对象)字段,静态字段和构成数组对象的元素)的访问方式.
JMM结构
线程 <=> 工作内存(共享变量副本) <=(JMM控制)=> 主内存(共享变量)
JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证
JMM规定
线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接在主内存中读写
不同线程之间不能直接访问其他线程工作内存的变量,线程之间的变量值的传递只能通过主内存传递
线程安全的本质
可见性
多个线程访问同一个共享变量时,其中一个线程对这个共享变量值的修改,其他线程能够立刻获得修改以后的值
原子性
操作是不可中断的,要么全部执行成功要么全部执行失败
有序性
编译器和处理器为了优化程序性能而对指令序列进行重排序,也就是编写的代码顺序和最终执行的指令顺序是不一致的
线程安全-可见性
实现可见性必须保证
线程修改后的共享变量值能够及时从线程工作内存中刷新到主内存中
其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中
volatile
原理
线程写volatile变量的过程
JMM会把该线程对应的工作内存(本地内存)中的共享变量值刷新到主内存
线程读volatile变量的过程
JMM会把该线程对应的工作内存(本地内存)置为无效.线程接下来将从主内存中读取共享变量
synchronized
原理
线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要时从主内存中重新读取最新的线程值
线程释放锁时,必须把共享变量的最新值刷新到主内存中
具体过程
1.线程获得锁2.清空变量副本3.拷贝最新值4.执行代码5.最新值刷新到主内存6.释放锁
线程安全-原子性
Java中的原子操作
除了long和double之外的所有原始类型的赋值
所有volatile变量的赋值
java.concurrent.Atomic* 类的所有操作
非原子性操作,在执行的过程中,有可能被其他线程打断
synchronized
锁共享变量的操作
CAS
原子操作类
原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式.分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新属性(字段).
更新基本类型
AtomicInteger
常用方法
构造方法
public AtomicInteger()
初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue)
初始化一个指定值的原子型Integer
int get()
获取值
int getAndIncrement()
以原子方式将当前值加1,返回自增前的值.
int incrementAndGet()
以原子方式将当前值加1,返回自增后的值.
int addAndGet(int data)
以原子方式将输入的数值与实例中的值相加,并返回结果
int getAndSet(int value)
以原子方式设置为value的值,并返回旧值
AtomicBoolean
AtomicLong
原理
自旋锁+CAS算法
源码解析
CAS算法
包含三个参数(V,E,N) V表示要更新的变量,E表示预期值,N表示新值 1.当预期值E==主内存中的值V 可以修改 将V修改成N 2.当预期值E!=主内存中的值V 不能修改 更新工作内存 再次发起尝试比较 直到E==V 才继续执行(自旋)
synchronized vs CAS
相同点
在多线程情况下,都可以保证共享数据的安全性
不同点
synchronized
总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改.所以在每次操作共享数据之前,都会上锁.(悲观锁)
CAS
从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁.只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据.如果别人修改过,那么我再次获取现在最新的值.如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)
JDK1.6之后对synchronized(偏向锁,轻量级锁,重量级锁)进行了大量的优化,优化为了CAS加锁机制
并发工具包
并发容器
Hashtable
HashMap线程不安全,Hashtable线程安全,但是效率低下
原理
底层哈希表,修改数据对整个哈希表加锁
特点
Hashtable采取悲观锁synchronized的形式保证数据的安全性
只要有线程访问,会将整张表全部锁起来,所以Hashtable效率低下
ConcurrentHashMap
为了保证数据的安全性也要考虑效率的情况下,JDK1.5以后所提供ConcurrentHashMap
原理
JDK1.7
通过一个Segment[]存放HashEntry[](哈希表)数据存在哈希表中,相当于Segment[]中存储了多个哈希表对Segment[]的每个元素(哈希表)的访问加锁,保证数据安全
JDK1.8
用一个哈希表存放数据,哈希表中用链表或红黑树存储数据通过对哈希表的每个元素(链表和红黑树中的根)的访问加锁,保证线程安全
1.如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表2.计算当前元素应存入的索引。3.如果该索引位置为null,则利用cas算法,将本结点添加到数组中.4.如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址, 挂在他下面,变成链表.5.当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象, 配合悲观锁保证多线程操作集合时数据的安全性
并发工具类
CountDownLatch
让某一条线程等待其他线程执行完毕之后再执行
常用方法
public CountDownLatch(int count)
参数传递线程数,表示等待线程数量.并定义一个计数器
public void await()
让线程等待,当计数器为0时,会唤醒等待的线程
public void countDown()
当前线程执行完毕时,会将计数器-1
原理
1.CountDownLatch是通过一个计数器来实现的,计数器的初始值为需要等待线程的数量2.线程调用CountDownLatch的await()方法会阻塞当前线程(即:主线程在闭锁上等待),直到计数器的值为03.当一个工作线程完成了自己的任务后,调用CountDownLatch的countDown()方法,计数器的值就会减1。4.当计数器值为0时,说明所有的工作线程都执行完了,此时,在闭锁上等待的主线程就可以恢复执行任务
Semaphore
可以控制访问特定资源的线程数量
信号量
一个控制访问多个共享资源的计数器
原理
1.Semaphore是通过一个计数器(记录许可证的数量)来实现的,计数器的初始值为需要等待线程的数量2.线程通过acquire()方法获取许可证(计数器的值减1),只有获取到许可证才可以继续执行下去,否则阻塞当前线程3.线程通过release()方法归还许可证(计数器的值加1)
CyclicBarrier(同步屏障)
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景
CyclicBarrier vs CountDownLatch
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。
Exchanger
线程间数据交换
原理
两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方
可以用于校对工作
网络编程
目的
实现设备和设备之间的数据转发
三要素
IP
设备在网络中的唯一标识
ipv4
32位 4字节
点分十进制
ipv6
128位 16字节
冒分十六进制(8组)
相关cmd命令
ping ip或者域名
ipconfig
特殊IP
127.0.0.1
本地回环
InetAddress类
获取对象
静态方法
public static InetAddress getByName(String host)
可以传入主机名(不推荐)或者IP地址
常用方法
public String getHostName()
获取主机名
public String getHostAddress()
获取主机地址
端口
程序在设备中的编号,范围0~65535之间,0~1024不建议使用
协议
UDP
特点
无连接,速度快,不安全,有大小限制64kb
单播
发送端使用步骤
1.创建核心对象
public DatagramSocket(int port)
2.打包数据,端口与接收端端口一致
public DatagramPacket(byte buf[], int length, InetAddress address, int port)
3.发送数据
datagramSocket.send(DatagramPacket p)
4.释放资源
datagramSocket.close()
接收端使用步骤
1.创建核心对象,必须指定端口
public DatagramSocket(int port)
2.创建包用于接收
public DatagramPacket(byte buf[], int length)
3.接收数据
datagramSocket.receive(DatagramPacket p)
4.解析数据
byte[] arr = datagramPacket.getData(); // 数据写入字节数组int length = datagramPacket.getLength(); // 获取数据长度
5.释放资源
datagramSocket.close()
组播
组播频段224.0.0.0~239.255.255.255,一般只能用224.0.1.xxx以后的
注意
组播发送端ip为组播频段ip
组播接收端使用MulticastSocket对象
组播接收端需要使用MulticastSocket对象的joinGroup方法加入组播频段
广播
广播频段255.255.255.255或者后缀是255
使用广播只需要修改发送端发送的ip为广播ip
TCP
特点
面向连接,速度较慢,数据安全,没有大小限制,数据不丢失
三次握手
客户端发连接请求
A->B : SYN=1,seq=x
服务器返回确认响应
B->A : SYN=1,ACK=1,seq=y,ack=x+1
客户端返回确认响应
A->B : ACK=1,seq=x+1,ack=y+1
四次挥手
客户端发取消连接请求
A->B : FIN=1,seq=u
服务器返回收到响应
B->A : ACK=1,seq=v,ack=u+1
服务器处理完信息后,发确认取消
B->A : FIN=1,ACK=1,seq=w,ack=u+1
客户端返回确认信息
A->B : ACK=1,seq=u+1,ack=w+1
客户端
1.创建核心对象
public Socket(String host, int port)
传入要连接的ip和端口
2.获取I/O流和服务器通话
输入流
接收服务器数据
输出流
给服务器发数据
3.释放资源
socket.close()
服务器端
1.创建核心对象,需要指定端口
public ServerSocket(int port)
2.监听客户端连接
public Socket accept()
返回Socket对象
阻塞方法,没有客户端连接就一直等待
serverSocket.accept()
3.通过Socket获取I/O流对话
输入流
接收客户端数据
输出流
给客户端发数据
4.释放资源,不需要释放服务器
socket.close()
UUID
生成一个不重复的字符串 长度36位 去掉'-'共32位
根据时间戳生成
UUID.randomUUID().toString();
类加载器
负责将.class文件加载到内存
作用
加载类
读取字节码目录下的资源文件(相当于项目src文件下的文件)
类加载时机
用到类的时候加载类进内存
类加载过程
1.加载
通过包名+类名获取类的二进制字节流,将其读进内存,在内存中生成一个代表该类的java.lang.Class对象
链接
2.验证
文件中的信息是否符合虚拟机规范有没有安全隐患
3.准备
初始化静态变量
4.解析
本类中如果用到了其他类,此时就需要找到对应的类
5.初始化
开始执行类中定义的Java程序代码,初始化类变量和其他资源
如果有父类先初始化直接父类(不适用于接口)
如果有静态代码块等,依次执行
分类
BootstrapClassLoader
负责加载最底层的东西
启动类加载器,负责将<Java_Runtime_Home>/lib 下面的类库加载到内存中.不能直接使用
PlatformClassLoader
加载大部分的JDK中的类
平台类加载器,负责加载<Java_Runtime_Home>/lib/modules
SystemClassLoader
加载少部分JDK中的类,及所有用户自定义的类
系统类加载器,负责加载用户类路径上(classpath)所指定的类
双亲委派(全盘委托)
双亲委派
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载
保证类的全局唯一性,保证类只被加载一次
ClassLoader
对象获取
public static ClassLoader getSystemClassLoader()
public ClassLoader getClassLoader()
建议用当前"类名.class.getClassLoader()"获取类加载器对象
加载资源文件
public InputStream getResourceAsStream(String name)
相对路径从字节码根目录开始(相当于项目的src目录)
public URL getResource(String name)
URL对象可以getPath获取绝对路径传给IO流操作文件
可以处理中文
反射
程序运行过程中无视访问修改符的操作类中的方法,属性,构造方法等成员
前提
获取该类的Class对象
Class<?> clazz = Class.forName("全限定类名(包名+类名)");
常用
类名.class;
已知类名
.class
对象.getClass();
已知对象名
不管怎么获取,字节码都是唯一的
操作构造方法
Constructor
对象获取
Class对象.getConstructor(Class... clazz)
获取单个公共构造,参数决定使用哪个重载
clazz传参为构造方法的参数的类型的Class对象
Class对象.getDeclaredConstructor(Class... clazz)
获取单个构造,包含私有,参数决定使用哪个重载
clazz传参为构造方法的参数的类型的Class对象
常用方法
public void setAccessible(boolean flag)
flag设置为true表示暴力反射,可使用私有
public T newInstance(Object ... initargs)
通过构造方法获取对象,传参要对应Constructor的参数
使用场景
反射+多态+配置文件
操作成员变量
Field
对象获取
Class对象.getField(String name)
获取单个公共字段
Class对象.getDeclaredField(String name)
获取单个字段,包含私有
常用方法
public void setAccessible(boolean flag)
flag设置为true表示暴力反射,可使用私有
public void set(Object obj, Object value)
obj要被修改的对象,value修改后的值
public Object get(Object obj)
obj要被获取值的对象
成员变量必须在对象身上才有意义
操作成员方法
Method
对象获取
Method[] methods = Class对象.getMethods()
获取所有公共方法,包含继承
Method[] methods = Class对象.getDeclaredMethods()
获取所有方法,包含私有,不包含继承
Class对象.getMethod(String name, Class<?>... parameterTypes)
获取单个公共方法
parameterTypes传参为方法参数类型的Class对象,防止重载
Class对象.getDeclaredMethod(String name, Class<?>... parameterTypes)
获取单个方法,包含私有
parameterTypes传参为方法参数类型的Class对象,防止重载
常用方法
public void setAccessible(boolean flag)
flag设置为true表示暴力反射,可使用私有
public Object invoke(Object obj, Object... args)
obj为调用的对象,args为方法参数列表
如果反射的方法有返回值则返回该返回值,否则返回null
XML
可扩展标记语言
可扩展
标签完全由用户自定义
标记语言
由标签和属性组成
作用
配置文件
标签
围堵标签
<标签></标签>
自闭和标签
<标签/>
属性
值必须用成对引号包裹,单双都可以
语法
xml文档声明必须载第一行第一列
<?xml version="1.0" encoding="UTF-8" ?>
xml只能有一个根标签
注释
<!-- 这是一个注释 -->
特殊字符
< --> "<"> --> ">"
CDATA
处理大量特殊字符
<![CDATA[ 内容 ]]>
解析
DOM4J
需要导包
步骤
1.创建一个从SAX解析器
SAXReader saxReader = new SAXReader();
2.使用解析器的read方法传入I/O流(或File对象) 读取xml文件到内存,返回Document对象
public Document read(InputStream in)
3.使用Document对象的方法getRootElement获取根节点标签
Element getRootElement()
4.解析Element对象
获取标签的属性值
String attributeValue(String name)
获取标签体内容
String getText()
获取子标签
获取多个
List<Element> elements()
获取所有子标签,不包含后代子标签
List<Element> elements(String name)
获取单个
Element element(String name)
如果有多个,默认返回第一个
约束
DTD
缺点
不能对文本进行更细节的约束
一个xml中只能引入一个dtd,不能引入多个,xml无法区分多个dtd中相同元素
Schema
编写
引入
枚举
作用
表示固定几个值,值作为对象,不能进行基本运算,比较安全
原理
枚举本身是多个该枚举类的实例(多例),保证一个类只有这几个对象
使用场景
一般在要表示几个固定选项时使用
格式
public enum 枚举类名{ 枚举项1,枚举项2,枚举项3;}
enum
特点
枚举中如果不给定构造,会有一个私有空参构造
枚举中只能定义私有构造
枚举项必须在第一行有效语句
枚举本质上是一个类,类中可以定义的内容,枚举都可以定义
每个枚举项本质上是该枚举类的一个对象实例
如果枚举类中有抽象方法,则枚举项会作为匿名子类,且必须实现抽象方法
注解
原理
本质
一个接口,JDK1.5出现,用来定义抽象方法
注解中可以定义抽象方法
public abstract 返回值类型 方法名() default 默认值;
public abstract可以省略default指定方法返回默认值(不指定不写)
定义格式
public @interface 注解名{ ... }
使用注解
@注解名(对注解中所有无默认返回值的方法重写)
重写格式
方法名 = 返回值
当需要重写的仅有一个value方法时 value可以省略
当返回值类型是数组,但数组元素只有一个时,大括号可以省略
作用
编译检查(JDK实现)
@Override
重写
@Deprected
过时
@SupressWarning("all")
压制警告
携带数据和框架通信
解析
注解属于字节码层面(需要反射)
Class,Field,Method,Constructor
判断注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
因为一个对象可能有多个注解,所以需要指定注解的Class
获取注解
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
因为一个对象可能有多个注解,所以需要指定注解的Class
注解本质是一个接口,获取到的是注解的实现类
元注解
作用
修饰注解的注解
@Target
如果不写,默认注解可以修饰任何能修饰的东西
使用枚举类ElementType指定可修饰的东西
ElementType
METHOD
方法
FIELD
字段
TYPE
类
@Retention
如果不写,默认策略为CLASS
使用枚举类RetentionPolicy指定生效时期
RetentionPolicy
CLASS
源码和字节码中生效
RUNTIME
源码和字节码及加载到内存的字节码中生效
SOURCE
源码中生效
@Inherited
标注该注解可以被子类继承
@Documented
标注抽成文档时,要不要生成注解的信息
单元测试
Junit
作用
测试代码,白盒测试
使用步骤
导包
给测试方法添加@Test注解就可以独立运行
注意事项
1.测试方法的返回值类型必须为void2.测试方法的修饰符必须public3.测试方法的不能有参数4.测试方法的不能是静态5.测试方法名一般叫: test要测试的方法名6.测试类名一般叫: "要测试的类Test"或者"Test要测试的类"7.junit主要测试异常的, 如果有"异常" 则出现红色, 如果没有异常,一般绿色,除非遇到第8点情况可能出现红色8.测试类想要测结果是否和你预期的一样, 需要用到"断言" Assert, 如果期望的结果和实际结果不一样, 则会红色
其他常用注解
@Before
测试方法执行前一定执行
@After
测试方法执行后一定执行
日志
Log4J
优点
可以通过控制配置文件控制日志打印
可以在控制台记录日志 还可以写入文件
log4j使用了另外一条线程,不影响主线程运行
使用步骤
导包
创建核心对象
Logger logger = LoggerFactory.getLogger(当前工程中任何类的字节码对象)
Logger使用接口,非具体实现类
使用功能打印对应级别的日志
debug
info
warn
error
使用配置文件控制日志打印
日志级别大小
debug < info < warn < error < fatal
0 条评论
下一页