1.HashMap
-
HashMap 内部数据结构: 数组+链表/红黑树
-
空值:只允许一个键为Null(多的会覆盖),值不限
-
影响性能参数
初始容量:创建哈希表(数组)时桶的数量,默认为16
负载因子:哈希表在其容量自动增加之前可以达到多慢的一种尺度,默认为0.75
-
HashMap工作原理
基于Hashing的原理,我们使用put(key, value )存储对象到HashMap中,使用get(key)从HashMap获得对象
-
put() 的工作原理
-
HashMap 的底层数组长度为何是2的n次方
HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂。- 使数据分布均匀,减少碰撞
- 当length为2的n次方时,h&(length - 1) 就相当于对length取模,而且在速度、效率上比直接取模要快得多
java1.8做了哪些优化?
- 数组+链表 改成了数组+链表或红黑树
- 链表的插入方式从头插法改成了尾插法
- 扩容的时候1.7需要对原数组的元素进行重新Hash定位在新数组的位置,1.8采用更简单的逻辑判断,位置不变 或 索引+旧容量大小。
- 在插入时,1.7先判断是都需要扩容,再插入。1.8先插入,插入完成时再判断是否需要扩容。
- HashMap线程安全方面会出现什么问题
- 在jdk1.7中,多线程环境下,扩容会造成环形练或数据丢失。
- 在jdk1.8中,多线程环境下,会发生数据覆盖的情况。
-
为什么HashMap的底层数组长度是2的n次方?
-
当length为2的N次方, h & (length-1) = h % length
&的效率更高,因为位运算直接对内存数据进行操作,不需要转化成十进制,所以位运算比取模运算效率高
-
当length为2的N次方时候,数据分布均匀,减少冲突
hash策略为h & (length-1) 举例length为奇数、偶数时的情况:
从上面的图表中我们可以看到,当 length 为15时总共发生了8次碰撞,同时发现空间浪费非常大,因为在 1、3、5、7、9、11、13、15 这八处没有存放数据。
这是因为hash值在与14(即 1110)进行&运算时,得到的结果最后一位永远都是0,那么最后一位为1的位置即 0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的。这样,空间的减少会导致碰撞几率的进一步增加,从而就会导致查询速度慢。
而当length为16时,length – 1 = 15, 即 1111,那么,在进行低位&运算时,值总是与原来hash值相同,而进行高位运算时,其值等于其低位值。所以,当 length=2^n 时,不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,查询速度也较快。
-
-
HashMap线程安全方面会出现什么问题
-
put的时候导致的多线程数据不一致
ThreadA 和ThreadB 。A希望插入一个key-value对到HashMap中,首先计算记录要落到的Hash桶的索引坐标,然后获取到该桶里面的链表头结点,此时A的时间片用完。B和A的操作一样,而B的记录成功插到了桶里面,而到A运行时,对持有的过期链表头一无所知则会覆盖B的数据。造成数据不一致。
-
resize而引起的死循环
该情况发生在HashMap自动扩容时,当2个线程同时检查到元素个数超过数组大小×负载因子,此时2个线程会在put()方法调用resize(),两个线程同时修改一个链表结构会产生一个循环链表(JDK1.7中,会出现resize前后元素顺序倒置的情况)。接下来再想通过get()获取某一个元素,就会出现死循环。
-
-
为什么1.8改用红黑树
比如某些人通过找到你的hash碰撞值,来让你的HashMap不断地产生碰撞,那么相同key位置的链表就会不断增长,当你需要对这个HashMap的相应位置进行查询的时候,就会去循环遍历这个超级大的链表,性能及其地下。java8使用红黑树来替代超过8个节点数的链表后,查询方式性能得到了很好的提升,从原来的是O(n)到O(logn)。
2.Collections
import java.util.Collections;
public class Demo{
public static void main(String[] args){
ArrayList<String> list = new ArrayList<>(); //ArrayList 是有序的集合
list.add("a");
list.add("b");
/*
这些静态方法,直接通过 类名.方法名 调用
*/
Collections.addAll(list,"a","b","d"); //一次性添加元素
Collections.shuffle(list); //打乱顺序
Collections.sort(list); //默认排序(升序)
}
}
自定义排序
public class Person implements Comparable<Person>(){
private String name;
private int age;
getter setter...
@override
public int compareTo(Person o){
return this.getAge()-o.getAge(); //用this-参数 表示升序,参数-this表示降序
}
}
3.接口map
// Map<K,V> 有两个泛型
/* K - 此映射所维护的键的类型 不允许重复
V - 映射值类型 运行重复
key和value 一一对应
将键映射到值的对象
常用子类:HashMap 不同步(多线程(意味速度快)) 无序
java.util.LinkedHashMap<k,v> extends HashMap<k,v> 有序
put(K key, V value) 添加键和值 返回值为V
remove(Object key) 将映射删除
keySet() 返回包含键的视图
entrySet() 返回此映射中包含映射关系的set视图
*/
Map<String> map = new HashMap<>();
map.put("","");
map.put("","");
map.put("","");
//遍历方法: 使用Map 集合的方法,keySet() 获取所有的key,存入一个set中
// 遍历set,通过get(key),找到value
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
while(it.hasNext()){
String key = it.next();
Integer value =map.get(key);
System.out.println(key+value);
}
//或者使用增强for遍历
for(String key : map.keySet()){
...
}
//键值对对象 Entry<k,v> 在集合一创建好就有的
Set<Map.Entry<k,v>> ;
//Entry对象的方法 getKey() 和 getValue()
Map<String,Integer> map = new HashMap<>();
map.put("sda":2);
map.put("s":1);
map.put("sa":3);
Set<Map.Entry<String,Integer>> set = map.entrySet();
//使用迭代器
Iterator<Map.Entry<String,Integer>> it = set.iterator();
while(it.hasNext()){
Map.Entry<String,Integer> entry = it.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+"="+value);
}
【练习】计算一个字符串每个字符出现次数
package com.test.demo01;
import java.util.HashMap;
import java.util.Scanner;
/*
计算一个字符串每个字符出现次数
一:
String类的方法 toCharArray ,吧字符串转换为一个字符数组
String类方法 length()+charAt(索引)
二:
使用Map集合中的方法判断获取到的字符是否存储在Map中。 containKey(以获取到的字符),返回bool值
*/
public class MapTest {
public static void main(String[] args) {
Scanner sc =new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
//创建Map集合,key 是字符串的字符, value是字符的个数
HashMap<Character,Integer> map = new HashMap<>();
//遍历字符串,获取每一个字符
for(char c : str.toCharArray()){
if(map.containsKey(c)){
//存在的情况
Integer value = map.get(c);
value++;
map.put(c,value);
}
else {
map.put(c,1);
}
}
//遍历map集合,输出结果
for(Character key:map.keySet()){
Integer value= map.get(key);
System.out.println(key+":"+value);
}
}
}
4.异常
idea 用alt+enter处理代码错误
异常就是一个类,产生异常就是创建异常对象并抛出异常对象,中断处理。
最顶层的父类是 java.lang.Throwable ,子类 是java.lang.Error 和java.lang.Exception
4.1异常分类
Exception:编译期异常
RuntimeException:运行期异常
4.1.1throw关键字
在指定的方法中抛出指定的异常
必须写在方法的内部
public class Demo04Exception {
public static void main(String[] args) {
int[] array = null;
int e =getElement(array,0);
System.out.println(e);
}
public static int getElement(int[] arr,int index){
if(arr==null){
throw new NullPointerException("传递的数组的值的null"); //空指针异常为运行期异常,不用自己处理,交给JVM
}
int ele =arr[index];
return ele;
}
}
Exception in thread "main" java.lang.NullPointerException: 传递的数组的值的null
at com.test.demo01.Demo04Exception.getElement(Demo04Exception.java:13)
at com.test.demo01.Demo04Exception.main(Demo04Exception.java:8)
4.1.2声明异常throws
关键字throws运用于方法声明之上 ,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)
声明异常格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2...{}
public static void readFile(String fileName){
if(!fileName.equals("C")){
throw new FileNotFoundException("错误"); //该句报错!是编译异常,需要在方法声明处处理
}
}
// 以下修改:
public static void readFile(String fileName) throws FileNotFoundException{
if(!fileName.equals("C")){
throw new FileNotFoundException("错误"); //该句报错!是编译异常,需要在方法声明处处理
}
}
4.1.3.try-catch
public static void main(String[] args) {
try{
readFile("D");
}catch (IOException e){
System.out.println("传递的文件有误");
}
}
public static void readFile(String fileName) throws IOException{
if(!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}
}
4.1.4自定义异常
/*
注意:1.自定义一个异常类都是以Exception结尾
2.自定义异常类,必须继承Exception 或者RuntimeException
继承Exception,那么自定义异常类就是一个编译期异常,如果方法内部抛出编译器异常,就必须处理这个异常,要么throws或者try-catch
继承RuntimeException 那么则是一个运行期异常,无需处理,交给虚拟机中断处理
*/
//格式:
public class XXXException extends Exception | RuntimeException{
//添加一个空参数的构造方法
//添加一个带异常信息的构造方法
}
NullPointerException的源码
public class NullPointerException extends RuntimeException{
private static final long serialVersionUID = 5162710183389028792L;
public NullPointerException(){ super();}
public NullPointerException(String s) {super(s);}
}
自己写一个自定义异常类
public class RegisterException extends Exception{
public RegisterException(){ super();}
public RegisterException(String message){ super(message);}
}
【练习】用自己写的异常类,模拟用户操作,如果用户名已存在,则提示信息
/*
【练习】用自己写的异常类,模拟用户操作,如果用户名已存在,则提示信息
分析:
1.使用数组保存已经注册的用户名(以后用数据库)
2.Scanner获取(以后用前端页面)
3.定义方法,对用户输入做判断
遍历数组,获取每一个应用户名,比较返回true、false
循环结束:注册成功
*/
public class Demo01RegisterException {
static String[] usernames = {"张三"};
public static void main(String[] args) throws RegisterException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.next();
checkUsername(username);
}
public static void checkUsername(String username) throws RegisterException {
for(String name: usernames){
if(name.equals(username)){
//抛出异常
throw new RegisterException("用户名已被注册");
}
System.out.println("恭喜您注册成功!");
}
}
}
请输入用户名
张三
Exception in thread "main" com.test.demo02.RegisterException: 用户名已被注册
at com.test.demo02.Demo01RegisterException.checkUsername(Demo01RegisterException.java:29)
at com.test.demo02.Demo01RegisterException.main(Demo01RegisterException.java:22)
Process finished with exit code 1
5.多线程
进程是进入内存工作的,线程是有一条线路到cpu。
如电脑管家 的多个功能是多个线程,点击运行就会开启一条应用程序到cpu的执行路径,这个路径的名称叫线程
单核心单线程cpu:(切换速度是1/n毫秒)
多核心多线程:同时执行多个线程,效率高,多线程互不影响
5.1.主线程
执行main方法的线程
JVM执行main方法,main方法进入栈内存中。JVM找OS开辟一条main方法通向cpu的执行路径,cpu通过路径来执行main方法,路径为main线程。
线程实际上一直都存在(主方法就是主线程),每个JVM进程启动的时候至少启动一下2个线程:
main线程:程序的主要执行,以及启动子线程
gc线程:负责垃圾收集
5.2.创建线程类
创建新执行线程有两种方法,一种是将类声明为Thread的子类,该子类重写Thread类的run方法。加下来可以分配并启动该子类的实例。例如
// 计算大于某一规定值的质数
class PrimeThread extends Thread{
long minPrime;
PrimeThread(long minPrime){
this.minPrime = minPrime;
}
public void run(){
//compute
}
}
练习:
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println("run:"+i);
}
}
}
public class Demo01Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start(); //start方法开辟新的栈空间==新多一个线程
for(int i = 0; i<20;i++){
System.out.println("main: "+i);
}
}
}
main: 0
main: 1
main: 2
main: 3
main: 4
run:0
main: 5
main: 6
main: 7
main: 8
...
原理
1、创建一个Thread类的子类
2、重写run方法,设置线程任务
3、创建Thread类的子类对象
4、调用Thread类方法start方法,开启新的线程,执行run方法void start()使该线程开始执行 ,Java虚拟机调用该线程的run方法。
结果是两个线程并发运行;当前线程(从main线程)和另一个线程(执行其run方法)。
多次(start)启动一个线程是非法的。特别是当线程已经结束后。
5.3.Thread类
public class MyThread extends Thread {
@Override
public void run() {
//获取线程名称
String name = getName();
System.out.println(name);
}
}
/*
方法 getName()
获取当前正在执行的线程名称,使用线程的方法getName
static Thread currentThread()
*/
public class Demo01Thread {
public static void main(String[] args) {
//创建Thread类的子类对象
MyThread mt = new MyThread();
//调用start方法,开启新线程,执行run方法
mt.start();
new MyThread().start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
//获取线程名称
Thread t = Thread.currentThread(); //currentThread 返回线程 这是静态方法,直接类名.方法名
System.out.println(t);
}
}
/************************/
public class Demo01Thread {
public static void main(String[] args) {
//创建Thread类的子类对象
MyThread mt = new MyThread();
//调用start方法,开启新线程,执行run方法
mt.start();
new MyThread().start();
new MyThread().start();
}
}
//
Thread[Thread-0,5,main]
Thread[Thread-1,5,main]
Thread[Thread-2,5,main]
Process finished with exit code 0
设置线程名称:
//1.使用setName方法
void setName(String name);
//2。创建一个带参数的构造方法,参数传递线程名称;调用父类的带参构造方法,把线程名传递给父类,让父类给子线程起名字
Thread(String name);
sleep方法:
//使当前正在执行的线程以指定的毫秒数暂停
public static void sleep(long millis)
public static void main(String[] args) {
for( int i=1;i<60;i++){
System.out.println(i);
//让程序一秒打印一次
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
创建线程的另一个方式
5.3.1java.lang.Runable 接口
*重点!最常用 !! 相当于把运行逻辑和线程容器分开了,只需要注入Runnable实现类的实例即可。
第一中方法继承Thread后就不能继承别的了,所以用接口比较好。Runnable可实现变量共享。
创建线程的另一种方法是声明Runnable接口的类,该类然年后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。
Runnable 里面没有start方法来开启线程,应该:
PrimeRun p = new PrimeRun(143); //创建一个接口的实现类对象
new Thread(p).start(); //new一个Thread类,其构造方法中传递这个实现类对象
示例:
public class RunnableImpl implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" :"+i);
}
}
}
/*
实现步骤:
1.创建一个Runnable接口的是实现类
2.在创建类中重写Runnable接口的run方法,设置子线程任务
3.创建一个Runnable接口的实现类对象 (在主线程)
4.创建Thread类对象,构造方法中传递Runnable接口的实现类方法
5.调用Thread类的start方法,开启新的线程执行run方法
*/
public class Demo04Runnable {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl(); //创建一个Runnable接口的实现类对象
Thread t =new Thread(run); //创建Thread类对象,构造方法中传递Runnable接口的实现类方法
t.start();
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" :"+i);
}
}
}
//运行结果就是主线程和子线程的运行
-
Runnable的实现方式是实现其接口即可
-
Thread的实现方式是继承其类
-
Runnable接口支持多继承,但基本上用不到
-
Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。
使用Runnable的好处:
-
避免单继承的局限性,
-
增强程序的扩展性,降低了程序的耦合性
实现Runnable接口的方式,把设置线程任务和开启新线程进行分离。 传递不同的实现类进行不同任务。
5.4.匿名内部类实现线程
格式: new 父类/接口(){ 重写父类/接口方法}
视频的有误,找博客看
5.5.线程安全
示例:卖票
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
@Override
public void run() {
while(true) {
if (ticket > 0) {
//卖
System.out.println(Thread.currentThread().getName() + "--》正在卖" + ticket + "张票");
ticket--;
}
}
}
}
//创建 三个线程,同时开启,对共享票源出售
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl(); //创建一个实现类卖100张是共同的,而不是300张
Thread t0 = new Thread(run);
Thread t2 = new Thread(run);
Thread t1 = new Thread(run);
//命名:new Thread(mt,"线程A").start();
t1.start();
t0.start();
t2.start();
}
}
Thread-0--》正在卖100张票
Thread-2--》正在卖100张票
Thread-1--》正在卖100张票
Thread-2--》正在卖98张票
Thread-0--》正在卖99张票
Thread-2--》正在卖96张票
Thread-2--》正在卖94张票
Thread-1--》正在卖97张票 出现了线程安全问题,会出现重复的票和不存在的票
线程同步机制:
- 同步代码块
- 同步方法
- 锁机制
5.5.1.同步代码块
synchronized(锁对象){ 访问共享数据的代码}
修改上例
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true) {
synchronzied(obj){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "--》正在卖" + ticket + "张票");
ticket--;
}
}
}
}
}
同步技术原理:
使用了一个锁对象, 也叫同步锁,也叫对象监视器。
3个线程一起抢夺cpu的执行权 谁抢到了谁执行run方法进行卖票切抢到了cpu的执行权执行run方法遇到synchronized代码块这时如会检查synchronized代码块是否有锁对象发现有,就会获取到锁对象,进入到同步中执行t1抢到了cpu的执行权行run方法遇到synchronized代码块这时t1会检查synchronized代码块是否有锁对象发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象一直到t0线程执行完同步中的代码, 会把锁对象归还给同步代码块。
总结:同步中的线程,没有执行完毕就不会释放锁,同步外的线程没有锁就进不去同步。
程序频繁的判断锁,会消耗效率。
5.5.2.同步方法
public synchronized void method(){
访问共享数据的代码
}
//或者
public void method(){
synchronized(this){ //锁对象是this,this是创建对象之后产生的
访问共享数据的代码
}
}
静态同步方法
注意将访问的变量也设为静态的。
其锁对象不是this。**而静态方法优先于对象。**静态方法 的锁对象是本类的class属性,(class文件对象)
5.5.3.Lock锁
java.util.concurrent.locks的Lock接口
方法:
void lock(); 获取锁
void lockInterruptibly();
boolean newCondition();
boolean tryLock(long time, TimeUnit unit);
void unlock(); 释放锁
5.6线程状态
以下六种状态:
NEW 至今尚未启动的线程
RUNNABLE 正在java虚拟机中执行的线程
BLOCKED 受阻塞并等待某一个监视器锁的线程
WATTING 无限期等待另一个线程来执行某一特定操作的线程(进入wait set)
TIMED_WATTING 等待另一个线程来执行某一特定操作的线程
TERMINATED 以退出的线程
Object类有这两个方法:notify () 唤醒在此脆响上等待的单个进程
wait () 在其他线程调用此对象的notify 或notifyAll方法前,导致当前线程等待
// 等待和唤醒案例
/*
等待唤醒案例:线程之间的通信
创建一-个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裏起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须唯一
只有锁对象才能调用wait和notify
object类中的方法
void wait( )
在其他线程调用此对象的notify()方法或notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器_上等待的单个线程。
会继续执行wait方法之后的代码
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
Object obj = new Object();
new Thread(){
@Override
public void run() {
//保证等待和唤醒只有一个在执行
synchronized (obj){
System.out.println("告知老板要的包子");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒后执行的代码
System.out.println("吃包子");
}
}
}.start();
new Thread(){
@Override
public void run() {
//老板线程
//花5秒做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
//做好后唤醒顾客
System.out.println("做好了!");
obj.notify();
}
}
}.start();
}
}
5.7等待唤醒机制
也称线程间通信,多个线程处理一个资源,但线程的任务不同。
案例:(自己再敲一遍)
package com.test.Demo05;
//把包子对象和当作锁对象
public class BaoZi {
String pi;
String xian;
boolean flag = false;
}
package com.test.Demo05;
import static java.lang.Thread.sleep;
public class BaoZiPu extends Thread{
private BaoZi bz;
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
int count = 0;
while(true)
{
synchronized (bz){
if(bz.flag==true){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(count%2==0){
bz.pi = "薄皮";
bz.xian = "三鲜";
}else{
bz.pi = "冰皮";
bz.xian = "大葱";
}
count++;
System.out.println("包子铺正在生产:"+ bz.pi+bz.xian);
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改包子状态
bz.flag = true;
//唤醒顾客
bz.notify();
System.out.println("已经生产完了:"+bz.pi+bz.xian);
}
}
}
}
package com.test.Demo05;
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(BaoZi bz){
this.bz=bz;
}
@Override
public void run() {
while(true){
synchronized (bz){
if(bz.flag==false){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//呗唤醒之后
System.out.println("正在吃:"+bz.pi+bz.xian);
//修改状态
bz.flag = false;
bz.notify();
System.out.println("已经吃完了,包子铺开始生产包子");
System.out.println("--------------------------");
}
}
}
}
package com.test.Demo05;
public class Demo {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
new BaoZiPu(bz).start();
new ChiHuo(bz).start();
}
}
5.8线程池
频繁创建线程消耗时间和效率
线程池:容器 - ->集合 如(ArrayList, HashSet, LinkedList, HashMap) 这里使用LinkedList<Thread>
Thread t =list.remove(0);
返回的是被移除的元素(线程只能被一个任务使用)
list.add(t);
linked.addLast(t);
JDK1.5后有内置的线程池。 java.util.concurrent.Executors
类是生产线程池的工厂类
package com.test.Demo06ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
JDK1.5后有内置的线程池。 java.util.concurrent.Executors 类是生产线程池的工厂类
静态方法:
static ExecutorService newFixedThreadPool (int nThreads) 创建一个可重用固定线程数的线程池
参数: nThreads 数量
返回值: ExecutorService接口 ,返回的是ExecutorService接口的实现类对象。(面向接口编程)
java.util.concurrent.ExecutorService :线程池接口、
用来从线程池获取线程。调用start方法执行线程任务
submit(Runnable task)提交一个Runnable任务用于执行
关闭/销毁线程池的方法:
void shutdown()
使用步骤:
1.使用线程池的工厂类Executors 里面的静态方法newFixedThreadPool 生产一个指定线程数量的线程池
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService的方法submit,传递线程任务,开启线程执行run方法。
4.调用ExecutorService的方法shutdown销毁线程。(不建议用)
*/
public class Demo01ThreadPool {
public static void main(String[] args) {
//1.使用线程池的工厂类Executors 里面的静态方法newFixedThreadPool 生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
// 3.调用ExecutorService的方法submit,传递线程任务,开启线程执行run方法。
es.submit(new RunnableImpl()); //使用完了,会自动归还给线程池
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
//4.调用ExecutorService的方法shutdown销毁线程。(不建议用)
//es.shutdown();
}
}
package com.test.Demo06ThreadPool;
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了一个新的线程");
}
}
pool-1-thread-1创建了一个新的线程
pool-1-thread-2创建了一个新的线程
pool-1-thread-1创建了一个新的线程
Process finished with exit code -1
6.Lambda
函数式编程思想:在数学中,函数就是由输入量、输出量的一条计算方案。面向对象过分强调“必须通过对象的形式”,而函数的思想尽量忽略面向对象的复杂语法
冗余的Runnable代码
package com.test.Demo07;
public class Demo01 {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类
Thread t = new Thread(run);
t.start();
//-----------简化代码。
Runnable r = new Runnable()
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"新线程创建了");
}
};
new Thread(r).start();
//继续简化就是new里面直接传递匿名内部类
}
}
Thread-0传统写法
Thread-1新线程创建了
Process finished with exit code 0
6.1.lambda表达式练习
public class Demo02 {
public static void main(String[] args) {
/*new Thread(new Runnable){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();*/
// 使用lambda表达式
new Thread(()->{
System.out.println(Thread.currentThread().getName());
}).start();
}
}
() -> System.out.println(Thread.currentThread().getName());
//()就是run方法的参数(无参数)
//-> 代表将前面的参数传递给后面的代码
【练习】:给定一个厨子Cook接口,内涵唯一 抽象方法makeFood,使用Lambda表达式标准格式调用invokeCook方法, 打印输出:“吃饭啦!”
public interface Cook{
void makeFood();
}
public class Demo05InvokeCook {
public static void main(String[] args) {
//创建invokeCook方法,参数是Cook接口,传递cook接口的匿名内部类对象
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
//使用lambda表达式
invokeCook(()->{
System.out.println("吃饭了");
});
}
//定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
private static void invokeCook(Cook cook){
cook.makeFood();
}
}
注意上面的参数传递
【练习】:使用数组存储多个Person对象;对数组中的Person对象使用Array的sort方法对年龄升序
public class Demo01Arrays {
public static void main(String[] args) {
//使用数组存储多个Person对象
Person[] arr = {
new Person("撒旦",999),
new Person("安抚输",23),
new Person("问的",43),
new Person("啊实打实的",26)
};
//对数组Person对象使用Array的sort的方法。(前-后)为升序
Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
//遍历数组
for(Person person : arr){
System.out.println(person);
}
}
}
注意对象数组的创建格式,内部用逗号分开。 注意 Arrays.sort( )的使用 和Comparator 的使用。
//使用表达式简化匿名内部类
Arrays.sort(arr, (Person o1, Person o2)->{
return o1.getAge()-o2.getAge();
})
【练习】(有参数有返回)给定一个计算器Calculator接口,内含抽象方法calc 可以将两个int数字相加
public interface Calculator{
int calc(int a, int b);
}
在下面代码中,请使用Lambda标准格式调用invokeCalc方法
public class Demo08InvokeCalc {
public static void main(String[] args) {
///
invokeCalc(10, 20, new Calculator() {
@Override
public int calc(int a, int b) {
return a+b;
}
});
//使用lambda
invokeCalc(10,20,(int a,int b)->{
return a+b;
});
}
/*
定义一个方法,参数传递两个int类型的整数,传递Calculator 接口,方法内部调用接口的方法。
*/
private static void invokeCalc(int a, int b, Calculator calculator){
int result = calculator.calc(a,b);
System.out.println("结果是:"+result);
}
}
Lambda可推导,可省略
//上述例子省略
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"线程创建了");
}
).start();
//优化省略 :省略只有一条语句的分号 ,和中括号
new Thread(()->System.out.println(Thread.currentThread().getName()+"线程创建了")).start.
//上述例子省略
involeCalc(120,130,(int a, int b)->{
return a+b;
});
//优化
invokeCalc(120,130,(a,b)->a+b);
有且仅有一个抽象方法的接口,称为函数式接口。
Semaphore一些知识
并发编程
7.File类
java.io.File
文件和目录抽象表现形式,File与操作系统无关,所以写的时候考虑文件可移植
//静态成员变量
String pathSeparator = File.pathSeparator; //打印路径分割符,windows是; Linux 是:
String separator = File.separator; //返回文件名称分隔符 windows是反斜杠\ ,Linux 是正斜杠/
static char pathSeparatorChar; //与系统有关的路径分隔符
static char separatorChar; //与系统有关的默认名称分隔符
//写路径
C:\develop\a\a.txt Windows
C:/develop/a.txt Linux
"C:"+File.separator+"develpop"+File.seperator+"a"+File.seperator+"a.txt"
路径:不区分大小写
Windows 的反斜杠是转义字符,所以用两个反斜杠
//构造方法有三种
File f1 = new File("C:\\Fake\\user"); //传入路径
File f1 = new File("C:\\","t.txt"); //传入parent和child两个String型
File f1 = new File(parent,"t.txt"); //parent可以为File传入
7.1常用方法
public String getAbsolutePath()
:返回此文件的绝对路径
public String getPath()
:将此File转换为路径名字符串 File类的toString也被重写为该方法
public String getName()
:返回由此File表示的文件或目录的名称
public Long length()
:返回由此File表示的文件的大小,以字节为单位。如果路径不存在或文件夹,返回0
判断功能方法
public boolean exists()
:判断构造方法的路径是否存在
public boolean isDirectory()
:是否为目录
public boolean isFile()
:是否为文件
创建和删除功能方法
public boolean createNewFile()throws IOEception
:当且仅有当前文件不存在时,创建一个新的空问文件夹,调用时要抛出异常
public boolean delete()
:删除文件或目录(不走回收站,在硬盘操作)
public boolean mkdir()
:创建由此File表示的目录,单级空文件夹
public boolean mkdirs()
:创建由此File表示的目录,包括任何必须但不存在的父目录(可创建多级文件夹)
File f1 = new File("D:\\Dre"); //单级的可创建
boolean b1 = f1.makedir();
//会在相应的路径创建文件夹
目录的遍历
public String[] list()
:返回一个String数组,表示该File目录中所有子文件或目录
public File[] listFiles()
:返回一个File数组,表示该File目录中所有子文件或目录
注意:list方法和listFile方法遍历的是构造方法中给出的目录,如果构造方法中的目录路径不存在或不是一个目录,会抛出空指针异常
private static void show01() {
File f1 = new File("D:\\IDEA");
String[] arr = f1.list();
//数组用遍历
for(String s : arr){
System.out.println(s);
}
}
8.递归
注意事项:条件限定和递归次数不能太多,会发生栈内存溢出(注意方法在栈内执行);构造方法禁止递归
【练习】递归打印多级目录
D:\Chrome_downloads\Python
本机目录,底下还有文件,打印所有文件
public class Demo04 {
public static void main(String[] args) {
getAllFile(new File("D:\\Chrome_downloads"));
}
//定义一个方法,传递File类型目录,对目录遍历
public static void getAllFile(File dir){
//public File[] listFiles() :返回一个File数组,表示该File目录中所有子文件或目录
File[] files = dir.listFiles();
for (File f : files) { // 注意数组的遍历
System.out.println(f);
}
}
}
D:\Chrome_downloads\Algorithem
D:\Chrome_downloads\Computer
D:\Chrome_downloads\debug.log
D:\Chrome_downloads\ECO
D:\Chrome_downloads\Java&Android
D:\Chrome_downloads\Machine Learning
D:\Chrome_downloads\Maths
D:\Chrome_downloads\METASHELF
D:\Chrome_downloads\OS&Net&DB
D:\Chrome_downloads\Python
Process finished with exit code 0
只有这些文件夹名,但没有遍历子文件夹,解决:判断每一个结果,是不是文件夹 f.isDirectory
,是的话则进入这个文件夹遍历
public class Demo04 {
public static void main(String[] args) {
getAllFile(new File("D:\\Chrome_downloads"));
}
//定义一个方法,传递File类型目录,对目录遍历
public static void getAllFile(File dir){
//public File[] listFiles() :返回一个File数组,表示该File目录中所有子文件或目录
File[] files = dir.listFiles();
for (File f : files) {
if(f.isDirectory()){
getAllFile(f);
}else{
System.out.println(f);
}
}
}
}
listFiles
方法一共做了三件事情:
1.listFile方法会对构造方法中传递的目录进行遍历,获取目录的每一个文件\文件夹–>封装成File对象。
2.listFile方法会调用参数传递的过滤器方法accept
3.listFile方法会把遍历得到的每一个File对象,传递给accept方法的参数pathname
8.1过滤器
java.io.FileFilter
接口用于抽象路径名的过滤器
/*抽象方法*/boolean accept(File pathname) //测试指定抽象路径名是否应该包含在某个路径名列表中
java.io.FilenameFilter
接口,实现此接口的类实例可以用于过滤器文件名
/*抽象方法*/boolean accept(File dir, String name) //测试指定文件是否应该包含在某一文件列表中
/*
File dir:构造方法中传递的被遍历的目录
String name: 使用ListFiles方法遍历目录,获取的每一个文件
*/
两个过滤器接口没有实现类,需要自己实现,重写方法accept,自己定义规则
修改上例:在 listFiles
方法传入过滤器实现类
public class Demo05 {
public static void main(String[] args) {
getAllFile(new File("D:\\Chrome_downloads\\Python"));
}
//定义一个方法,传递File类型目录,对目录遍历
public static void getAllFile(File dir){
//`public File[] listFiles() `:返回一个File数组,表示该File目录中所有子文件或目录
File[] files = dir.listFiles(new FileFilterImpl());
for (File f : files) {
if(f.isDirectory()){
getAllFile(f);
}else{
System.out.println(f);
}
}
}
}
//过滤器的实现类
public class FileFilterImpl implements FileFilter {
@Override
public boolean accept(File pathname) {
String name = pathname.getPath();
if(name.endsWith(".log")){
return true;
}else{
return false;
}
}
}
历得到的每一个File对象,传递给accept方法的参数pathname
8.1过滤器
java.io.FileFilter
接口用于抽象路径名的过滤器
/*抽象方法*/boolean accept(File pathname) //测试指定抽象路径名是否应该包含在某个路径名列表中
java.io.FilenameFilter
接口,实现此接口的类实例可以用于过滤器文件名
/*抽象方法*/boolean accept(File dir, String name) //测试指定文件是否应该包含在某一文件列表中
/*
File dir:构造方法中传递的被遍历的目录
String name: 使用ListFiles方法遍历目录,获取的每一个文件
*/
两个过滤器接口没有实现类,需要自己实现,重写方法accept,自己定义规则
修改上例:在 listFiles
方法传入过滤器实现类
public class Demo05 {
public static void main(String[] args) {
getAllFile(new File("D:\\Chrome_downloads\\Python"));
}
//定义一个方法,传递File类型目录,对目录遍历
public static void getAllFile(File dir){
//`public File[] listFiles() `:返回一个File数组,表示该File目录中所有子文件或目录
File[] files = dir.listFiles(new FileFilterImpl());
for (File f : files) {
if(f.isDirectory()){
getAllFile(f);
}else{
System.out.println(f);
}
}
}
}
//过滤器的实现类
public class FileFilterImpl implements FileFilter {
@Override
public boolean accept(File pathname) {
String name = pathname.getPath();
if(name.endsWith(".log")){
return true;
}else{
return false;
}
}
}