ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] > # 本文关键字 > 1. Java中的线程、锁 > - 生产者消费者模式 > - ThreadLocal > 2. Java集合 > 3. Java异常处理机制 > 4. 正则表达式 # 1、Java多线程 ### 1、什么是多线程?使用多线程有什么好处? > **进程**:是系统进行资源分配和调度的基本单位,在Android中表现为一个APP; > **线程**:是程序执行的最小单元,是进程的一个实体,一个进程中可以有多个线程; > **多线程**:是进程中同时又多个线程在同时运行。 > **优点** :使用多线程最直接的好处就是提高了程序的执行效率 > **生命周期**:新建状态 就绪状态 运行状态 阻塞状态 死亡状态 ### 2、多线程锁:synchronized()、 Lock(接口) lock()/unlock() > 1.不加锁执行有什么现象 > 2.加锁后执行有什么不同 > 3.若需两个线程执行中产生交叉,并输出交叉结果,退出应如何实现? >实例:定义三个变量i=0;a=3;b=5;和两个方法 ### 3、wait(),notify(),join(),yield() **1.基础概念** > wait():**必须获得对象锁**,一般与synchronized关键字一起使用,使当前线程进入到阻塞状态,并释放当前锁 > notify()/notifyAll():唤醒单个/所有被阻塞的线程(**必须获得对象锁**),使这些线程进入到就绪状态,即可运行状态,等待cpu执行 > join():使当前线程等待调用该方法的线程结束,再执行后续的操作 > yield():告诉处理器当前线程可以暂停,暂停后直接进入到就绪态,不会释放当前锁,使同优先级或高优先级的线程可以运行,通常在debug或者测试环境下使用 **2.主线程需等子线程结束应如何实现(分别使用wait()、notify()和join()实现)** **3.yeild()的理解,什么情况下使用?** ```java /** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield(); ``` > 根据官方的API介绍,我们可以知道:yeild ( ) 方法是**给调度程序发送了一个当前线程可以让出正在使用的处理器的暗示,当然调度程序可以根据当时的情况,选择是否忽略这个暗示;通常在开发中,yield()函数用在测试或者debug过程中** ### 4、可重入锁、ReentrantReadWriteLock > * **read锁和write锁互斥** > * **read/write锁可以多次进入** > * **synchronized也是可重入锁** ### 5、线程池 **使用线程池的优点:** > * **重用**线程池中的线程,避免线程的创建和销毁带来的性能消耗; > * 有效控制线程池的**最大并发数**,避免大量的线程之间因互相抢占系统资源而导致阻塞现象; > * 进行**线程管理**,提供定时/循环间隔执行等功能。 a.Executors的**类型:** ``` //创建一个单一线程池:线程以队列顺序来执行。 ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建一个定长线程池,超出的线程会在队列中等待。 ExecutorService threadPool = Executors.newFixedThreadPool(2); //创建一个定长线程池,支持定时及周期性任务执行。 ExecutorService threadPool = Executors.newScheduledThreadPool(3); //创建一个无界线程池:可进行线程自动回收,可存放线程数量最大值为Integer.MAX_VALUE ExecutorService threadPool = Executors.newCachedThreadPool(); ``` b.在阿里开发手册中说明了Executors各个方法的**弊端**: > * newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。 > > > * newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。 c.**解决方式**:改用ThreadPoolExecutor创建线程池,便于明确线程池的运行规则,规避资源耗尽的风险。 其中,Executor、ThreadPoolExecutor、ScheduledExecutorService和ScheduledThreadPoolExecutor关系如下: ``` java.util.concurrent.Executor : 负责线程的使用与调度的根接口 |–ExecutorService:Executor的子接口,线程池的主要接口 |–ThreadPoolExecutor:ExecutorService的实现类 |–ScheduledExecutorService:ExecutorService的子接口,负责线程的调度 |–ScheduledThreadPoolExecutor:继承了ThreadPoolExecutor实现了ScheduledExecutorService ``` d.ThreadPoolExecutor的**构造函数:** ``` public ThreadPoolExecutor(int corePoolSize, //核心线程数量 int maximumPoolSize, //最大线程数量 long keepAliveTime, //线程的存活时间(默认只对非核心线程有效,可设置是否对核心线程生效) TimeUnit unit, //存活时间单位 有TimeUnit.MILLSECONDS,TimeUnit.SECOND等 BlockingQueue<Runnable> workQueue, //线程池的任务队列 ThreadFactory threadFactory, //线程工厂,用来创建新的线程 RejectedExecutionHandler handler) //线程池无法执行新任务时,用来抛出异常 ``` **推荐阅读**: [JDK 源码解析--Executors、ExecutorService、ThreadPoolExecutor 线程池](https://blog.csdn.net/wenniuwuren/article/details/51429120 "JDK 源码解析--Executors、ExecutorService、ThreadPoolExecutor 线程池") [要点提炼|开发艺术之线程](https://www.jianshu.com/p/ab77a2e83c52 "要点提炼|开发艺术之线程")[](https://blog.csdn.net/wenniuwuren/article/details/51429120) ### 6、ThreadLocal 每个线程中都有一份的线程变量,作用域在本线程中。 核心方法有: ``` protected T initialValue() {return null;} //一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法 public T get() {} //获取ThreadLocal在当前线程中保存的变量副本 public void set(T value) {} //设置当前线程中变量的副本 public void remove() {} //移除当前线程中变量的副本 ``` 在使用get()前必须先进行set() 或者重写 *initialValue() 方法* 推荐阅读:[http://www.cnblogs.com/dolphin0520/p/3920407.html](http://www.cnblogs.com/dolphin0520/p/3920407.html "http://www.cnblogs.com/dolphin0520/p/3920407.html") # 2、生产者和消费者模式 **生产者和消费者在同一时间段内共用同一存储空间,生产者向该空间里生产数据,而消费者从该空间中取走数据。** **实现方式:** * 使用wait()和notify()实现 * ``` /** * 通过wait()和notify() 实现的生产者和消费者模型 * Created by duoshilin on 2018/12/18. */ public class ProducerConsumer { public static void main(String[] args) { Queue<Integer> queue = new LinkedList<>(); new Producer("P-1",queue,5).start(); new Producer("P-2",queue,5).start(); new Consumer("C-1",queue,5).start(); new Consumer("C-2",queue,5).start(); new Consumer("C-3",queue,5).start(); } public static class Producer extends Thread{ private Queue<Integer> queue;//缓存队列 String name; //生产出的产品名 int maxSize; //最大生产量 volatile static int i=1; //表示产品编号 public Producer(String name, Queue<Integer> queue, int maxSize) { super(name); this.queue = queue; this.name = name; this.maxSize = maxSize; } @Override public void run() { while (true){ synchronized (queue){ while (queue.size() == maxSize){ try { System.out.println("缓存队列已满,生产者["+name+"]正在歇息,等待消费者消费..."); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //System.out.println("生产者["+name+"] 正在生产第 "+i+" 件商品..."); //模拟生产的耗时操作 try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } queue.offer(i); System.out.println("生产者["+name+"] 生产了第 "+(i++)+" 件商品..."); queue.notifyAll(); //使当前线程释放锁,让其他线程有机会执行,模拟出生产和消费可同时进行的效果 try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static class Consumer extends Thread{ private Queue<Integer> queue;//缓存队列 String name; //当前消费的产品名 int maxSize; public Consumer(String name, Queue<Integer> queue, int maxSize) { super(name); this.queue = queue; this.name = name; this.maxSize = maxSize; } @Override public void run() { while (true){ synchronized (queue){ while (queue.isEmpty()){ try { System.out.println("缓存队列已空,消费者["+name+"]正在等待新的商品..."); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //模拟消费的耗时操作 try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } int x = queue.poll(); System.out.println("消费者["+name+"] 使用了第 "+x+" 件商品..."); queue.notifyAll(); //使当前线程释放锁,让其他线程有机会执行,模拟出生产和消费可同时进行的效果 try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } ``` * 运行效果 ``` 生产者[P-1] 生产了第 1 件商品... 消费者[C-3] 使用了第 1 件商品... 缓存队列已空,消费者[C-2]正在等待新的商品... 缓存队列已空,消费者[C-1]正在等待新的商品... 生产者[P-2] 生产了第 2 件商品... 消费者[C-1] 使用了第 2 件商品... 缓存队列已空,消费者[C-2]正在等待新的商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 3 件商品... 生产者[P-2] 生产了第 4 件商品... 消费者[C-3] 使用了第 3 件商品... 消费者[C-2] 使用了第 4 件商品... 缓存队列已空,消费者[C-1]正在等待新的商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-2] 生产了第 5 件商品... 生产者[P-1] 生产了第 6 件商品... 消费者[C-3] 使用了第 5 件商品... 消费者[C-1] 使用了第 6 件商品... 缓存队列已空,消费者[C-2]正在等待新的商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 7 件商品... 生产者[P-2] 生产了第 8 件商品... 消费者[C-3] 使用了第 7 件商品... 消费者[C-2] 使用了第 8 件商品... 缓存队列已空,消费者[C-1]正在等待新的商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-2] 生产了第 9 件商品... 生产者[P-1] 生产了第 10 件商品... 消费者[C-3] 使用了第 9 件商品... 消费者[C-1] 使用了第 10 件商品... 缓存队列已空,消费者[C-2]正在等待新的商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 11 件商品... 生产者[P-2] 生产了第 12 件商品... 消费者[C-3] 使用了第 11 件商品... 消费者[C-2] 使用了第 12 件商品... 缓存队列已空,消费者[C-1]正在等待新的商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-2] 生产了第 13 件商品... 生产者[P-1] 生产了第 14 件商品... 消费者[C-3] 使用了第 13 件商品... 消费者[C-1] 使用了第 14 件商品... 缓存队列已空,消费者[C-2]正在等待新的商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 15 件商品... ``` * 使用Lock(ReentrantLock) 和 Condition 实现的生产者消费者模式 * > 使用lock.lock() 和 lock.unlock() 为代码加锁和释放锁,将需要加锁的代码放在这两行代码之间 * ``` /** * 使用Lock 和 Condition 实现的生产者消费者模式 * Created by duoshilin on 2018/12/18. */ public class ProducerConsumerByLock { private static int i = 1; private static Lock lock = new ReentrantLock(); private static Condition pCondition = lock.newCondition(); private static Condition cCondition = lock.newCondition(); private static Queue<Integer> queue = new LinkedList<>(); public static void main(String[] args) { new Producer("P-1",queue,5).start(); new Producer("P-2",queue,5).start(); new Consumer("C-1",queue,5).start(); new Consumer("C-2",queue,5).start(); new Consumer("C-3",queue,5).start(); } /** * 生产者 */ public static class Producer extends Thread{ Queue<Integer> queue; int maxSize; String name; public Producer(String name, Queue<Integer> queue, int maxSize) { super(name); this.queue = queue; this.maxSize = maxSize; this.name = name; } @Override public void run() { while (true){ lock.lock();//加锁 while (queue.size() == maxSize){ try { System.out.println("缓存队列已满,生产者["+name+"]正在歇息,等待消费者消费..."); pCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //模拟生产的耗时操作 try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } queue.offer(i); System.out.println("生产者["+name+"] 生产了第 "+(i++)+" 件商品..."); //唤醒所有的生产者、消费者 pCondition.signalAll(); cCondition.signalAll(); //休息 try { pCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock();//释放锁 } } } public static class Consumer extends Thread{ Queue<Integer> queue; int maxSize; String name; public Consumer(String name, Queue<Integer> queue, int maxSize) { super(name); this.queue = queue; this.maxSize = maxSize; this.name = name; } @Override public void run() { while (true){ lock.lock(); while (queue.isEmpty()){ try { System.out.println("缓存队列已空,消费者["+name+"]正在等待新的商品..."); cCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //模拟消费的耗时操作 try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } int x = queue.poll(); System.out.println("消费者["+name+"] 使用了第 "+x+" 件商品..."); pCondition.signalAll(); cCondition.signalAll(); //休息 try { cCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); } } } } ``` 运行效果如下: ``` 生产者[P-1] 生产了第 1 件商品... 生产者[P-2] 生产了第 2 件商品... 消费者[C-1] 使用了第 1 件商品... 消费者[C-2] 使用了第 2 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 3 件商品... 生产者[P-2] 生产了第 4 件商品... 消费者[C-1] 使用了第 3 件商品... 消费者[C-2] 使用了第 4 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 5 件商品... 生产者[P-2] 生产了第 6 件商品... 消费者[C-1] 使用了第 5 件商品... 消费者[C-2] 使用了第 6 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 7 件商品... 生产者[P-2] 生产了第 8 件商品... 消费者[C-1] 使用了第 7 件商品... 消费者[C-2] 使用了第 8 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 9 件商品... 生产者[P-2] 生产了第 10 件商品... 消费者[C-1] 使用了第 9 件商品... 消费者[C-2] 使用了第 10 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 11 件商品... 生产者[P-2] 生产了第 12 件商品... 消费者[C-1] 使用了第 11 件商品... 消费者[C-2] 使用了第 12 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 13 件商品... 生产者[P-2] 生产了第 14 件商品... 消费者[C-1] 使用了第 13 件商品... 消费者[C-2] 使用了第 14 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 15 件商品... 生产者[P-2] 生产了第 16 件商品... 消费者[C-1] 使用了第 15 件商品... 消费者[C-2] 使用了第 16 件商品... 缓存队列已空,消费者[C-3]正在等待新的商品... 生产者[P-1] 生产了第 17 件商品... 生产者[P-2] 生产了第 18 件商品... 消费者[C-1] 使用了第 17 件商品... ``` * 使用阻塞队列(LinkedBlockingQueue)实现 > LinkedBlockingQueue是一个阻塞的线程安全的队列,底层采用链表实现。 > >   LinkedBlockingQueue构造的时候若没有指定大小,则默认大小为Integer.MAX_VALUE,当然也可以在构造函数的参数中指定大小。LinkedBlockingQueue不接受null。 > > 添加元素的方法有三个:add,put,offer,且这三个元素都是向队列尾部添加元素的意思。    > > add方法在添加元素的时候,若超出了队列的长度会直接抛出异常:       > > put方法,若向队尾添加元素的时候发现队列已经满了会发生阻塞一直等待空间,以加入元素。  > > offer方法在添加元素时,如果发现队列已满无法添加的话,会直接返回false。   > > 从队列中取出并移除头元素的方法有:poll,remove,take。      > >         poll: 若队列为空,返回null。 > >         remove:若队列为空,抛出NoSuchElementException异常。 > >         take:若队列为空,发生阻塞,等待有元素。 ``` /** * 生产者消费者模式:使用{@link java.util.concurrent.BlockingQueue}实现 * Created by duoshilin on 2018/12/18. */ public class ProducerConsumerByBQ { private static volatile int i = 1; private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5); public static void main(String[] args) { Thread p1 = new Producer("P-1", queue, 5); Thread p2 = new Producer("P-2", queue, 5); p1.start(); p2.start(); new Consumer("C-1", queue, 5).start(); new Consumer("C-2", queue, 5).start(); new Consumer("C-3", queue, 5).start(); try { p1.join(); p2.join(); } catch (InterruptedException e) { e.printStackTrace(); } Iterator iterator = queue.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } /** * 生产者 */ public static class Producer extends Thread { LinkedBlockingQueue<Integer> queue; int maxSize; String name; public Producer(String name, LinkedBlockingQueue<Integer> queue, int maxSize) { super(name); this.queue = queue; this.maxSize = maxSize; this.name = name; } @Override public void run() { while (true) { try { synchronized (Producer.class){ queue.put(i); System.out.println("生产者[" + name + "] 生产了第 " + (i++) + " 件商品..."); } Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static class Consumer extends Thread { LinkedBlockingQueue<Integer> queue; int maxSize; String name; public Consumer(String name, LinkedBlockingQueue<Integer> queue, int maxSize) { super(name); this.queue = queue; this.maxSize = maxSize; this.name = name; } @Override public void run() { while (true) { try { synchronized (Consumer.class){ int x = queue.take(); System.out.println("消费者[" + name + "] 使用了第 " + x + " 件商品..."); } Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } } } ``` 运行效果: * ``` 生产者[P-1] 生产了第 1 件商品... 生产者[P-2] 生产了第 2 件商品... 消费者[C-1] 使用了第 1 件商品... 消费者[C-2] 使用了第 2 件商品... 生产者[P-1] 生产了第 3 件商品... 消费者[C-3] 使用了第 3 件商品... 生产者[P-2] 生产了第 4 件商品... 消费者[C-1] 使用了第 4 件商品... 生产者[P-2] 生产了第 5 件商品... 消费者[C-2] 使用了第 5 件商品... 生产者[P-1] 生产了第 6 件商品... 消费者[C-2] 使用了第 6 件商品... 生产者[P-2] 生产了第 7 件商品... 消费者[C-3] 使用了第 7 件商品... 生产者[P-2] 生产了第 8 件商品... 消费者[C-2] 使用了第 8 件商品... 生产者[P-1] 生产了第 9 件商品... 消费者[C-1] 使用了第 9 件商品... 生产者[P-1] 生产了第 10 件商品... 消费者[C-3] 使用了第 10 件商品... 生产者[P-2] 生产了第 11 件商品... 消费者[C-2] 使用了第 11 件商品... 生产者[P-2] 生产了第 12 件商品... 消费者[C-3] 使用了第 12 件商品... 生产者[P-1] 生产了第 13 件商品... 消费者[C-1] 使用了第 13 件商品... 生产者[P-2] 生产了第 14 件商品... 消费者[C-2] 使用了第 14 件商品... ``` # 3、Java集合 ![Java集合.png](https://upload-images.jianshu.io/upload_images/13971762-c893cbc9be0a7ae9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 图片来源:[https://blog.csdn.net/Hacker_ZhiDian/article/details/80590428](https://blog.csdn.net/Hacker_ZhiDian/article/details/80590428 "https://blog.csdn.net/Hacker_ZhiDian/article/details/80590428") * Java中的集合分为三大类:**List、Set和Map**。由上图可以看出,**List和Set继承自Collection,而Map不是Collection的子类。** * **List接口的实现类有:ArrayList、LinkList、Vector =>Stack** 点击展开内容 ![ArrayList.png](https://upload-images.jianshu.io/upload_images/13971762-7b108a883eb8eb9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![LinkedList.png](https://upload-images.jianshu.io/upload_images/13971762-a527821b58501a74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![Vector.png](https://upload-images.jianshu.io/upload_images/13971762-d4f12b7e5ab7c6ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![Stack.png](https://upload-images.jianshu.io/upload_images/13971762-d6f472f29675caa7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **ArrayList:内部采用数组**保存元素,初始默认容量为 10,之后添加元素时,如果数组容量不足,则以 1.5 倍的倍数扩容数组,溢出时抛出 OutOfMemeryError 异常。扩容操作即为新建一个更大的数组并将原数组中的元素拷贝到新数组中。在元素较多时扩容操作开销较大,如果一开始可以确定最大需要的容量,那么建议使用另一个构造方法*public ArrayList(int initialCapacity){}*来创建指定初始容量的 ArrayList 以提高效率。因为采用的数组储存元素,所以插入和删除元素操作较慢(时间复杂度为 O(N))。 **ArrayList 为非线程安全的类**。 **LinkedList :内部采用双向链表**来储存元素,每添加一个元素就新建一个 Node 并添加到对应的位置,就没有所谓的扩容机制,同时实现了 Deque (双端队列)接口,可以作为队列 / 双端队列使用。插入元素、移除元素效率较高(时间复杂度为 O(1)),但是随机访问元素效率较低(时间复杂度为 O(N))。**LinkedList 非线程安全。** **Vector :**和 ArrayList 相似,**内部采用数组保存元素**,默认容量为 10。创建时如果指定了 capacityIncrement 参数,那么每次扩容时数组容量增加 capacityIncrement ,否则扩容时数组容量变为原来的 2 倍。**Vector 线程安全**。 **Stack :**继承于 Vector 类,提供了数据结构中 栈 的相关操作方法,**线程安全**。 * **Map接口的实现类有:HashMap => LinkHashMap、TreeMap(有序的)、WeakHashMap、IdentifyHashMap、ConcurrentHashMap和Hashtable** * **HashMap 中元素的遍历顺序和元素的插入顺序是没有任何关系的**,因为插入元素时主要依据的是元素的键的 hashCode 值,而每个元素的键的 hashCode 没有什么规则(根据键所属的类的实现而定),所以我们并不能试图按照插入元素的顺序来取出元素。如果需要使得取出的元素顺序是按照插入元素的先后顺序排序的话,请使用 LinkedHashMap 。 **容量**(默认为 16,如果自定义初始容量,那么会处理成最小的不小于指定的容量的 2 的次幂数,注意 HashMap 的容量一定会是 2 的次幂数); **扩容机制**(每次扩容变成上一次容量的 2 倍,如果当前元素数目达到扩容阀值(负载因子 * 当前 HashMap 总容量),进行扩容); **负载因子**(默认 0.75 ); **最大容量**(Integer.MAX_VALUE - 8); * **TreeMap 最大的特点是能根据插入的键值对的键来对键值对元素节点进行排序**,而当我们遍历 TreeMap 对象的时候取得的元素顺序是按照某个规则来进行排序的,具体规则我们可以在创建 TreeMap 对象的实现传入一个 Comparator 对象的参数来进行指定。需要注意的是:如果没有指定 TreeMap 的 Comparator 对象,那么需要保证 TreeMap 储存的键值对元素的 “键” 是实现了 Comparable 接口的,否则会报类型转换异常(ClassCastException),这一点在源码的分析中已经提到了。 而相对于 HashMap 来说,TreeMap 没有什么初始容量和负载因子的概念,因为它是用的是红黑树这种数据结构,即为动态申请内存空间(插入一个元素就申请一个元素的内存空间),也因为如此,其插入元素和查询元素的时间复杂度均为 O(logn),即为树的高度,n 为 TreeMap 中节点数。 **红黑树**是一种很有用的数据结构,只是维护的时候比一般的二叉搜索树复杂一点,主要是**为了维护高度平衡以保证较高的查找效率**。 * **LinkedHashMap **内部通过**双向链表**来维持元素的顺序,同时其继承于 HashMap。 * **Hashtable**(1.5后被弃用)和HashMap很类似,最重要的区别是**Hashtable是线程安全的,而HashMap则不是线程安全的**,在1.5之后推荐使用ConcurrentHashMap([HashMap和Hashtable的区别](http://www.importnew.com/7010.html "HashMap和Hashtable的区别")) * **ConcurrentHashMap **类,这个类是 JDK1.5 新增的一个类,可以用来代替Hashtable,可以非常高效的进行相关的元素操作,同时还保证多线程安全。内部实现非常巧妙,简单来说就是内部有多个互斥锁,每个互斥锁负责一段区域 > 假设现在内部有 100 个元素,即有一个长度为 100 的元素数组,那么 ConcurrentHashMap 提供了 10 个锁,每个锁负责 10 个元素(0~9, 10~19, …, 90~99),每当有线程操作某个元素时,通过这个元素的键的 hash 值可以得到其操作的是哪个区域,之后就锁住对应区域的锁对象即可,而其他区域的元素依然可以被其他线程访问。这就好比一个厕所,里面有多个位置,每个位置每次只能有一个人上厕所,但是不能因为这一个人上厕所就把整个厕所给锁掉,所以就是每个位置设置一把锁,对应只负责这个位置即可。 * **WeakHashMap** 默认的初始容量是 16,最大容量为 1 << 30 ;默认扩容因子为 0.75;可以指定初始容量,但是处理过后的初始容量一定是 2 的次幂,好处是可以通过 & 运算来代替 % 运算提高效率;每次扩容时容量翻倍。节点(Entry)之间利用单项链表之间来处理 hash 值冲突 * **Set接口的实现类有:HashSet、TreeSet、LinkHashSet** * Set的实现是基于Map的,将使用要保存的值都保存在Map的key上面,然后对Map的key进行相关操作 > 推荐阅读:[https://blog.csdn.net/Hacker_ZhiDian](https://blog.csdn.net/Hacker_ZhiDian "https://blog.csdn.net/Hacker_ZhiDian") # 4、Java异常处理机制 ![Throwable.png](https://upload-images.jianshu.io/upload_images/13971762-b1879f1386480c2e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) * **检查性异常:**所谓检查(**Checked**)是指编译器要检查这类异常,检查的目的一方面是因为该类异常的发生难以避免,另一方面就是让开发者去解决掉这类异常,所以称为必须处理(try ...catch)的异常。如果不处理这类异常,集成开发环境中的编译器一般会给出错误提示。**例如:**一个读取文件的方法代码逻辑没有错误,但程序运行时可能会因为文件找不到而抛出FileNotFoundException,如果不处理这些异常,程序将来肯定会出错。所以编译器会提示你要去捕获并处理这种可能发生的异常,不处理就不能通过编译。 * **运行时异常(非检查性异常):** 所谓非检查(**Unchecked**)是指编译器不会检查这类异常,不检查的则开发者在代码的编辑编译阶段就不是必须处理,这类异常一般可以避免,因此无需处理(try ...catch)。如果不处理这类异常,集成开发环境中的编译器也不会给出错误提示。**例如:**你的程序逻辑本身有问题,比如数组越界、访问null对象,这种错误你自己是可以避免的。编译器不会强制你检查这种异常。 * **错误:** 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的 所有的异常类是从 java.lang.Exception 类继承的子类。Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。Java 程序通常不捕获错误Error。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。Error 用来指示运行时环境发生的错误。例如,JVM 内存溢出。一般地,程序不会从错误中恢复。异常类有两个主要的子类:IOException 类和 RuntimeException 类。 | | **异常**| **描述**| |--|--|--| | **Java中一些常见的非检查性异常** | || ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。| || ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 | | |ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 | | |ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 | | |IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 | | |IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。| | |IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。| | |IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。| | |IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。| | |NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 | | |NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 | || NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。| || SecurityException| 由安全管理器抛出的异常,指示存在安全侵犯。| | |StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。| | |UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。| | **Java中的一些受检异常** | | |ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。| | |CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 | | |IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。| | |InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 | | |InterruptedException | 一个线程被另一个线程中断,抛出该异常。| | |NoSuchFieldException | 请求的变量不存在 | | |NoSuchMethodException | 请求的方法不存在 | ### Exception的一些常用方法及说明 | Exception | 方法及说明| |----|:-----| | **public String getMessage()**| 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 | | **public Throwable getCause()**| 返回一个Throwable 对象代表异常原因。 | | **public String toString()**|使用getMessage()的结果返回类的串级名字。 | | **public void printStackTrace()** | 打印toString()结果和栈层次到System.err,即错误输出流。 | | **public StackTraceElement [] getStackTrace()** | 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 | | **public Throwable fillInStackTrace()** |用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 | ### 异常处理 ``` try { // 程序代码 }catch(ExceptionName e) { //Catch 块1 }catch(ExceptionName e) { //Catch 块2 } finally { //不论是否发生异常,总会执行,所以通常 //做一些善后操作,如释放资源等 } ``` ### throws/throw 关键字: 如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 **throws 关键字来声明异常**。throws 关键字放在方法签名的尾部。 也可以使用 **throw 关键字抛出一个异常**,无论它是新实例化的还是刚捕获到的。 # 5、正则表达式 参考资料: > [http://www.runoob.com/java/java-regular-expressions.html](http://www.runoob.com/java/java-regular-expressions.html "http://www.runoob.com/java/java-regular-expressions.html") > > [http://www.cnblogs.com/interdrp/p/5586587.html](http://www.cnblogs.com/interdrp/p/5586587.html "http://www.cnblogs.com/interdrp/p/5586587.html")