Javase四月上

1.HashMap

  1. HashMap 内部数据结构: 数组+链表/红黑树

  2. 空值:只允许一个键为Null(多的会覆盖),值不限

  3. 影响性能参数

    ​ 初始容量:创建哈希表(数组)时桶的数量,默认为16

    ​ 负载因子:哈希表在其容量自动增加之前可以达到多慢的一种尺度,默认为0.75

  4. HashMap工作原理

    基于Hashing的原理,我们使用put(key, value )存储对象到HashMap中,使用get(key)从HashMap获得对象

  5. put() 的工作原理

    v2-23c7ff9a318161e8846dd3fd3a089a0d_1440w
  6. HashMap 的底层数组长度为何是2的n次方
    HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂。

    • 使数据分布均匀,减少碰撞
    • 当length为2的n次方时,h&(length - 1) 就相当于对length取模,而且在速度、效率上比直接取模要快得多

java1.8做了哪些优化?

  • 数组+链表 改成了数组+链表或红黑树
  • 链表的插入方式从头插法改成了尾插法
  • 扩容的时候1.7需要对原数组的元素进行重新Hash定位在新数组的位置,1.8采用更简单的逻辑判断,位置不变 或 索引+旧容量大小。
  • 在插入时,1.7先判断是都需要扩容,再插入。1.8先插入,插入完成时再判断是否需要扩容。
  1. HashMap线程安全方面会出现什么问题
  • 在jdk1.7中,多线程环境下,扩容会造成环形练或数据丢失。
  • 在jdk1.8中,多线程环境下,会发生数据覆盖的情况。
  1. 为什么HashMap的底层数组长度是2的n次方?

    1. ​ 当length为2的N次方, h & (length-1) = h % length

      &的效率更高,因为位运算直接对内存数据进行操作,不需要转化成十进制,所以位运算比取模运算效率高

    2. 当length为2的N次方时候,数据分布均匀,减少冲突

      hash策略为h & (length-1) 举例length为奇数、偶数时的情况:

      jisuchongtu oushuchongtu

      从上面的图表中我们可以看到,当 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数组中分布较均匀,查询速度也较快。

  2. HashMap线程安全方面会出现什么问题

    1. put的时候导致的多线程数据不一致

      ThreadA 和ThreadB 。A希望插入一个key-value对到HashMap中,首先计算记录要落到的Hash桶的索引坐标,然后获取到该桶里面的链表头结点,此时A的时间片用完。B和A的操作一样,而B的记录成功插到了桶里面,而到A运行时,对持有的过期链表头一无所知则会覆盖B的数据。造成数据不一致。

    2. resize而引起的死循环

      该情况发生在HashMap自动扩容时,当2个线程同时检查到元素个数超过数组大小×负载因子,此时2个线程会在put()方法调用resize(),两个线程同时修改一个链表结构会产生一个循环链表(JDK1.7中,会出现resize前后元素顺序倒置的情况)。接下来再想通过get()获取某一个元素,就会出现死循环。

      HashMap死循环

      HashMap线程不安全的体现

  3. 为什么1.8改用红黑树

    比如某些人通过找到你的hash碰撞值,来让你的HashMap不断地产生碰撞,那么相同key位置的链表就会不断增长,当你需要对这个HashMap的相应位置进行查询的时候,就会去循环遍历这个超级大的链表,性能及其地下。java8使用红黑树来替代超过8个节点数的链表后,查询方式性能得到了很好的提升,从原来的是O(n)到O(logn)。

    HashMap常见面试题整理

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的区别

  1. Runnable的实现方式是实现其接口即可

  2. Thread的实现方式是继承其类

  3. Runnable接口支持多继承,但基本上用不到

  4. Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。

使用Runnable的好处:

  1. 避免单继承的局限性,

  2. 增强程序的扩展性,降低了程序的耦合性

    实现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张票   出现了线程安全问题,会出现重复的票和不存在的票

线程同步机制:

  1. 同步代码块
  2. 同步方法
  3. 锁机制
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 以退出的线程

image-20210414164637740

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);

有且仅有一个抽象方法的接口,称为函数式接口。

死磕Lambda表达式(四):常用的函数式接口

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;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值