Java 进阶
Java
Java 的一些进阶知识,以及一些常用示例。
Jvm
Jvm组成
Jvm 是个虚拟机,主要由:类装载系统、执行引擎、垃圾回收器组成。
执行引擎,是Java跨平台的核心,负责虚拟机与OS的指令交互工作。
类装载系统
- 加载器工作过程
类加载的过程主要包括:加载、验证、准备、解析、初始化等阶段。
- 加载:将字节码文件读入虚拟机,并堆中生成一个class引用。
- 验证:利用加载完成的class验证文件正确性,以及父类子类是否冲突等。
- 准备:给类变量,以及存在默认值的成员分配空间。
- 解析:生成所有成员的引用,就是把指针连起来。
- 初始化:初始化所有静态变量,或存在默认值的变量,过一次静态代码块。
1~4 都是 c++ 程序在执行,5为java执行程序。
- 加载器分类
- 启动类加载器:引导类装载器进入虚拟机,类似Linux内核文件,c++。
- 扩展类加载器:加载指定Jre目录文件,形成 java基本运行环境,java。
- 系统类加载器: 加载指定路径的字节码文件,虚拟机开机自启程序。
- 自定义加载器:这里需要加载的是未定义的,动态class文件。
- 双亲委派原则
Java 除顶层加载器之外,类加载器是一种子->父的单向循环过程。
主要是为了保证类不会被加载第二次,类似于一种层级的环境变量。
类加载器的主要过程就是重复如下操作:
先到当前class查询缓存是否存在此类,存在则返回,不存在将加载请求抛给父类。
当顶层类依然不存在此类的时候:
// 自定义类加载器--> 遵循双亲委派原则 一般都用这个
public class ExpLoder extends ClassLoader{
// 顶层找不到类的时候,会来这里
@Override
protected Class<?> findClass(String name){
return null;
}
// 将字节流转化为,jvm可识别的文件
@Override
protected final Class<?> defineClass(byte[] b,int off,int len){
return defineClass(null, b, off, len, null);
}
}
// 自定义类加载器 --> 不遵循双亲委派原则
public class ExpLoder extends ClassLoder{
// 重写这个方法会直接破坏双亲原则
@Override
protected Class<?> loadClass(String name,boolean resolve){
//...
}
}
- 线程上下文类加载
本身是并发带来的问题,多个线程各自走双亲委派原则,导致类加载不一致。
所以线程上下文类加载,并不遵循双亲委派原则,而是直接给主线程去加载了。
垃圾回收器
Java 对象执行完毕之后,清除无用空间的操作,这里指堆空间。
- 一般性内存空间整理算法
- 标记清除:标记无用空间,统一清除,易产生碎片空间。
- 复制算法:将使用空间复制到一个新的区域,之后清除整片旧空间,太浪费了。
- 标记整理:标记无用空间,前移所有使用空间,覆盖无用空间,太耗时了。
- 分代算法
-
新生代:每个周期都有大量空间失效,所以用复制算法。
Eden 区:存储 new 的对象,很多对象会当场去世。
from 区:存储从 Eden 区存活下的对象。
to 区:存储上 from 区活虾的对象。- 当 Eden区满了,则开启 GC,执行一次周期。
- 再将 to 区存活对象,移动至老年代。
- 再将 from 区存活对象,移动至 to 区域。
- 先将 Eden 区存对象,移动至 from 区,再清除 Eden 区。
-
老年代:相对而言,每个周期少量空间失效,一般用 标记-整理,或标记-清楚。
空间满了,则开启GC,根据算法不同选择不同的策略。
-
元空间,以前叫永久代,受限于本地内存。
string 常量区就在这里,类似于方法区这个东西。
- 垃圾收集器
-
串行收集器 serial
串行收集器会停止所有线程,简单且高效,适用于单线程、C/S的client端。
新生代-复制,老年代-标记整理。 -
ParNew
本身就是串行收集器的多线程版本,没啥差别,停止所有线程,简单高效。
-
CMS
CMS 是一种基本的以空间换时间的收集器,追求最短响应时间。
- 初始标记:停止所有线程,标记失效空间。
- 并发标记:不会停止任何线程,而是开新线程去获取失效空间地址。
- 重新标记:停止所有线程,将并发标记结果写入标记集合。
- 并发清除:不会停止任何线程,开新线程清除失效的空间。
CMS 会导致吞吐量下降,部分浮动垃圾无法清除,且碎片空间太多了。
-
G1
G1 是对 CMS 的一种改进版本。
- 初始标记:短时间停止所有线程,标记失效空间。
- 并发标记:不会停止任何线程,开新线程获取失效空间地址。
- 最终标记:停不停线程都行,将并发标记结果写入标记集合。
- 筛选回收:停不停线程都可以,开新线程标记-整体失效空间。
G1 相对于 CMS主要是,线程停顿时间可控,且采用标记-整理。
G1 垃圾回收的性能由,线程停顿时间控制。
Jvm 内存
Jvm 内存结构,内存模型。
内存结构
Jvm 内存结构主要分为两大部分。
- 进程区
进程区,亦称为线程共享区,所有线程都可访问。
- 堆区:存储对象用的,被垃圾收集器所管理。
- 方法区:主要存储类一级别的变量,包括类信息,静态变量,方法等,class对象引用在堆区,其指向的就是这里。
- 元空间:存储一些常量,String常量就是在这一个区域。
- 线程区
线程区,亦称线程私有区,仅线程自身可以访问。
- 程序计数器:切换线程上下文所用的标识位,记录行号。
- 本地方法栈:一些OS方法,或其它语言的方法。
- 虚拟机栈:线程执行时用的栈,变量,对象引用,出口,函数链接等等。
内存模型
本身就是一个缓存模型,由主内存,工作内存,栈内存构成。
- 变量类型
- 可被内存模型处理的变量仅包括了实例字段、静态字段和构成数组对象的元素。
- 不包括局部变量与方法参数,这些是线程私有的。
- 线程通信
- 任何一个线程都有自己的缓存,里面存放着常用变量,其他变量在主内存里。
- 线程之间的通信,是其中一个线程将变量写入主内存里,另一个去主内存拿。
- 线程之间的通信必须在主内存里完成,线程之间是无法直接执行通信的。
JMM通信模型:files/jmm.png。
- 操作说明
- lock:将一个变量标识为一条线程独占状态。
- unlock:将一个处于锁定状态的变量释放出来。
- read:将一个变量从主内存传输到线程的工作内存中,以便随后的 load 使用。
- load:将 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
- use:将工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign:把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store:把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write:把 store 操作从工作内存中一个变量的值传送到主内存的变量中。
- 模型性质
被 volatile 的变量的所有读写都是在主存内执行的,且会所有操作会加锁。
- 原子性:所有内存模型操作都是原子性的,即不会干到一半不干了。
- 可见性:被 volatile修饰的变量,任何更改对多有线程都是可见的。
- 顺序性:被 volatile修饰的变量,禁止作内存优化,所有操作都是顺序的。
内存屏障
-
问题所在
-
当下 cpu 一般都存在多级缓存,其查找变量的时候,先在一级缓存中寻找,继续在二级缓存之中寻找,如此类推。虽然缓存有很多,但内存终究就只有一个。多级缓存会带来一个数据不一致的问题。
-
在不同 cpu 执行的不同线程对同一个变量的缓存值不同,为了解决这个问题。
-
-
硬件协议
一般为解决,多级缓存,以及多线程缓存不一致的问题,从 cpu 本身出发的 mesi 协议,可解决缓存不一致的问题。
读屏障:在指令前插入读屏障,可以让高速缓存中的数据失效,强制从主内存取。
-
Jvm内存屏障
Jvm是在代码指令中添加汇编指令,使用cpu的内存屏障协议保证数据的一致性。
1.阻止屏障两侧指令重排序,(Jvm有内存优化的)。
2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
并发
并发的主要依靠多线程来解决,在这个过程中需要锁来维持数据一致性。
锁
锁主要的作用是保证线程安全,但会影响性能。
分类
- 独占锁与共享锁
独占锁:即为写锁,亦称互斥锁,最多只能有一个线程获取此类锁。
共享锁:即为读锁,存在写操作时,只能有一个线程可获取锁;仅存在读操作时,可存在多个线程同时获取锁。
- 悲观锁与乐观锁
悲观锁:默认会发生冲突,所以任何操作都加锁。
乐观锁:默认发生错误较少,所以仅在写操作时加锁。
- 公平锁与非公平锁
公平锁:内部顺序获取锁,不会产生饥饿,但效率较低。
非公平锁:内部会根据规则抢锁,会产生饥饿,但效率高。
- 重入锁
亦称递归锁,锁是以现称为单位获取获释放的。
当线程中某一部分获取锁后,相当于整个线程获取到了锁。
主要是保证了同一个锁,不会因为相同线程的争抢而导致发生死锁。
- 自选锁
当线程获取锁被阻塞时,轮询获取锁。
优点是避免程序切换上下文,缺点是消耗cpu。
- 引用锁,非引用锁
所有被 synchronized 锁住的都是引用类型。
所有被 lock 锁住的都是代码块。
synchronized & ReentrantLock
- synchronized
悲观锁,非公平锁,重入锁。
只有类锁,对象锁,成员锁这三种引用锁,且是虚拟机级别的。任何一个类,对象,成员都有一个内置锁,亦称监听器 monitor。
是一个互斥锁,即同时刻只能有一个线程获得某一级别的锁,其他线程阻塞。
但不同线程或相同线程可获得不同级别的锁,即为重入锁。被synchronized修饰的代码都会根据条件获取指定锁,其他线程只能等锁释放
public class Example{
static int k = 10;
public int m = 10;
public void show(){
//...
}
synchronized static public void sh(){
//...
}
synchronized public void show(){
//...
}
}
// 测试对象
Example exp = new Example();
// 同步 类,静态变量 或调用静态同步方法时,会加类锁。
synchronized(Example.class){};
synchronized(exp.k){};
sh();
// 同步对象,则加对象锁。
synchronized(exp){}
// 同步成员变量,或调用同步成员函数时,加成员锁。
synchronized(exp.m){}
show();
- ReentrantLock
悲观锁,默认非公平锁,可设定为公平锁,可重入锁,。
核心为 AbstractQueueSynchronzer,AQS基于FIFO双向队列实现。
// 此时为,非公平锁
ReentrantLock lock = new ReentrantLock();
// 此时为,公平锁
ReentrantLock lock = new ReentrantLock(true);
- 两者异同
相同之处:都是悲观锁,且都是可重入锁,都是阻塞式锁。
| 类型 | 加锁范围 | 公平锁 | 级别 | 锁问题 |
|---|---|---|---|---|
| synchronized | 类,对象,成员 | 非公平锁 | 虚拟机级别 | 写锁 |
| ReentrantLock | 代码区 | 公平锁,非公平所都可以 | 线程级别 | 可设定读写锁 |
- volatile
volatile 亦是一种线程同步的方式,主要是线程通信。
- 其他
synchronized 由一对 monitorenter/moniterexit 字节码指令实现。
java 锁竞争的基本原则,会根据金证程度来使用权限。
偏向锁->轻量级锁->重量级锁。
线程
多线程是提高并发量、吞吐量的主要手段。
线程同步
- 线程同步
线程同步的方式,可根据消耗按照如下三种方式排序。
// 利用 volatile 同步线程
public class Example implements Runnable{
// 被 volatile 修饰的变量,写操作后必须刷新变量的值
static volatile int k = 20;
@Override
public void run(){
while(true){
System.out.println(Thread.currentThread().getName()+":"+k--);
try{
Thread.currentThread().sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
// 利用 synchronized 同步数据
public class Example implements Runnable{
static int k = 20;
@Override
public void run(){
while(true){
// 成员锁,仅限定这个成员的访问
show();
try{
Thread.currentThread().sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
// 同步方法
synchronized private void show(){
System.out.println(Thread.currentThread().getName()+":"+k--);
}
}
import java.util.concurrent.locks.ReentrantLock;
// 并发量很大的时候,用 ReentrantLock 锁同步代码区
public class Example implements Runnable{
// 同步测试
static int k = 20;
// 同一时间,被 lock 的代码区,只能由一个线程来执行
Lock lock = new ReentrantLock();
@Override
public void run(){
while(true){
try{
// 同步锁开始
lock.lock();
// 输当前线程名称,k的值,之后再 k--
System.out.println(Thread.currentThread().getName()+":"+k--);
}catch(Exception e){
e.printStackTrace();
}finally{
// 同步锁结尾
// 放在这里是为了防止发生异常后直接锁死整个线程组
lock.unlock();
try{
// try catch 块 真他妈是个让人头疼的东西。
Thread.currentThread().sleep(1000);
}catch(InterruptedException d){
d.printStackTrace();
}
}
}
}
}
Example exp = new Example();
// 并发执行多线程,同步数据。
new Thread(exp,"Thread-A").start();
new Thread(exp,"Thread-B").start();
new Thread(exp,"Thread-C").start();
new Thread(exp,"Thread-D").start();
线程池
- 多线程弊端
多线程并发除了可能会影响数据一致性之外,亦会因线程本身而引发异常。
- 实时新建、销毁、切换线程,本身就非常消耗Cpu。
- 可能存在需要短时间内new大量线程,易发生OO,引发宕机。
- 线程缺乏管理,比如定时执行,定期执行,定时中断等等。
- 概念
线程池就是首先创建一些线程,它们的集合称为线程池。
线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程。
程序将任务传给线程池,线程池就会启动一条线程来执行这个任务。
执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
- 工作机制
在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程。
线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
线程池示例
线程池管理接口。
- 缓存线程池
通常用于执行生存周期较短的异步任务。
当任务来的时候,会检测是否有被缓存的线程,有则用缓存的线程,无则新增线程。
所有正在执行的线程都是核心线程,所有被缓存的线程都是非核心线程。
所有被缓存的线程都有超时机制,超过 60秒没有被引用,线程会被回收。
// 初始化线程池,此时线程池内无线程,之后新增的线程都会被缓存,缓存后可复用
ExecutorService cache = Executors.newCachedThreadPool();
for(int i=0; i<10; i++){
Thread.sleep(1000);
// 新增线程-->至线程池中
cache.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currentThread.getName()+"-running");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
}
// 虽然循环 10 次,但实际只有 2 个线程。
- 固定大小的线程池
内部拥有固定数量的可重用线程。
所有线程都是核心线程,不会被回收,任务超过线程上限则进入队列等待。
// 获取系统核数,利用这个创建固定大小的线程池
int corenum = Runtime.getRuntime().availableProcessors();
// 创建 固定数量线程的线程池
ExecutorService fix = Executors.newFixedThreadPool(corenum);
for(int i=0; i<100; i++){
fix.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currentThread.getName()+"-running");
try{
Thread.sleep(1000);
}catch(TnterruptedException e){
e.printStackTrace();
}
}
});
}
- 延迟的周期性线程池
一般为固定数量的可重用线程,外加额外线程,且线程都是周期性执行任务。
其核心线程数量是固定的,空闲亦不会被回收,而非核心线程执行完毕则立即回收。
// 创建固定数量的线程池
ScheduledExecutorService sched = Executors.newScheduledThreadPool(5);
for(int i = 0; i<5; i++) {
sched.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("延迟执行" + Thread.currentThread().getName());
}
}, 5, 1, TimeUnit.SECONDS);
}
// 此处含义为,创建任务后 5 秒之后开始周期性执行,每个周期间隔 1 秒
- 单线程式线程池
内部只有一个线程,且所有任务会按照 FIFO 顺序执行。
内部只有一个核心线程,空闲亦不会被回收。
// 内部只有一个线程
ExecutorService single = Executors.newSingleThreadExecutor();
for(int i = 0;i < 10;i++){
// 任务执行顺序不会改变
single.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
自定义线程池
其上所展示的四种线程池都来自这个类,只不过参数被确定了。
| 类型 | 创建 | 核心线程 |
|---|---|---|
| 缓存线程池 | newCachedThreadPool() | 核心线程与非核心线程动态变化 |
| 固定线程池 | newFixedThreadPool(int n) | 所有线程都是核心线程 |
| 单线程线程池 | newSingleThreadExecutor() | 只有一个核心线程 |
| 周期性线程池 | newScheduledThreadPool(int n) | 固定的核心线程,动态变化的非核心线程 |
- 线程池核心类
// 线程池原始定义方法
public ThreadPoolExecutor(
// 核心线程数量
int corePoolSize,
// 最大线程数量,当核心线程满了之后,会查询这个值来确定是否新增线程
int maximumPoolSize,
// 线程总数大于核心线程数量时,多余空闲线程最多空闲时间,超时则释放资源
long keepAliveTime,
// keepAliveTime 时间单位
TimeUnit unit,
// 超出核心线程时,多余任务的存储位置
BlockingQueue<Runnable> workQueue,
// 创建新线程时使用的工厂
ThreadFactory threadFactory,
// 当线程池达到上限之时,所要执行的策略
// 当任务队列达到最大值被阻塞,且总线程数量达到最大值时,即为上限
RejectedExecutionHandler handler
) {
//...
}
- 线程池一般策略
线程总数 < 核心线程:新开核心线程,任务进入线程。
线程总数 = 核心线程:若存在空闲的核心线程,任务进入线程,否则进入队列。
队列到达上限,线程总数 < 线程最大值:新增非核心线程,任务进入队列。
队列到达上限,线程总数 = 线程最大值:执行饱和策略。
// 此处主要是来存储,超出执行的任务
BlockingQueue<Runnable> task = null;
// 内部是数组,数组必有界
task = new ArrayBlockingQueue<>(100);
// 内部是链表,链表内存是动态化的,但也可设置上限,即有界、无界皆可
task = new LinkedBlockingQueue<>();
// 无缓冲的队列,无界
task = new SynchronousQueue<>();
// 任务到达上限时所要执行的 饱和策略
RejectedExecutionHandler rejection = null;
// 忽略新增任务,新增则抛异常,亦为默认值
rejection = new ThreadPoolExecutor.AbortPolicy();
// 忽略新增任务,新增不会抛异常
rejection = new ThreadPoolExecutor.DiscardPolicy();
// FIFO,删除队列头部任务,之后继续新增
rejection = new ThreadPoolExecutor.DiscardOldestPolicy();
// 新增任务失败后,主线程自己去执行,不要用这玩意儿
// 因为新增任务都需要主线程,才可进入队列或直接进入线程
// 且队列内的任务,都需要主线程分配给各个线程才可执行
rejection = new ThreadPoolExecutor.CallerRunsPolicy();
- 自定义示例
BlockingQueue<Runnable> task = new SynchronousQueue<Runnable>();
RejectedExecutionHandler rej = new ThreadPoolExecutor.AbortPolicy();
ThreadFactory th = Executors.defaultThreadFactory();
// 核心线程池
ExecutorService core = new ThreadPoolExecutor(6,18,60,TimeUnit.SECONDS,task,th,rej);
/*
* 线程池
* execute 实现 Runnable接口创建任务
*/
core.execute(new Runnable(){
@Override
public void run(){
System.out.println("Runnable--execute");
}
});
Future<?> ex = core.submit(new Runnable(){
@Override
public void run(){
System.out.println("running");
System.out.println(0/0);
}
});
/*
* 线程池
* submit 实现 Callable<Object> 接口创建任务,存在返回值
* Future 类可以获取,submit 任务的返回值,但会阻塞至任务结束。
* Future submit Ruuuable 能获取到的只有异常,或null
* Future submit Callable 可以获取到异常以及程序返回值
*/
Future<?> res = core.submit(new Callable<Integer>(){
@Override
public Integer call(){
System.out.println("Runnable-submit");
return 1;
}
});
// 此处会阻塞至,res执行完毕,才会有结果
System.out.println(res.get());
- 线程池异常
Java 所有线程池中的任务发生异常后,整个线程会被回收。
// 新建线程池
ExecutorService core = Executors.newFixedThreadPool(3);
// execute Runnable 系列无法捕获异常,线程会被回收
core.execute(new Runnable{
@Override
public void run(){
System.out.println("runnable -- Exception");
// 此处会发生异常
System.out.println(10/0);
}
});
// submit Runnnable 可捕获异常,线程依然会被回收
Future exp = core.submit(new Runnable(){
@Override
public void run(){
System.out.println("runnable -- Exception");
// 此处会发生异常
System.out.println(10/0);
}
});
// 此处可以捕获线程执行发生的异常
try{
System.out.println("exception"+exp.get());
}catch(Exception e){
e.printStackTrace();
}
特殊操作
Future 是面向任务的一种类,可控制当前任务。
submit 一般配合 Future 使用。
Callable<?>存在返回值,可抛出异常,仅在线程池中使用。
// 新建线程池
ExecutorService core = Executors.newFixedThreadPool(3);
/*
* Future submit callable 系列可控制一个完成的线程生命
*/
Future res = core.submit(new Callable<Integer>{
// callable 一般仅在线程池中使用
@Override
public Integer call(){
System.out.println("submit future");
return 1;
}
})
// Future 可获取当前任务的所有信息
System.out.println(res);
/*
* Future 类亦可以中途取消执行任务
* true 可强行中断,false 不可强行中断
* 返回值,取消结果
*/
Boolean ok = res.cancel(true);
/*
* Future 可获取线程执行结果
* Future 可捕获,任务线程执行时发生的异常
*/
try{
// res.get() 即可获取线程执行结果,阻塞式获取
System.out.println(res.get());
}catch(Exception e){
e.printStackTrace();
}
线程变量
- 局部变量
ThreadLocal,线程级别的HashMap,存储线程局部变量。
- 默认新开线程时,此线程即会被创建。
- 可用在多线程共享变量。
- 数据结构
主要是一些线程安全的数据结构。
Synchronized:所有被修饰的变量都是线程安全的。
StringBuffer:线程安全的字符串结构。
BlockQueue:线程安全的队列。
Vector:线程安全的数据结构。
Collections.synchronizedList:将列表转化为线程安全的,但内部锁粒度过大。
ConcurrentHashMap:内部是分段锁,不会把整个表锁起来。
SynchronizedMap/Hashtable:线程安全,但锁粒度太大。
IO
主要介绍常见的IO知识点。
IO流
- 一般IO
主要分为字节流、字符流。
字节流:即为二进制数据流。
字符流:即为char[]流,一般需要字符编码格式,默认utf-8。
- 核心概念
同步:一种可靠的时序操作,执行操作后,后续任务需要等待。
异步:执行操作后,后续任务无需等待,一般通过事件、回调等方式实现。阻塞:这是线程的状态,仅当条件允许时,线程会进入就绪状态等待执行。
非阻塞:不管 IO是否结束,该返回直接返回。
- IO模型
IO,亦称BIO,是一种同步且阻塞的IO方式,默认IO都是这个模型。
NIO 是一种同步非阻塞的IO方式。
AIO 是一种异步非组赛的IO方式。
BIO:读写时,会阻塞线程,只有操作执行完毕时才会返回结果。
NIO:读写时,不会阻塞线程,只有操作执行完毕时才会返回结果。
AIO:读写时,不会阻塞线程,执行结果会分段返回。
常用类
主要是一些类的常用示例。
Java8 新特性
主要介绍下 Java-8的一些特点。
1.语法糖
// 语法糖,本质上源代码没变,只是减少了代码量
// lambda 系列
new Thread(()->{System.out.println("lambda");}).start();
// lambda 若一行可以写完,则无需;
new Thread(()->System.out.println("lambda")).start();
2.接口默认方法
// 接口内部可以存在默认方法,且可以被重写
public interface Exp{
default void show(){
System.out.println("interface-default-function");
}
}
3.函数式接口
函数式接口,就是一个接口默认一个方法,可以即时定义使用。
调用形式有一种,面向切面编程的思想。
// 函数式接口是一种特殊的接口
// 必须被 FunctionalInterface 注解标注,且内部只能存在一个方法
@FunctionalInterface
public interface Exp{
public void show();
}
// 定义 接口内的 show 方法
Exp exp = () -> System.out.println("Function interface");
exp.show();
@FunctionalInterface
public interface Example{
public int sh(int k);
}
// 不同的函数接口,就是不同的切面,且此处 return 可省略
Example example = (x) -> x++ ;
Example exp = (x) -> x*10;
System.out.println(example.sh(10)+"-"+exp.sh(10));
// java8 自带一些函数式接口
Consumer<String> ex = (x) -> System.out.println(x);
ex.accept("hello");
Suppiler<String> ex = () -> "hello";
System.out.println(ex.get());
// 这里就是动态代理
public void proxyFun(Consumer<String> consumer,String msg){
consumer.accept(msg);
}
// 函数式编程模式
proxyFun((x)->System.out.println(x),"123465");
4.时间API
// 新增一组时间API
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.millis());
//...
Date date = Date.from(clock.instance());
5.Stream
增加了部分类似,sql语法的方法。
6.多重注解
允许对注解单位加多重注解,面向多切面编程。
常见运算
- Number
此下的方法适用于Number的所有子类。
// 将 string 解析为 Number
Integer.parseInt("123");
// 将 Number 转化为 String
Integer.toString(123);
// 比较两个 Number 的大小,相等 0 ,小于 -1,大于 1
Integer.compare(1,2);
- Math
此下主要包括一些常用的计算方法。
// Number 求绝对值
Math.abs(-1000);
// Number 四舍五入
Math.round();
// Number 四舍五入 保留一位小数
Math.ceil();
// Math.max(); Math.min();
// ...
String
String 是最为通用的类型。
- Character
char 类型可以看作是 int 类型。
// char 类型可以看作特殊的类型
char a = 100;
// 将 char 转化为长度为一的string
Character.toString(a);
- String
String 是一个特殊的类型,实际是个常量,只要改变相当于一次新建。
// 将 Number 类型转化为 String
String p = String.valueOf(123);
String p = "abc";
// p 转大写
p = p.toUpperCase();
String p = "ABC";
// p 转小写
p = p.toLowerCase();
// 相等为 0,adc-abd -1,abc-abb 1
p.compareTo("ABD");
// 比较 String 是否相同
"sd".equals("sd");
// 比较 String 是否相同,忽略大小写
"SD".equalsIgnoreCase("sd");
// ...
- String 常量池
常量池中的变量都是 final,即不可更改的变量,本身是一个char数组。
-
String p = "hellow";此种方式创建string的时候,先到常量池中搜索,存在相同的string则直接返回引用,否则在常量池之中创建相应的 string,并返回引用。 -
String p = new String("hellow");此种方式创建string的时候,先在常量池中搜索,若存在则直接返回引用,否则在堆区创建相应的对象。
// 此种方式创建的string便是在常量池中间的变量
String p = "hellow";
// p q 都指向常量池中的一个变量
String q = "hellow";
- StringBuilder & StringBuffer
不同于 String 类型,其本身是对象,任何操作都是针对对象本身,不会出现疯狂召唤GC的情况。
- StringBuilder:线程不安全。
- StringBuffer:线程安全,其所有方法都被 synchronized 所修饰。
时间类型
时间类型是最为基本的常用类型。
// 获取时间对象
Date date = new Date();
// 这里获取的是一个完整的时间对象,一般用不到
Calendar calendar = Calendar.getInstance();
// 这里获取的就只是时间对象
Date cal = Calendar.getInstance().getTime();
// 时间戳 13 位
date.getTime();
Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
// 时间格式化类型
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 格式化时间,Date对象即可
sdf.format(date);
sdf.format(cal);
流类型
Java 本身分为字符流、字节流,如下为基本的抽象类。
| 流类型 | in | out |
|---|---|---|
| 字节流 | InputStream | OutputStream |
| 字符流 | Reader | Writer |
// 常见流
FileInputStream input;
FileOutputStream output;
FileReader reader;
FileWriter writer;
//序列化的流
FileOutputStream out = new FileOutputStream("./exp.txt");
ObjectOutputStream obj = new ObjectOutputStream(out);
obj.write(new Object());
obj.close();
out.close();
// 反序列化的流
FileInputStream in = new FileInputStream("./exp.txt");
ObjectInputStream obj = new ObjectInputStream(in);
Object exp = obj.readObject();
obj.close();
in.close();
// IOException
集合类型
集合类型是一种常用数据结构类型,动态存储空间,且面向对象设计。
Collection
Collection接口采用线性列表的方式存储数据。
- List
List接口用于存储有序,可重复的数据。
/*
* ArrayList<?> 实现类
* 优点:实际结构是动态数组,查询快,增删慢。
* 缺点:线程不安全。
*/
ArrayList<String> list = new ArrayList<String>();
/*
* Vector<?> 实现类
* 优点:实际结构是动态数组,查询快,增删慢。
* 缺点:线程安全。
*/
Vector<String> vector = new Vector<String>();
/*
* LinkedList<?> 实现类
* 优点:实际结构是链表,查询慢,增删快。
* 缺点:线程不安全。
*/
LinkedList<String> linklist = new LinkedList<String>();
/*
* Collections.synchronizedList(),静态化方法
* 此方法的作用是将,任何基于List接口实现的类转化为线程安全的类。
*/
List safelist = Collections.synchronizedList(list);
// 新增 移除 索引 ...
- Set
Set接口用于不可重复的数据。
/*
* HashSet<?> 实体类
* 内部就是个 hash表,依赖 hashCode(),equals()方法保证唯一性。
* 主要存储 无序,不重复的数据。
*/
HashSet<String> set = new HashSet<String>();
/*
* LinkedHashSet<?> 实体类
* 内部是一个由 hash表,和链表结构组成。
* 主要存储 有序,不可重复的数据。
*/
LinkedHashSet<String> linkset = new LinkedHashSet<String>();
/*
* TreeSet<?> 实体类
* 内部就是大名鼎鼎的红黑树。
* 主要存储 有序,不可重复的数据。
*/
TreeSet<String> tree = new TreeSet<String>();
- Queue
Queue接口提供阻塞式,以及非阻塞式队列。
/*
* BlockingQueue<?> 接口
* 这个接口下的队列都是阻塞式的。
*/
BlockingQueue blockqueue;
/*
* ConcurrentLinkedQueue<?> 实体类
* 这个类提供非阻塞式的队列。
*/
ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<String>();
Map
Map 接口采用键值对映射的方式存储数据。
/*
* HashMap<?,?> 实体类
* 内部就是 hash表。
*/
HashMap<String,String> map = HashMap<String,String>();
/*
* LinkedHashMap<?,?> 实体类
* 内部键值映射为 hash表,外部键排序去重为链表。
* 是一个键有序的,hash表。
*/
LinkedHashMap<String,String> m = new LinkedHashMap<String,String>();
Thread
java 线程,其优点在于并发,缺点在于相较于协程其更复杂且占用资源较大。
- Runnable 接口
Runnable 接口核心在于,执行线程资源申请,并在新资源中间执行线程。
Runnable 接口内仅定义 run方法,线程管理需要手动定义。
// Runnable 接口实现过程
public class Example implements Runnable{
// 线程核心方法
@Override
public void run(){
while(true){
//...
}
}
}
// 实例化对象,内部没有定义线程管理
Example exp = new Example();
// 没有线程管理策略,仅能直接打开线程,其他操作做不了
exp.run();
// Runnable 接口,管理
public class Demo implements Runnable{
private boolean flag = false;
public Demo(){
//...
}
// 线程核心方法
@Override
public void run(){
while(true){
//...
if(this.flag){
break;
}
}
}
// 此时打开线程
public void start(){
this.run();
}
// 此时会直接退出线程而并非挂起线程
public void stop(){
this.flag = true;
}
}
// 实例化对象,Demo内部实现了部分线程管理
Demo demo = new Demo();
// 开线程
demo.start();
// 关闭线程,线程处于死亡状态
demo.stop();
- Thread
Thread 类一般需要重写 run()方法来使用。
public class Example extends Thread{
@Override
public void run(){
//...
}
}
// Thread 类自带线程管理方法
new Example().start();
public class Demo extends Runnable{
@Override
public void run(){
//...
try{
// Thread.currentThread(),静态方法获取当前线程引用
Thread.currentThread().sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
Demo demo = new Demo();
// 利用 Thread类以及实现Runnable接口的类的对象创建线程
// 线程可以命名,也不命名
Thread th = new Thread(demo,"thread-1");
// 创建线程,命名线程,开启线程
new Thread(demo).start();
// 简写开线程,name字段可以不加
new Thread(
new Runnable(){
@Override
public void run(){
//...
}
}
).start();
// lambda 简写创建线程
new Thread(() -> { while(true){System.out.println(123);} }).start();
特殊
一些特殊的类。
特殊数据结构
Hash 亦称哈希,是一种将任意字符串转化为定长字符串,本质是一种压缩映射。
Hash 亦属于数字签名,或称信息摘要算法中的一个。
Object obj = new Object();
// java 中任何一个对象都有其相应的 hash 值。
obj.hashCode();
- HashMap
Map 是一种 key:value 键值对形式的对象。
1.底层原理
默认是一个数组,且数组内部可嵌套其他数据结构。
// 1. 获取 hashCode()
int hash = obj.hashCode();
// 2. 用数组长度对 hash 取模运算,主要是获取数组下标
int i = hash%len;
// 或执行与运算,数组长度要减 1,现版是这个,但效果和取模一样
int i = hash&(len-1);
// 3. 根据 i 存入数组内部,此处是链表
// 当后续新的对象已被存到这里时,加入链表即可
// 当链表长度超 8,则变为红黑树,不然查询太慢
// 当红黑树实际元素少于 6,则退化为链表
// 4. 获取对象到这里时,利用 equals 方法即可
2.哈希碰撞与扩容
哈希碰撞即为两个对象哈希值相同而产生的异常。
HashMap 数组默认 16,键值对 64。
超过最大阈值 75%,则扩容,空间大一倍。

浙公网安备 33010602011771号