Java 的一些进阶知识,以及一些经常使用示例。java
Jvm 是个虚拟机,主要由:类装载系统、执行引擎、垃圾回收器组成。c++
执行引擎,是Java跨平台的核心,负责虚拟机与OS的指令交互工做。算法
类加载的过程主要包括:加载、验证、准备、解析、初始化等阶段。sql
1~4 都是 c++ 程序在执行,5为java执行程序。编程
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 内存结构主要分为两大部分。
进程区,亦称为线程共享区,全部线程均可访问。
线程区,亦称线程私有区,仅线程自身能够访问。
自己就是一个缓存模型,由主内存,工做内存,栈内存构成。
JMM通讯模型:files/jmm.png。
被 volatile 的变量的全部读写都是在主存内执行的,且会全部操做会加锁。
问题所在
当下 cpu 通常都存在多级缓存,其查找变量的时候,先在一级缓存中寻找,继续在二级缓存之中寻找,如此类推。虽然缓存有不少,但内存终究就只有一个。多级缓存会带来一个数据不一致的问题。
在不一样 cpu 执行的不一样线程对同一个变量的缓存值不一样,为了解决这个问题。
硬件协议
通常为解决,多级缓存,以及多线程缓存不一致的问题,从 cpu 自己出发的 mesi 协议,可解决缓存不一致的问题。
读屏障:在指令前插入读屏障,可让高速缓存中的数据失效,强制从主内存取。
Jvm内存屏障
Jvm是在代码指令中添加汇编指令,使用cpu的内存屏障协议保证数据的一致性。
1.阻止屏障两侧指令重排序,(Jvm有内存优化的)。
2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
并发的主要依靠多线程来解决,在这个过程当中须要锁来维持数据一致性。
锁主要的做用是保证线程安全,但会影响性能。
独占锁:即为写锁,亦称互斥锁,最多只能有一个线程获取此类锁。
共享锁:即为读锁,存在写操做时,只能有一个线程可获取锁;仅存在读操做时,可存在多个线程同时获取锁。
悲观锁:默认会发生冲突,因此任何操做都加锁。
乐观锁:默认发生错误较少,因此仅在写操做时加锁。
公平锁:内部顺序获取锁,不会产生饥饿,但效率较低。
非公平锁:内部会根据规则抢锁,会产生饥饿,但效率高。
亦称递归锁,锁是以现称为单位获取获释放的。
当线程中某一部分获取锁后,至关于整个线程获取到了锁。
主要是保证了同一个锁,不会由于相同线程的争抢而致使发生死锁。
当线程获取锁被阻塞时,轮询获取锁。
优势是避免程序切换上下文,缺点是消耗cpu。
全部被 synchronized 锁住的都是引用类型。
全部被 lock 锁住的都是代码块。
悲观锁,非公平锁,重入锁。
只有类锁,对象锁,成员锁这三种引用锁,且是虚拟机级别的。任何一个类,对象,成员都有一个内置锁,亦称监听器 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();
悲观锁,默认非公平锁,可设定为公平锁,可重入锁,。
核心为 AbstractQueueSynchronzer,AQS基于FIFO双向队列实现。
// 此时为,非公平锁 ReentrantLock lock = new ReentrantLock(); // 此时为,公平锁 ReentrantLock lock = new ReentrantLock(true);
相同之处:都是悲观锁,且都是可重入锁,都是阻塞式锁。
类型 | 加锁范围 | 公平锁 | 级别 | 锁问题 |
---|---|---|---|---|
synchronized | 类,对象,成员 | 非公平锁 | 虚拟机级别 | 写锁 |
ReentrantLock | 代码区 | 公平锁,非公平所均可以 | 线程级别 | 可设定读写锁 |
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();
多线程并发除了可能会影响数据一致性以外,亦会因线程自己而引起异常。
线程池就是首先建立一些线程,它们的集合称为线程池。
线程池能够很好地提升性能,线程池在系统启动时即建立大量空闲的线程。
程序将任务传给线程池,线程池就会启动一条线程来执行这个任务。
执行结束之后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程。
线程池在拿到任务后,就在内部寻找是否有空闲的线程,若是有,则将任务交给某个空闲的线程。
一个线程同时只能执行一个任务,但能够同时向一个线程池提交多个任务。
线程池管理接口。
一般用于执行生存周期较短的异步任务。
当任务来的时候,会检测是否有被缓存的线程,有则用缓存的线程,无则新增线程。
全部正在执行的线程都是核心线程,全部被缓存的线程都是非核心线程。
全部被缓存的线程都有超时机制,超过 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知识点。
主要分为字节流、字符流。
字节流:即为二进制数据流。
字符流:即为char[]流,通常须要字符编码格式,默认utf-8。
同步:一种可靠的时序操做,执行操做后,后续任务须要等待。
异步:执行操做后,后续任务无需等待,通常经过事件、回调等方式实现。阻塞:这是线程的状态,仅当条件容许时,线程会进入就绪状态等待执行。
非阻塞:无论 IO是否结束,该返回直接返回。
IO,亦称BIO,是一种同步且阻塞的IO方式,默认IO都是这个模型。
NIO 是一种同步非阻塞的IO方式。
AIO 是一种异步非组赛的IO方式。
BIO:读写时,会阻塞线程,只有操做执行完毕时才会返回结果。
NIO:读写时,不会阻塞线程,只有操做执行完毕时才会返回结果。
AIO:读写时,不会阻塞线程,执行结果会分段返回。
主要是一些类的经常使用示例。
主要介绍下 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的全部子类。
// 将 string 解析为 Number Integer.parseInt("123"); // 将 Number 转化为 String Integer.toString(123); // 比较两个 Number 的大小,相等 0 ,小于 -1,大于 1 Integer.compare(1,2);
此下主要包括一些经常使用的计算方法。
// Number 求绝对值 Math.abs(-1000); // Number 四舍五入 Math.round(); // Number 四舍五入 保留一位小数 Math.ceil(); // Math.max(); Math.min(); // ...
String 是最为通用的类型。
char 类型能够看做是 int 类型。
// char 类型能够看做特殊的类型 char a = 100; // 将 char 转化为长度为一的string Character.toString(a);
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"); // ...
常量池中的变量都是 final,即不可更改的变量,自己是一个char数组。
String p = "hellow";
此种方式建立string的时候,先到常量池中搜索,存在相同的string则直接返回引用,不然在常量池之中建立相应的 string,并返回引用。
String p = new String("hellow");
此种方式建立string的时候,先在常量池中搜索,若存在则直接返回引用,不然在堆区建立相应的对象。
// 此种方式建立的string即是在常量池中间的变量 String p = "hellow"; // p q 都指向常量池中的一个变量 String q = "hellow";
不一样于 String 类型,其自己是对象,任何操做都是针对对象自己,不会出现疯狂召唤GC的状况。
时间类型是最为基本的经常使用类型。
// 获取时间对象 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接口采用线性列表的方式存储数据。
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接口用于不可重复的数据。
/* * 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接口提供阻塞式,以及非阻塞式队列。
/* * BlockingQueue<?> 接口 * 这个接口下的队列都是阻塞式的。 */ BlockingQueue blockqueue; /* * ConcurrentLinkedQueue<?> 实体类 * 这个类提供非阻塞式的队列。 */ ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<String>();
Map 接口采用键值对映射的方式存储数据。
/* * HashMap<?,?> 实体类 * 内部就是 hash表。 */ HashMap<String,String> map = HashMap<String,String>(); /* * LinkedHashMap<?,?> 实体类 * 内部键值映射为 hash表,外部键排序去重为链表。 * 是一个键有序的,hash表。 */ LinkedHashMap<String,String> m = new LinkedHashMap<String,String>();
java 线程,其优势在于并发,缺点在于相较于协程其更复杂且占用资源较大。
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 类通常须要重写 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();
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%,则扩容,空间大一倍。