并发编程,集合框架

本大爷的目录

ava并发编程

为什么很重要

并发编程可以充分利用计算机的资源,把计算机的性能发挥到最大,可以最大节约公司的成本,提高效率

1、什么是高并发

并发VS并行的区别

并发 concurrency:多线程“同时”操作同一个资源,并不是真正的同时操作,而是交替操作,单核CPU的情况下,资源按照时间段分配给多个线程(一个处理器处理多个任务)

并行 parallelism:是真正的多个线程同时执行,多核CPU,每个线程使用一个CPU资源来运行

并发编程描述的是一种可以使系统允许多个任务在重叠的时间段内执行的设计结构, 不是指多个任务在同一时间段内执行,而是指系统具备处理多个任务在同一时间内执行的能力

高并发是指我们设计的程序,可以支持海量任务的执行在时间段上重叠的情况。

高并发的标准:

  • QPS:每秒响应的HTTP请求数量,QPS不是并发数
  • 吞吐量:单位时间内处理的请求数,由QPS和并发数来决定
  • 平均响应时间:系统对一个请求作出响应的平均时间

QPS=并发数/平均响应时间

  • 并发用户数:同时承载正常使用系统的用户人数

互联网分布式架构设计,提高系统并发能力的两种方式:

  • 垂直扩展
  • 水平扩展

垂直扩展

提升单机处理能力(一台电脑)

  • 1.提升单机硬件设备,增加CPU核数,升级网卡,硬盘扩容,升级内存

  • 2.提升单机的架构性能,使用Cache提升效率,使用异步请求增加单服务的吞吐量,NoSQL提升数据库访问能力

水平扩展

(多台电脑)

集群:一个厨师搞不定,多雇几个厨师一起炒菜。多个人干同一件事情。

分布式:任务细分,给厨师雇两个助手,一个负责洗菜,一个负责切菜,厨师只负责炒菜。一件事情拆分成多个步骤,由不同的人去完成。

站点层扩展:Nginx反向代理,一个Tomcat跑不动那就10个Tomcat去跑

服务层扩展:RPC框架实现远程调用, Spring Cloud、Spring Boot、Dubbo、分布式架构,将业务逻辑拆分到不同的RPC Client,各自完成对应的业务,如果某项业务并发量很大,增加新的RPC Client,就能扩展服务层的性能,做到理论上的无限高并发

数据层的扩展:在数据量很大的情况下,将原来的一台数据库服务器,拆分成多台,已达到扩充系统性能的目的,主从复制,读写分离,分表分库

2、JUC

JUC: import java.util.concurrent

JDK提供的一个工具包,专门用来帮助开发者完成java并发编程

3、进程和线程

java默认的线程数2个

  • main主线程

  • GC垃圾回收机制

    java本身是无法开启线程的,java无法操作硬件,只能通过调用本地方法,C++编写的动态函数库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zoqSIdfK-1633617401240)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210727165619657.png)]

java 中实现多线程有几种方式

1.继承Thread类

2.实现Runnable接口

3.实现Callable接口

Callable和Runnable的区别在于Runnable的run方法没有返回值,Callable的call方法有返回值

public class CallAble01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testCallAble callAble = new testCallAble();
        FutureTask<String> futureTask = new FutureTask<>(callAble);
        new Thread(futureTask).start();
        String val = futureTask.get();
        System.out.println(val);
    }
}
class testCallAble implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("已开启线程");
        return "helloWord";
    }
}
  • Callable和Thread没有直接关系,不能直接由Thread开启线程(new Thread(callable).start()),

    又因为FutureTask的和Runnable有关系,所以可以借助FutureTask来开启线程

  • 以下两个是FutureTask的两个构造器

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9BLJ6wp2-1633617401242)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210808154600828.png)]

  • 资源和线程绑定,耦合度太高
public class Callable02 {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(account,"A").start();
        new Thread(account,"B").start();


    }
}

class Account implements Runnable{
    private static int num=0;
    @Override
    public synchronized void run() {
        num++;
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是当前的第 "+num+"位访问的");
    }
}
  • 解耦
public class Callable03 {
    public static void main(String[] args) {
        Account01 account = new Account01();
        new Thread(()->{
            account.count();
        },"A").start();
        new Thread(()->{
            account.count();
        },"B").start();
    }	
}
class  Account01{
    private static int num=0;
    public synchronized void count() {
        num++;
        System.out.println(Thread.currentThread().getName()+"是当前的第 "+num+"位访问的");
    }
}

4、Sleep和Wait

  • sleep和wait的区别在于这两个方法来自不同的类分别是Thread和Object

  • sleep是让当前线程休眠,wait是让访问当前对象的线程休眠。

  • sleep不会释放锁,wait会释放锁

  • sleep处理线程,wait处理资源

  • 由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的。在以下情况下,持有锁的线程会释放锁:

    1、当前线程的同步方法、代码块执行结束的时候释放

    2、当前线程在同步方法、同步代码块中遇到break 、 return 终于该代码块或者方法的时候释放。

    3、当前线程出现未处理的error或者exception导致异常结束的时候释放

    4、调用obj.wait()会立即释放锁,当前线程暂停,释放锁,以便其他线程可以执行obj.notify(),但是notify()不会立刻立刻释放sycronized(obj)中的obj锁,必须要等notify()所在线程执行完synchronized(obj)块中的所有代码才会释放这把锁。而 yield(),sleep()不会释放锁。

5、synchronized 锁定的是什么

1、synchronized修饰非静态方法时,锁定方法的调用者

public class Callable04 {
    public static void main(String[] args) {
        Date date = new Date();
        new Thread(()->{date.func1();},"AAA").start();
        new Thread(()->{date.func2();},"BBB").start();
    }
}
class Date{
    public synchronized void func1() {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是第一名");
    }
    public synchronized void func2(){
     
        System.out.println("我是第二名");
    }
}
/*自己的理解
1-加锁前,AAA进入方法前等待了五秒,BBB等待了三秒,所以BBB先输出
我是第二名
我是第一名
2-加锁后,synchronized锁定了方法的调用者也就是我们new出来的date,
AAA先进去,等待五秒,由于date被锁定,BBB只能先等AAA结束再进去
我是第一名
我是第二名
*/

2、synchronized修饰静态方法时,锁定的是类

public class Callable04 {
    public static void main(String[] args) {
        Date date = new Date();
        Date date2 = new Date();
        new Thread(()->{date.func1();},"AAA").start();
        new Thread(()->{date2.func2();},"BBB").start();
    }
}
class  Date{
    public synchronized static void func1() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是第一名");
    }
    public synchronized static void func2(){
        System.out.println("我是第二名");
    }
}
/*
因为synchronized修饰静态方法锁的是类,当线程AAA进入func1后,类Data()就被锁住了,
所以线程BBB就得等待,
我是第一名
我是第二名
*/

3、synchronized静态方法和实例方法同时存在,静态方法锁定的是类,实例方法锁定的是对象

public class Callable04 {
    public static void main(String[] args) {
        Date date = new Date();
//        Date date2 = new Date();
        new Thread(()->{date.func1();},"AAA").start();
        new Thread(()->{date.func2();},"BBB").start();
    }
}
class  Date{
    public synchronized static void func1() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是第一名");
    }
    public synchronized void func2(){
        System.out.println("我是第二名");
    }
}
/*
因为func1是静态方法锁定的是类,func2是实例方法锁定的是实例对象,
他们各自管各自的,无法实现同步
*/

6、lock

  • JUC提供的的一种锁机制,功能和synchronized类似,是对synchronized的一种升级,他是一个接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOToOlrj-1633617401243)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210808210609854.png)]

  • Lock它的实现类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nwEZteIi-1633617401244)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210808210804354.png)]

  • 他的常用实现类是ReentrantLock

  • synchronized是通过JVM实现锁机制,ReentrantLock是通过JDK实现锁机制

  • synchronized是一个关键字,ReentrantLock是一个类,本质上就不一样

  • ReentrantLock中文名叫重入锁,顾名思义就是可以给同一个资源添加多把锁

  • 解锁方式不一样:

    • synchronized:线程执行完后自动释放锁,
    • ReentrantLock:需要手动解锁

经典案例卖票

  • 卖票之synchronized锁
//卖票
public class lock01 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{
            for (int i = 0; i < 3000; i++) {
                ticket.sale();
            }},"张三").start();
        new Thread(()->{
            for (int i = 0; i <3000 ; i++) {
                ticket.sale();
            }},"李四").start();
    }
}
class Ticket{
    private Integer saleNum=0;//卖出票
    private Integer lastNum=3000;//剩余票
    public synchronized void sale(){
        if(lastNum>0){
            saleNum++;
            lastNum--;
            try {
                TimeUnit.MILLISECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"卖出了"+saleNum+"张票,剩余"+lastNum);
        }
        System.out.println("-=---");
    }
}
  • 卖票之Locked锁
 //卖票
public class lock01 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{
            for (int i = 0; i < 3000; i++) {
                ticket.sale();
            }},"张三").start();
        new Thread(()->{
            for (int i = 0; i <3000 ; i++) {
                ticket.sale();
            }},"李四").start();
    }
}
class Ticket{
    private Integer saleNum=0;//卖出票
    private Integer lastNum=3000;//剩余票
    private Lock lock=new ReentrantLock();
    public  void sale(){
        lock.lock();
        if(lastNum>0){
            saleNum++;
            lastNum--;
            try {
                TimeUnit.MILLISECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"卖出了"+saleNum+"张票,剩余"+lastNum);
        }
        lock.unlock();
        System.out.println("-=---");
    }
}
/*
当然我们可以上多把锁,但要注意上几把锁就要解几次锁
*/

synchronized和Lock锁的区别

1、synchronized 是自动上锁,自动解锁。Lock 锁是手动上锁,手动解锁

2、synchronized 是无法判断当前是否获取到了锁,Lock 可以判断当前获取到锁

3、synchronized 拿不到锁就会一直等待,Lock 不一定会一直等待(tryLock下面有讲到)

4、synchronized 是关键字,Lock 是接口

5、synchronized 是非公平锁,Lock 可以设置是否为公平锁

private Lock lock=new ReentrantLock(true);

公平锁:很公平,排队,当锁没有被占用时,当前线程需要判断队列中中是否有其他等待线程

非公平锁:不公平,插队,当锁没有被占用时,当前线程可以直接占用,而不需要判断队列中是否有等待线程

实际开发中推荐使用Lock锁的方式

  • ReentrantLock 具备限时性的特点,可以判断某一个线程在一定时间段内能否获取到锁,使用tryLock方法,返回的是Boolean类型,true表示可以获取到锁,false表示无法获取到锁
public class lock02 {
    public static void main(String[] args) {
        TimeOut timeOut = new TimeOut();
        new Thread(()->{timeOut.timeLock();},"A").start();
        new Thread(()->{timeOut.timeLock();},"B").start();
    }
}
class TimeOut{
    private ReentrantLock lock=new ReentrantLock();
    public void timeLock(){
        try {
            if(lock.tryLock(3, TimeUnit.SECONDS)){//判断三秒内能不能拿到锁
                System.out.println(Thread.currentThread().getName()+"拿到了锁");
                TimeUnit.SECONDS.sleep(5);
            }else{
                System.out.println(Thread.currentThread().getName()+"没拿到锁");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(lock.isHeldByCurrentThread()){//判断当前线程是否持有这个锁,
                lock.unlock();
            }
        }
    }
}
/*
lock.isHeldByCurrentThread()这个方法是ReentrantLock里的方法,不是Lock里的方法
这个方法表示判断当前线程是否持有这个锁
*/

生产者消费者模式

synchronized

public class lock03 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.producers();
            }},"A").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.consumers();
            }},"B").start();
    }
}
class Data{
    private Integer num=0;
    public synchronized void producers(){
        while(num!=0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num++;
        this.notify();
        System.out.println(Thread.currentThread().getName()+"生产了:"+num+"只鸡");
    }
    public synchronized void consumers(){
        while(num==0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }System.out.println(Thread.currentThread().getName()+"消费了:"+num+"只鸡");
        num--;
        this.notify();
    }
}
  • 必须使用while判断,不能用if,因为if它会存在线程虚假唤醒的问题,虚假唤醒就是一些wait方法会在除了notify的情况下被唤醒,不是真正的被唤醒。

因为if只会执行一次,执行完会接着向下执行if()外边的
而while不会,直到条件满足才会向下执行while()外边的

Lock

public class lock03 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.producers();
            }},"A").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.consumers();
            }},"B").start();
    }
}
class Data{
    private Integer num=0;
    private ReentrantLock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();
    public void producers(){
        lock.lock();
        while(num!=0){
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num++;
        condition.signal();
        System.out.println(Thread.currentThread().getName()+"生产了:"+num+"只鸡");
        if(lock.isHeldByCurrentThread()){lock.unlock();}
    }
    public synchronized void consumers(){
        lock.lock();
        while(num==0){
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"消费了:"+num+"只鸡");
        num--;
        condition.signal();
        if(lock.isHeldByCurrentThread()){lock.unlock();}
    }
}
  • 使用Lock锁,就不能使用waitnotify 来暂停线程和唤醒线程,而应该使用Condition的await和signal来暂停he唤醒线程。

7、ConcurrentModificationException

并发访问异常(没有读写分离,一边读一边写就会报异常)

public class lock04 {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //写
                list.add("a");
                //读
                System.out.println(Thread.currentThread().getName()+list);
                //同时对一个资源进行读写及回报这个异常
            },String.valueOf(i)).start();
        }
    }
}
/*
同时对一个资源进行修改
我们可以看出ArrayList是线程不安全的
*/

如何解决?

1、换一个资源,把ArrayList换成Vector

Vector list = new Vector<>();

Vector的add方法有synchronized关键字

x [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lmclo5Ui-1633617401245)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210809163211967.png)]

ArrayList是没有synchronized的,所以他的线程是不安全的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJpcY567-1633617401246)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210809163527194.png)]

2、使用Collections.synchronizedList()

List<String> list= Collections.synchronizedList(new ArrayList<>());

就是对ArrayList的一个处理

3、JUC: CopyOnWriteArrayList(读写分离)

List<String> list = new CopyOnWriteArrayList();
  • // CopyOnWriteArrayList里add方法的源码
    public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();//把原来的数组复制一份放到elements中
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);//定义一个新的数组把elements放进去,然后数																		组长度加1(为了放入e)
                newElements[len] = e;//把add的的内容放进新数组
                setArray(newElements);//把新数组替换原来的数组(set)
                return true;
            } finally {
                lock.unlock();
            }
        }
    
  • set

public class lock04 {
    public static void main(String[] args) {
//        Vector<String> list = new Vector<>();
//        List<String> list= Collections.synchronizedList(new ArrayList<>());
        Set<String> list = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 20; i++) {
            final int temp=i;
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //写
                list.add(String.valueOf(temp)+"a");
                //读
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
  • Map
public class lock04 {
    public static void main(String[] args) {
//        Vector<String> list = new Vector<>();
//        List<String> list= Collections.synchronizedList(new ArrayList<>());
        Map<String,Object> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            final int temp=i;
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             map.put(UUID.randomUUID().toString().substring(0,3),UUID.randomUUID().toString().substring(0,2));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}
/*
UUID.randomUUID().toString().substring(0,3)
生成一个随机数,取前两位。
*/

8、JUC工具类

  • CountDownLatch: 减法计数器

    • 可以用来倒计时,当两个线程同时执行时,若果要确保一个线程优先执行,可以使用计数器,当计数器清零的时候,再让另一个线程执行

    • new CountDownLatch(100)、countDownLatch.countDown()、countDownLatch.await()必须配合起来使用,创建的时候赋的值是多少,countDown()就必须执行多少次,否则计数器是没有清零的,计数器就不会停止,其它线程也无法唤醒,所以必须保证计数器清零,countDown()的调用次数必须大于构造函数的参数值

public class JUC01 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(100);
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("Thread线程++++++++++++++");
                countDownLatch.countDown();
            }
        }).start();
                countDownLatch.await();
        for (int i = 0; i < 100; i++) {
            System.out.println("main线程+++++++++++");
        }
    }
}
/*
CountDownLatch.await() 方法在倒计数为0之前会阻塞当前线程.计数器停止,唤醒其他线程
*/
  • CyclicBarrier:加法计数器

cyclicBarrier.await(); 在其他线程中试图唤醒计数器线程,当其他线程的执行次数达到计数器的临界值时,就唤醒计数器线程,并且计数器是可以重复使用的,当计数器的线程执行完成一次之后,计数器自动清零,等待下一次执行

public class JUC02 {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(20,()->{
            System.out.println("放行-----------");
        });//当线程执行完30次是,他就输出一次
        for (int i = 0; i < 100; i++) {
            final int temp=i;
            new Thread(()->{
                try {
                    cyclicBarrier.await();
                    System.out.println(temp);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
/*
final int temp=i;这里temp的作用是因为lambda中的变量必须是final或有效的final
*/
  • Semaphore:计数信号量

实际开发中主要用它来完成限流操作,限制可以访问某些资源的线程数量

使用Semaphore只有三个操作

1.初始化

2.获取许可

3.释放

每个线程在执行的时候,首先需要去获取信号量,只有获取到资源才可以执行,执行完毕之后需要释放资源,留给下一个线程。

public class JUC03 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5,true);//初始化,ture表是他是公平锁,不可以插队
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();//获得许可
                    System.out.println(Thread.currentThread().getName()+"进店购物");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println(Thread.currentThread().getName()+"出店");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

9、读写锁

本身是一个接口ReadWriteLock,实现类是ReentrantReadWriteLock,可以多线程同时读,但是同一个时间内只能有一个线程进行写入操作。

读写锁也是为了实现线程同步,只不过粒度更细,可以分别给读和写操作设置不同的锁

写入锁也叫独占锁,只能被一个线程占用,读取锁也叫共享锁,多个线程各一同时占用。

public class JUC04 {
    public static void main(String[] args) {
        ReadWrite readWrite = new ReadWrite();
        for (int i = 0; i < 5; i++) {
            final int temp=i;
            new Thread(()->{
                readWrite.write(temp,String.valueOf(temp));
            },String.valueOf(i)).start();
        }
        for (int i = 0; i < 5; i++) {
            final int temp=i;
            new Thread(()->{
                readWrite.read(temp);
            },String.valueOf(i)).start();
        }
    }
}
class ReadWrite{
    private HashMap<Integer,String> map=new HashMap<>();
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    public void write(Integer key,String value){
        /*
        * 写入
        * */
        readWriteLock.writeLock().lock();
        System.out.println(key+"开始写入");
        map.put(key, value);
        System.out.println(key+"写入完毕");
        readWriteLock.writeLock().unlock();
    }
    public void read(Integer key){
        /*
        * 读取
        * */
        readWriteLock.readLock().lock();
        System.out.println(key+"开始读取");
        map.get(key);
        System.out.println(key+"读取完毕");
        readWriteLock.readLock().unlock();
    }
}

10、线程池

预先创建好一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲中取出,用完之后不要销毁,还回到缓冲池中,为了提高资源的利用率–池化思想

优势

  • 提高线程的利用率
  • 提高响应速度
  • 便于同一管理线程对象
  • 可以控制最大的并发数

1、线程池初始化的时候创建一定数量的线程对象

2、如果缓冲池中没有空闲的线程对象,则新来的任务进入等待队列

3、如果缓冲池中没有空闲的线程对象,等待队列中也已经填满,可以申请在创建一定数量的新线程对象,直到到达线程池的最大值,这时候如果还有新的任务进来,只能选择拒绝

//实际开发不推荐使用工具类
public class pool01 {
    public static void main(String[] args) {
        //单例-只开启一个线程
//        ExecutorService executorService= Executors.newSingleThreadExecutor();
        //指定线程数量
//        ExecutorService executorService=Executors.newFixedThreadPool(5);
        //缓存线程池,开启的的线程数(Integer.MAX_VALUE)
        ExecutorService executorService=Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            final int temp =i;
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"----"+temp);
            });
        }
        executorService.shutdown();
    }
}
/*
newSingleThreadExecutor
newFixedThreadPool
newCachedThreadPool
这三个工具类都是通过new ThreadPoolExecutor去实现的,所以在实际开发中我们应该自己去通过new ThreadPoolExecutor定义,
*/

无论哪种线程池都是工具类Executor封装的,底层代码都一样,都是通过创建ThreadPoolExecutor 对象来完成线程池的构建

  • ThreadPoolExecutor 源码
    public ThreadPoolExecutor(int corePoolSize, //核心池大小,初始化的线程数量
                              int maximumPoolSize,//线程池最大线程数,他决定了线程池的上限
                              long keepAliveTime,//存活时间
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize: 核心池大小,初始化的线程数量

  • maximumPoolSize: 线程池最大线程数,他决定了线程池的上限

corePoolSize就是线程池的大小,maximumPoolSize是一种补救措施,任务量突然增大的时候的一种补救措施。

  • keepaliveTime:线程对象的存活时间

  • unit:线程对象存活时间单位

  • workQueue:等待队列

  • threadFactory:线程工厂,用来创建线程对象

  • handler:拒绝策略(四种)

    • AbortPolicy: 直接抛出异常

      • java.util.concurrent.RejectedExecutionException
    • DiscardPolicy:放弃任务不抛出异常

    • DiscardOldestPolicy:尝试与等待队列中最前的任务去争夺,不抛出异常

    • CallerRunsPolicy:谁调用,谁处理

      • main=====>办理业务5
        pool-1-thread-3=====>办理业务4
        pool-1-thread-2=====>办理业务1
        pool-1-thread-1=====>办理业务0
        pool-1-thread-3=====>办理业务2
        pool-1-thread-1=====>办理业务3
        

自定义线程池

//自定义线程池
public class pool02 {
    public static void main(String[] args) {
        ExecutorService executorService=null;
        try {
            //自定义线程池参数
            executorService=new ThreadPoolExecutor(
                    2,
                    3,
                    1L,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(2),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy());
            for (int i = 0; i < 6; i++) {
                final int temp=i;
                executorService.execute(()->{
                    try {
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"=====>办理业务"+temp);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
           executorService.shutdown();
        }
    }
}
/*
i=1,2,表示1,2个人来办理业务,满足核心池大小,按照i的大小来开启核心线程
i=3,4,表示3,4个人来办理业务,核心池满了,进入排队队列
i=5。表示5个人来办理业务,核心池满了,队列满了,只能开启补救措施,再开一个线程
i=6.表示6个人来办理业务,核心池满了,队列满了,而且超过了线程池的上限,所以只能使用拒绝策略
第六个人只能被拒之门外
*/

线程池三大考点

1、Executors工具类的三种实现

//单例-只开启一个线程
ExecutorService executorService= Executors.newSingleThreadExecutor();
//指定线程数量
ExecutorService executorService=Executors.newFixedThreadPool(5);
//缓存线程池,开启的的线程数(Integer.MAX_VALUE)
ExecutorService executorService=Executors.newCachedThreadPool();

2、七个参数

int corePoolSize, //核心池大小,初始化的线程数量
int maximumPoolSize,//线程池最大线程数,他决定了线程池的上限
long keepAliveTime,//存活时间
TimeUnit unit,//线程对象存活时间单位
BlockingQueue<Runnable> workQueue,//等待队列
ThreadFactory threadFactory,//线程工厂,用来创建线程对象
RejectedExecutionHandler handler//拒绝策略

3、四种拒绝策略

- AbortPolicy//直接抛出异常
- DiscardPolicy//放弃任务不抛出异常
- DiscardOldestPolicy//尝试与等待队列中最前的任务去争夺,不抛出异常
- CallerRunsPolicy//谁调用,谁处理

11、ForkJoin框架

ForkJoin是JDK=1.7之后发布时的多线程并发处理框架,功能上和JUC类似,JUC更多时候是使用单个类完成操作,ForkJoin使用多个类同时完成某项工作,处理上比JUC更加丰富,实际开发中使用的场景并不是很多,互联网公司真正有高并发需求的时候才会使用,面试会加分

本质上是对线程池的一种补充,对线程池功能的一种扩展,基于线程池,它的核心思想就是讲一个大型任务拆分成很多小任务,分别执行,最后将小任务的结果进行汇总,生成最终结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aFmaV3gX-1633617401247)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210810204734894.png)]

本质就是把一个线程的任务拆分成多个小任务,然后有多个任务并发执行,最终将结果进行汇总。

比如A B两个线程同时执行,A的任务比较多,B的任务相对较少,B先执行完毕,这时候B去帮助A完成任务(将A的一部分拿过来替A执行,执行完毕后再将结果汇总),从而提高效率,这就是工作窃取

工作窃取

ThreadPoolExecutor线程池本身是不会工作窃取(互帮互助)的,ForkJoin为了提高效率,

ForkJoin框架,核心是两个类

  • ForkJoinTask(描述任务)拆分任务
  • ForkJoinPool(线程池)提供多线程并发工作窃取。

使用ForkJoinTask最重要的就是要搞清楚如何拆分任务,这里用的是递归思想

1、需要创建ForkJoinTask任务,ForkJoinTask是一个抽象类,不能直接创建ForkJoinTask的实例对象,我们需要自定义一个类,继承ForkJoinTask子类RecursiveTask,Recursive就是递归的意思,该类就提供了实现递归的功能

/*
10亿求和
*/
public class ForkJoinTask01 extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    private Long temp=100_0000L;

    public ForkJoinTask01(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        if(end-start<temp){
            Long sum=0L;
            for (Long i = start; i <= end; i++) {
                    sum+=i;
            }
            return sum;
        }else{
            Long avg=(start+end)/2;
            ForkJoinTask01 Task1 = new ForkJoinTask01(start,avg);
            Task1.fork();
            ForkJoinTask01 Task2 = new ForkJoinTask01(avg+1,end);
            Task2.fork();
            return Task1.join()+Task2.join();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        long StartTimeMillis = System.currentTimeMillis();
        ForkJoinPool joinPool = new ForkJoinPool();
        ForkJoinTask01 task = new ForkJoinTask01(0L,10_0000_0000L);
        joinPool.execute(task);
        Long sun=0L;
        try {
            sun=task.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long endTimeMillis = System.currentTimeMillis();
        System.out.println(sun+"执行了"+(endTimeMillis-StartTimeMillis));
        joinPool.shutdown();
    }
}

12、Volatile关键词

public class text01 {
    public static void main(String[] args) {
        List<String> list= new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add("a");//有输出相同长度的集合,就是因为线程各自开辟了内存空间
                System.out.println(list);
            }).start();
        }
    }
}

Volatile 是JVM提供的轻量级同步机制,可见性,主内存对象线程可见(好好理解什么叫主内存对象线程可见)

  • 什么是内存可见性
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPsd8PSe-1633617401248)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210811160413115.png)]
public class text02 {
    private static int num=0;
    public static void main(String[] args) {
        new Thread(()->{
            while (num==0){
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println(num);

    }
}
/*
线程从共享变量中复制一份到工作内存中
由上述代码中我们可以看出当num=0时,线程进入where死循环,但是当我们再次给num赋值,重启,程序输出1后线程还是在死循环并没有停止,这我们就可以看出内存的可见性,一开始线程从共享变量中拿到值并复制到工作内存中,进入死循环,当执行到num=1时,我们的主线程(main)复制一份num的值,把num赋值1。所以副线程并不会退出死循环
*/

Volatile的作用就是跳工作内存,把它变成可见性,主内存对象线程可见,

private static volatile int num=0;
image-20210811162716259

思考一个问题?如果我不用Volatile,并且我在where里输出了,那么线程会停止吗?

while (num==0){
                System.out.println("---");
            }
  • 一般来说,线程会一直循环输出“—“,不会退出循环,但我们操作后发现他退除了循环???

    为什么循环里没有输出他就感知不到num的改变,循环里有任务他就能感知到num的改变

  • 因为一个线程执行完任务之后,会把变量存回到主内存中,并且从内存中读取当前变量最新的值,若果是一个空的任务,则不会重新读取内存中的值

线程池 里的队列workQueue

一个阻塞队列,用来执行等待指定的队列,常用的阻塞队列有以下几种:

  • ArrayBlockingQueue:基于数组的先进先出队列,创建时必须指定大小。
  • LinkBlockingQueue:基于链表的先进先出队列,创建时可以不指定大小,默认值是Integer.MAX_VALUE
  • SynchronousQueue:他不会保存提交的任务,而是直接新建一个线程来执行新来的任务。
  • PriorityBlockQueue:他是具有优先级的阻塞对列

集合框架

为什么要使用集合框架?

1、数组的长度是固定的

2、数组无法同时存储多个不同的数据类型

集合简单理解就是一个长度可以改变,可以保持任意数据类型的动态数组。

集合本身是数据结构的基本概念之一,我们这里说的集合是java语言是对这种数据结构的具体实现。

java里的集合不是由一个类来完成的,而是由一组接口和类构成了一个框架体系,大致可以分为三层,最上层是一组接口,继而是接口的实现类。

接口

  • Collection:集合框架最基础的接口,最顶层的接口
  • List: Collection 的子接口,有序,不唯一的对象,最常用的接口
  • Set: Collection 的子接口,是无序,唯一的对象
  • Map: 独立于Collection 的另外一个接口,最顶层的接口,存储一组键值对象,提供键到值的映射
  • Iterator: 输出集合元素的接口,一般用于无序接口
  • ListIterator: Iterator 子接口,可以双向输出集合中的元素
  • Enumeration: 传统的输出接口,已经被Iterator取代
  • SortedSet: Set的子接口,可以对集合中的元素进行排序
  • SortedMap: Map的子接口,可以对集合中的元素进行排序
  • Queue: 队列接口。
  • Map. Entry: Map的内部接口,描述Map中存储的一组键值对元素

1、Collection接口

/* 
 *@see     Set
 * @see     List
 * @see     Map
 * @see     SortedSet
 * @see     SortedMap
 * @see     HashSet
 * @see     TreeSet
 * @see     ArrayList
 * @see     LinkedList
 * @see     Vector
 * @see     Collections
 * @see     Arrays
 * @see     AbstractCollection
 * @since 1.2
 */

public interface Collection<E> extends Iterable<E> {
    // Query Operations
  • Iterable 这个接口是最最最顶层的接口,实现了集合的输出

Collection是集合框架中最基础的父接口,可以存储一组无序,不唯一的对象,一般不直接使用该接口,也不能被实例化,只是用来提供规范。

Collection是Iterable接口的子接口。

  • int size();                         //获取集合长度
    
  • boolean isEmpty();                  //判断集合是否为空
    
  • boolean contains(Object o);         //判断集合中是否存在某个对象,包含
    
  • Iterator<E> iterator();             //实例化Iterator接口,遍历集合
    
  • Object[] toArray();                 //将集合转换为一个数组
    
  • <T> T[] toArray(T[] a);             //将集合转化为指定数据类型的数组
    
  • boolean add(E e);                   //向集合中添加元素
    
  • boolean remove(Object o);           //从集合中删除元素
    
  • boolean containsAll(Collection<?> c);//判断集合中是否存在另一个集合中的所有元素
    
  • boolean addAll(Collection<? extends E> //向集合中添加某个集合中的所有元素
    
  • boolean removeAll(Collection<?> c);    //从集合中删除某个元素的所有元素
    
  • void clear();                          //清除集合中的所有元素
    
  • boolean equals(Object o);              //判断两个集合是否相等
    
  • int hashCode();                        //返回集合的哈希值
    

2、Collection 子接口

  • List :用来存放有序,不唯一的元素
  • Set :存放无序,唯一的元素
  • Queue :队列接口

3、List接口

 /*
 * @see Collection
 * @see Set
 * @see ArrayList
 * @see LinkedList
 * @see Vector
 * @see Arrays#asList(Object[])
 * @see Collections#nCopies(int, Object)
 * @see Collections#EMPTY_LIST
 * @see AbstractList
 * @see AbstractSequentialList
 * @since 1.2
 */

public interface List<E> extends Collection<E> {
    // Query Operations 

除了继承Collection的方法外

List常用的扩展方法

  • E get(int index);                //通过下标返回集合中对应位置的元素
    
  • E set(int index, E element);     //在集合中指定位置存入对象
    
  • int indexOf(Object o);           //从前往后查找某个对象在集合中的位置
    
  • int lastIndexOf(Object o);       //从后往前查找某个对象在集合中的位置
    
  • ListIterator<E> listIterator();  //实例化Iterator接口,遍历集合
    
  • List<E> subList(int fromIndex, int toIndex); //通过下标截取List集合 
    

4、List接口的实现类

  • ArrayList: 是开发中使用频率最高的List实现类,实现了长度可变的数组,在内存中分配 连续空间,所以读取快,增删慢

    • 非线程安全。
  • Vector:线程安全 ,效率低,实现线程安全直接通过synchronized关键字来完成

list.add(1,"你好1 ");   //在1的位置加个一个数据
list.set(1,"你好");     //把1位置上的值替换掉
  • Stack: Vector的子类,实现了栈的数据结构(先进后出)
public class Collection02 {
    public static void main(String[] args) {
        Stack<String> stack=new Stack<>();
        String push = stack.push("1");//入栈
        System.out.println(push);//输出为1
        stack.add("2");//add是继承Vector的方法,输出Boolean值
        stack.push("3");
        System.out.println("栈顶元素"+stack.peek());
        System.out.println(stack);
        System.out.println("栈顶元素"+stack.pop());
        System.out.println(stack);
        System.out.println(stack.search("1"));
    }
}

/*
peek():从栈中复制栈顶元素并输出
pop():直接从栈中把栈顶元素取出
输出:
栈顶元素3
[1, 2, 3]
栈顶元素3
[1, 2]
*/

我们看一看stack.search方法的源码: 它使用了List的lastIndexOf方法,是从后往前查找元素的位置

public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }
  • LinkedList:实现先进先出的队列,采用链表的形式存储。

ArrayList和LinkedList的区别:内存中存储形式不同,ArrayList采用数组的方式,LinkedList采用的是链表的形式

数组在内存中存储空间是连续的,读取快,增删慢。

因为数组在内存中是连续的,所以取数据可以根据寻址公式(address=0000+4*n)很快求出目标元素的内存地址。又因为内存是连续的,所以新增或删除元素,必然需要移动数据,而且数组长度越长,需要移动的元素越多,操作就越慢

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6AUqDY8q-1633617401249)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210812143701639.png)]

链表在内存中是不连续的,读取慢,增删快。链表在内存上是不连续的,没有固定的公式可以使用,要读取只能从第一位开始,一直遍历到目标元素,数据规模越大,操作越慢。

增删快,因为只需要重新设置目标元素前后两个节点的后置指针即可,与数据规模无关

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SMz59u5L-1633617401249)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210812144733094.png)]

public class Collection03 {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("1");
        linkedList.add("2");
        linkedList.offer("3");
        System.out.println(linkedList);//[1, 2, 3]
        linkedList.push("4");//加到了链表的头部
        System.out.println(linkedList);//[4, 1, 2, 3]
        linkedList.addFirst("5");
        System.out.println(linkedList);//[5, 4, 1, 2, 3]
        linkedList.addLast("7");
        System.out.println(linkedList);//[5, 4, 1, 2, 3, 7]
        System.out.println(linkedList.peek());//5
        System.out.println(linkedList.peekFirst());//5
        System.out.println(linkedList.peekLast());//7
        System.out.println(linkedList.pop());//5
        System.out.println(linkedList);//[4, 1, 2, 3, 7]

    }
}

思考一个问题??

  • LinkedList和Stack都有pop()方法,他们有什么区别和相同点??

    pop():方法都是取出集合中的第一个元素,但是两者的顺序是相反的,Stack是“后进先出”,所以pop取出的是最后一个元素,LinkedList是“先进先出”,所以pop取出的是第一个元素。

    LinkedList实现了Deque接口,而Deque是Queue的子接口,Queue就是队列,底层实现了队列的数据结构

    实际开发中,不能直接实例化Queue对象。

    Queue的实现类是AbstractQueue,这是一个抽象类,不能直接实例化,开发中需要用到他的子类PriorityQueue

    Queue中添加的数据必须是有顺序的。

public class Collection04 {
    public static void main(String[] args) {
        PriorityQueue<Object> priorityQueue = new PriorityQueue<>();
//        priorityQueue.add("1");
//        priorityQueue.add("2");
//        priorityQueue.add("A");
//        priorityQueue.add("B");
//        priorityQueue.comparator();
        priorityQueue.add(new A(1));
        priorityQueue.add(new A(2));
        System.out.println(priorityQueue);
    }
}
class A implements Comparable{
    private int num;
    public A(int num){
        this.num= num;
    }

    @Override
    public String toString() {
        return "A{" +
                "num=" + num +
                '}';
    }   

    @Override//compareTo比较
    public int compareTo(Object o) {
        A a=(A)o;
        if (this.num>a.num) {
            return 1;
        }else if (this.num==num){
            return 2;
        }else {
            return -1;
        }
    }
}

Queue 默认个元素升序,及自然排序

5、Set接口

跟List一样,Set是Collection的子接口,Set接口是以散列的形式存储数据,所以元素是没有顺序的,可以存储一组无序且唯一的数据

 /*
 * @see Collection
 * @see List
 * @see SortedSet
 * @see HashSet
 * @see TreeSet
 * @see AbstractSet
 * @see Collections#singleton(java.lang.Object)
 * @see Collections#EMPTY_SET
 * @since 1.2
 */

public interface Set<E> extends Collection<E> {
    // Query Operations

Set常用实现类:

  • HashSet
  • LinkedHashSet
  • TreeSet

HashSet:是我们开发中见经常能够使用的一个实现类,存储一组无序且唯一的对象。

无序:元素的存储顺序和便利顺序不一样。

public class Collection05 {
    public static void main(String[] args) {
        HashSet<Object> hashSet=new HashSet<>();
        hashSet.add("C");
        hashSet.add("A");
        hashSet.add("B");
        System.out.println(hashSet);//[A, B, C]
        hashSet.remove("B");
    }
}

LinkedHashSet是Set的另外一个实现类,可以存储一组有序且唯一的元素

有序: 元素的存储顺序和便利顺序一致

public class Collection {
    public static void main(String[] args) {
        LinkedHashSet<Object> set=new LinkedHashSet<>();
        set.add("b");
        set.add("a");
        set.add("c");
        System.out.println(set);//[b, a, c]
        set.spliterator().forEachRemaining((n)->{
            System.out.println(n);//b a c
        });
    }
}
  • 思考equals 和 == 的区别???

所有类中的equals都是继承Object类,Object中原生的equals方法就是在通过==进行判断

 /*
 * @see     java.util.HashMap
 */
public boolean equals(Object obj) {
    return (this == obj);
}

但是每个类都可以对equals方法进行重写, 覆盖掉之前的 == 进行逻辑的判断,该用新的逻辑判断是否相等。

LinkedHashSet 如何判断两个对象是否相等?

首先会判断两个对象的HashCode是否相等

什么是HashCode?

对象的内部信息(内存地址,属性值等),通过某种特定规则转换为一个散列值,就是该对象的HashCode。

new A(2).hashCode()
  • 两个不同对象的hashCode值可能相等
  • hashCode 不相等的两个对象一定不是同一个对象

集合在判断两个对象是当否相等的时候,首先会比较他们的hashCode,如果hashCode不相等,则认为不是同一个对象,可以添加。

如果hashCode值相等,还不能认为两个对象是相等的需要通过equals进行进一步的判断,equals,则两个对象相等,否则两个对象不相等

== :判断的是栈内存中的值

引用类型的数据,栈内存中存储的是地址,所以此时 == 判断的是引用地址。

基本数据类型,栈内存中存储的是具体的数值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILmv1uui-1633617401250)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210815075023283.png)]

栈中存储的是变量

Data data;引用类型具体的对象(属性)存储在堆中,再将堆中对象的内存地址赋值给栈中的变量data,data中存储的就是地址

int num;基本数据类型不需要用到堆内存,变量在栈中,变量的值直接存储在栈中。

6、TreeSet

LinkedHashSet和TreeSet都是存储在一组有序且唯一的数据,但是这里的两个有序是有区别的

LinkedHashSet的有序是指元素的存储顺序和遍历顺序一样

TreeSet的有序是指集合内部会自动对所有元素按照升序进行排序,无论存入的顺序是什么,遍历的时候一定按照升序输出,(按照ASCII码排序)

 /*
 * @see     Collection
 * @see     Set
 * @see     HashSet
 * @see     Comparable
 * @see     Comparator
 * @see     TreeMap
 * @since   1.2
 */

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
public class Collection01 {
    public static void main(String[] args) {
        TreeSet<Object> treeSet = new TreeSet<>();
//        treeSet.add("BCBC");
//        treeSet.add("CC");
//        treeSet.add("AC");
//        treeSet.add("AB");
//        treeSet.add("AA");
//        treeSet.add("BCBD");
        treeSet.add(new Data(1));
        treeSet.add(new Data(3));
        treeSet.add(new Data(2));
        treeSet.add(new Data(1));
        System.out.println(treeSet);
        Iterator iterator=treeSet.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
class Data implements Comparable{
    private int num;

    public Data(int num) {
        this.num = num;
    }
/*
* A.compareTo(B)
*返回值
* 1:表示A大于B
* 0:表示A等于B
* -1:表示A小于B
* */
    @Override//compare比大小
    public int compareTo(Object o) {
        if(o instanceof Data){
            Data data=(Data)o;
            if(this.num>data.num){
                return 1;
            }else if (this.num==data.num){
                return 0;
            }else{
                return -1;
            }
        }
        return 0;
    }

    @Override
    public String toString() {
        return "Data{" +
                "num=" + num +
                '}';
    }
}

7、Map

Key-Value: 数据字典,

List,Set接口都是Collection 的一个子接口,Map 接口是于Collection完全独立的一个体系。

List & Set VS Map

List & Set & Collection 只能操作单个元素,Map可以操作一对元素,因为Map操作空间是Key-Value映射。

 /*
 * @see HashMap
 * @see TreeMap
 * @see Hashtable
 * @see SortedMap
 * @see Collection
 * @see Set
 * @since 1.2
 */
public interface Map<K,V> {
    // Query Operations

Map 接口定义时使用了泛型,并且定义了两个泛型K和V,K表示Key,规定键元素的数据类型,V表示Value规定值元素的数据类型

  • int size();
    
  • boolean isEmpty();
    
  • boolean containsKey(Object key); //判断集合中是否含有key
    
  • boolean containsValue(Object value);//判断集合中是否含有Value
    
  • V get(Object key);//取出集合中key对应的Value
    
  • V put(K key, V value);//向集合中存入一组Key-Value
    
  • V remove(Object key);//删除集合中key对应的value
    
  • void putAll(Map<? extends K, ? extends V> m);//向集合中添加另外一个Map
    
  • void clear();//清除集合中所有的元素
    
  • Set<K> keySet();//取出集合中所有的Key值,返回一个Set集合
    
  • Collection<V> values();//取出集合中所有的value,返回一个Collection
    
  • Set<Map.Entry<K, V>> entrySet();//将 Map 以 Set 的形式输出
    
  • int hashCode();//获取集合的散列值
    
  • boolean equals(Object o);//比较两个集合是否相等
    

8、Map 接口的常用实现类

  • HashMap: 存储一组无序的,Key不可以重复,Value可以重复的元素
  • Hashtable: 存储一组无序的,Key不可以重复,Value可以重复的元素
  • TreeMap: 存储一组有序,Key不可以重复,Value可以重复的元素,可以按照Key进行排序

Hashtable用法和HashMap基本一样,他们的区别是,Hashtable是线程安全的,性能较低。HashMap是非线程安全的,但是性能较高

  • HashMap方法,没有用 synchronized 修饰,所以是非线程安全的,
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
  • Hashtable ,方法用 synchronized 修饰,所以是线程安全的
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}

HashMap和Hashtable保存的数据都是无序的,Map的另外一个实现类TreeMap主要功能是按照Key对集合中的元素进行排序。

9、Collections 工具类

Collection 接口,List 和 Set 的父接口。

Collections 不是一个接口,他是一个工具类,专门提供了一些对集合的操作,方便开发者去使用,完成相应的业务功能。

Collections 针对集合的工具类,Collection

Arrays 针对数组的工具类,Array

  • public static sort();//对集合进行排序
    
  • public static int binarySearch(List list,Object v);//查找v在结合中的位置,集合必须是升序排序,二分查找
    
  • private static get(List list, int index) //返回集合中index的值
    
  • public static void reverse(List<?> list)//对集合进行反序输出
    
  • public static void swap(List<?> list, int i, int j)//交换集合中指定位置的两个元素
    
  • public static <T> void fill(List<? super T> list, T obj)//将结合中所有元的替换形成T的数据类型
    
  • public static T min(Collection<? extends T> coll) //返回集合中的最小值
    
  • public static T max(Collection<? extends T> coll, Comparator<? super T> //返回集合中的最大值
    
  • public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)//在List中用new替换old
    
  • public static <T> boolean addAll(Collection<? super T> c, T... elements)//向集合中添加元
    

可变参数,再调用方法的时候,参数可以是任意个数,但是类型必须匹配

public static void test(int... arg){
    
} 

但是下列写法,可以传任意类型,任意数量的参数,多态的一种具体表现形式

public static void test(Object... arg){
    
}

Java中默认输出对象的格式:对象所属的全类名(全限定类名)带着包名的类名+@+对象的哈希值

JavaScript 脚本语言 逐行编译

Java是必须全部编译之后,统一执行,假如有十行java代码,必须先对这10行代码进行编译,通过之后,再交给JVM执行

js逐行执行,执行一行算一行,假如有10行JS代码,一行一行的开始执行,执行到第五行报错,那么6-10行将不再执行,但是已经执行的前五行结果不变。

public class demo01 {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<>();
        list.add("Hello");
        list.add("Word");
        Collections.addAll(list,"JavaEE","JavaME","Java");
        System.out.println(list);//[Hello, Word, JavaEE, JavaME, Java]
        Collections.sort(list);
        System.out.println(list);//[Hello, Java, JavaEE, JavaME, Word]
        //binarySearch,二分查找法(集合中的元素必须升序排列)
        int binarySearch = Collections.binarySearch(list, "Java");
        System.out.println("Java的下标"+binarySearch);//Java的下标1
        Collections.replaceAll(list,"Hello","Word");
        System.out.println(list);//[Word, Java, JavaEE, JavaME, Word]
    }
}

泛型

泛型(Generics),是指在类定义时不指定类中信息的数据类型,而是暂时用一个标识符来替代,当外部实例化对象的时候再来指定具体的数据类型

//定义A类的时候就指定了a的类型
public class A{
    private int a;
}
//定义A类的时候不指定属性的类型
public class A<TE,M>{
    private T b;
    public E B(M m){
        return E;
    }
}
//调用时
A<T,E,M> a=new A<>();

优点:这样极大的提升了程序的灵活性,提升了类的扩展性,泛型可以指代类中成员变量的数据类型,方法的返回值类型以及方法的参数类型。

1、泛型的应用

自定义类中添加泛型

public class 类名<泛型1,泛型2,泛型3...>{
	private 泛型1 属性名;
    public 泛型2 方法名(泛型3){
        方法体;
    }
}
public class Generics01 {
    public static void main(String[] args) {
        Time<Integer,String,Float> time=new Time<>();
        time.setHello(1);
        time.setWord("你好");
        time.setJava(1.1f);
        System.out.println(time);//Time{Hello=1, Word=你好, Java=1.1}
    }
}
class Time<T,E,M>{
    private T Hello;
    private E Word;
    private M Java;

    @Override
    public String toString() {
        return "Time{" +
                "Hello=" + Hello +
                ", Word=" + Word +
                ", Java=" + Java +
                '}';
    }

    public T getHello() {
        return Hello;
    }

    public void setHello(T hello) {
        Hello = hello;
    }

    public E getWord() {
        return Word;
    }

    public void setWord(E word) {
        Word = word;
    }

    public M getJava() {
        return Java;
    }

    public void setJava(M java) {
        Java = java;
    }
}

2、泛型通配符

有一个参数为ArrayList的方法,我们希望这个方法既可以接收泛型是String的集合,又可以接受泛型为Intger的集合,怎么实现??

  • 多态在泛型中不使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Nnsdb9X-1633617401250)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20210819144905021.png)]

public class generics02 {
    public static void main(String[] args) {
        ArrayList<String> arrayList=new ArrayList<>();
        ArrayList<Integer> arrayList1=new ArrayList<>();
        Test(arrayList);
        Test(arrayList1);
    }
    public static void Test(ArrayList<?> arrayList){}
}

ArrayList<?>表示可以使用任意的泛型类型的对象,这样test就具备通用性了。

3、泛型的上限和下限

  • 上限:表示实例化时具体的数据类型,可以是上限类型的子类或者上限类型本身,用extends表示
  • 下限:表示实例化时具体的数据类型,可以是下限类型的父类或者下限类型本身,用super表示
类名<泛型标识 extends 上限类名>
类名<泛型标识 super 下限类名>
public class generics03<T> {
    public static void main(String[] args) {
        test(new generics03<Integer>());
        test(new generics03<Float>());
        test(new generics03<Number>());
        //---------------------------------
        test1(new generics03<String>());
        test1(new generics03<Object>());
    }
    /*
    * 泛型上限
    * */
    public static void test(generics03<? extends Number> generics03){}
    /*
    * 泛型下限
    * */
    public static void test1(generics03<? super String> generics03){}
}

4、泛型接口

接口<>

public interface generics04<T> {
    public T getValue();
}

实现泛型接口有两种方式

  • 实现类在定义时继续使用泛型标识
public class generics04Impl<T> implements generics04{
    private T obj;

    public generics04Impl(T obj) {
        this.obj = obj;
    }

    @Override
    public T getValue() {
        return this.obj;
    }
}
  • 实现类在定义时直接给出具体的数据类型
public class generics04Impl01 implements generics04<String> {
    private String Obj;
    public generics04Impl01(String obj){
        this.Obj=obj;
    }
    @Override
    public String getValue() {
        return this.Obj;
    }
}
public class generics04VO {
    public static void main(String[] args) {
        generics04Impl generics=new generics04Impl<String>("你好");
        System.out.println(generics.getValue());
        generics04Impl01 generics2=new generics04Impl01("世界");
        System.out.println(generics2.getValue());
    }
}

Java实用类

类似Arrays,Collections

1、枚举

枚举Enum,是一种有确定值区间的数据类型,本质上就是一个类,具有简洁,方便,安全等特点

枚举的值被约束到了一个特定的范围内,只能从这个范围以内取值

为什么要有枚举??????????

因为在描述某些对象的属性时,该属性的值不能随便定义,必须在某个特定的区间内取值

出于对数据的安全性考虑,类似这种有特定取值范围的数据,我们就可以使用枚举来描述

枚举指由一组常量组成的类型,指定一个取值区间,我们只能从该区间中取值

public enum  Week {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;
}
final class Week extends Enum{
    public static final Week MONDAY;
    public static final Week TUESDAY;
    public static final Week WEDNSDAY;
    public static final Week THURSDAY;
    public static final Week FRIDAY;
    public static final Week SATURDAY;
    public static final Week SUNDAY;
    private static final Week $VALUES[];

    static{
        MONDAY = new Week("MONDAY",0);
        TUESDAY = new Week("TUESDAY",1);
        WEDNSDAY = new Week("WEDNSDAY",2);
        THURSDAY = new Week("THURSDAY",3);
        FRIDAY = new Week("FRIDAY",4);
        SATURDAY = new Week("SATURDAY",5);
        SUNDAY = new Week("SUNDAY",6);
        $VALUES[] = (new Week[]{
            MONDAY,TUESDAY,WEDNSDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
        })
    }

    public static Week[] values(){
        return $VALUES.clone();
    }

    public static Week valueOf(String s){
        return Enum.valueOf(s);
    }

    private Week(String s,int i){
        super(s,i);
    }

}

2、Math

Math类为开发者提供

public class Test {
    public static void main(String[] args) {
        System.out.println("常量E:"+Math.E);//常量E:2.718281828459045
        System.out.println("常量PI:"+Math.PI);//常量PI:3.141592653589793
        System.out.println("9的平方根:"+Math.sqrt(9));//9的平方根:3.0
        System.out.println("8的立方根:"+Math.cbrt(8));//8的立方根:2.0
        System.out.println("2的3次方:"+Math.pow(2,3));//2的3次方:8.0
        /*
        * 这里的int类型的1被自动转换成double类型变成1.0
        * 类型转换只有强制转换和自动转换
        * */
        System.out.println("较大值:"+Math.max(6.5, 1));//较大值:6.5
        System.out.println("绝对值:"+Math.abs(-1));//绝对值:1
        System.out.println(Math.ceil(10.0000001));//11.0
        System.out.println(Math.floor(10.999999));//10.0
        //0<Math.random()<1
        System.out.println("随机数:"+Math.random());//随机数:0.7138202757555706
        System.out.println("随机数:"+ (int) (Math.random() * 10));//随机数:1
        System.out.println("四舍五入:"+Math.rint(5.5));//6.0
    }
}

3、Random

用来产生随机数的类,并且可以指定任意区间,在此区间范围内产生一个随机数

  • public Random();//创建一个无参随机数构造器,使用系统时间作为默认种子
    
  • public Random(Long seed);//使用Long类型的种子创建一个随机数构造器
    
  • public boolean nextBoolean();//返回一个Boolean类型的随机数
    
  • public double nextDouble();//返回一个double类型的随机数,0.0~1.0之间
    
  • public float nextFloat();//返回一个float类型的随机数,0.0~1.0之间
    
  • public int nextInt();//返回一个int类型的
    
  • public int nextInt(n);//返回一个0~n的随机数,[0,n)
    
    name描述
    public static sort()对集合进行排序
    public static int binarySearch(List list,Object v)查找 v 在 list 中的位置,集合必须是生序排列
    public static get(List list,int index)返回 list 中 index 位置的值
    public static void reverse(List list)对 list 进行反序输出
    public static void swap(List list,int i,int j)交换集合中指定位置的两个元素
    public static void fill(List list,Object obj)将集合中所有元素替换成 obj
    public static Object min(List list)返回集合中的最小值
    public static Object max(List list)返回集合中的最大值
    public static boolean replaceAll(List list,Object old,Object new)在 list 集合中用 new 替换 old
    public static boolean addAll(List list,Object… obj)向集合中添加元素

4、String

java通过String类来创建和操作字符串

  • String 实例化

1、直接赋值

String str="HelloWord";

2、通过构造函数来创建对象

String str=new String("HelloWord");
public class Test{
    public static void main(String[] arg){
        String str="Hello";
        String Str2=new String("Hello");
        System.out.println(str==str2);//false
        System.out.println(str.equals(str2)));//ture
        
    }
}

String不是基本数据类型

5、String的常用方法

方法描述
public String()创建一个空的字符串对象
public String(String value)创建一个值为 value 的字符串对象
public String(char value[])将一个char数组转换为字符串对象
public String(char value[],int offset, int count)将一个指定范围的char数组转为字符串对象
public String(byte value[])将一个byte数组转换为字符串对象
public String(byte value[],int offset, int count)将一个指定范围的byte数组转为字符串对象
public int length()获取字符串的长度
public boolean isEmpty()判断字符串是否为空
public char charAt(int index)返回指定下标的字符
public byte[] getBytes()返回字符串对应的byte数组
public boolean equals(Object anObject)判断两个字符串值是否相等
public boolean equalsIgnoreCase(Object anObject)判断两个字符串值是否相等(忽略大小写)
public int compareTo(String value)对字符串进行排序
public int compareToIgnoreCase(String value)忽略大小写进行排序
public boolean startsWith(String value)判断字符串是否以 value 开头
public boolean endsWith(String value)判断字符串是否以 value 结尾
public int hashCode()返回字符串的 hash 值
public int indexOf(String str)返回 str 在字符串中的下标
public int indexOf(String str,int formIndex)从指定位置查找字符串的下标
public String subString(int beginIndex)从指定位置开始截取字符串
public String subString(int beginIndex,int endIndex)截取指定区间的字符串
public String concat(String str)追加字符串
public String replaceAll(String o,String n)将字符串中所有的 o 替换成 n
public String[] split(String regex)用指定的字符串对目标进行分割,返回数组
public String toLowerCase()转小写
public String toUpperCase()转大写
public char[] toCharArray()将字符串转为字符数组

6、StringBuffer

String对象一旦创建,值不能修改(原来的值不能修改,一旦修改后就是一个新的对象,只要一改动,将会创建一个新的对象)

修改之后会重新开启内存空间来存储新的对象。

String的值为什么不能修改?修改之后会创建一个新的对象?而不是在原有对象的基础上进行修改?

  • 因为String底层是用数组来存值的,数组长度一旦创建就不可修改,所以导致上述问题

StringBuffer 可以解决String频繁修改造成的空间资源浪费的问题。

StringBuffer底层也是使用数组来存值

  • StringBuffer数组默认长度为为16,使用无参构造来创建对象
public StringBuffer() {
        super(16);
    }
  • 使用有参构造创建对象,数组长度=值的长度+16
public StringBuffer(int capacity) {
        super(capacity);
    }
public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }
public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

append,追加和拼接

public class Random01 {
    public static void main(String[] args) {
        StringBuffer stringBuffer=new StringBuffer();
        StringBuffer stringBuffer1=new StringBuffer("String");
        /*stringBuffer 底层数组长度16
        * stringBuffer1 底层数组长度21
        * */
        stringBuffer.append("String");
        System.out.println(stringBuffer1.equals(stringBuffer));//false
        System.out.println(stringBuffer1.toString().equals(stringBuffer.toString()));//true
          System.out.println(stringBuffer.length());//6
        System.out.println(stringBuffer1.length());//6
    }
}
  • .length() 方法返回的并不是底层数组的长度,而是它的有效长度(值的长度)

思考???StringBuffer一旦创建,默认会有16个字节的空间去修改,但是一旦追加的字符串长度超过16,如何处理??

StringBuffer不会重新开辟新的空间,而是在原有基础上进行扩容,通过调用父类ensureCapacityInternal()方法对底层数组进行扩容,保持引用不变

stringBuffer.ensureCapacity(1);

StringBuffer 的常用方法,StringBuffer 是线程安全的,但是效率较低,StringBuilder 是线程不安全的,但是效率较高。

HashMap:线程不安全,效率高

Hashtable:线程安全,效率低

方法描述
public StringBuffer()创建一个空的 StringBuffer对象
public StringBuffer(String str)创建一个值为 str 的 StringBuffer 对象
public synchronized int length()返回 StringBuffer 的长度
public synchronized char charAt(int index)返回指定位置的字符
public synchronized StringBuffer append(String str)追加内容
public synchronized StringBuffer delete(int start,int end)删除指定区间的值
public synchronized StringBuffer deleteCharAt(int index)删除指定位置的字符
public synchronized StringBuffer replace(int start,int end,String str)将指定区间的值替换成 str
public synchronized String substring(int start)截取字符串从指定位置到结尾
public synchronized String substring(int start,int end)截取字符串从start开始,到end结束
public synchronized StringBuffer insert(int offset,String str)在指定位置插入 str
public int indexOf(String str)从头开始查找指定字符的位置
public int indexOf(String str,int fromIndex)从fromIndex开始查找指定字符的位置
public synchronized StringBuffer reverse()进行反转
public synchronized String toString()转为 String

读取数据不需要考虑线程安全问题,因为这种操作不存在安全隐患。

日期类

  • java.util.Date

Date表示当前系统时间

public class Data01 {
    public static void main(String[] args) {
        Date date = new Date();//Wed Aug 25 15:32:56 CST 2021
        /*
        * h是12小时制hh:mm:ss
        * H是24小时制HH:mm:ss
        * */
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String str = simpleDateFormat.format(date);//2021-08-25 03:35:34
    }
}
  • java.util.Calendar

Calendar 用来完成日期数据的逻辑运算

运算思路:

1、将日期数据传给Calendar(Calendar提供了很多静态常量,专门用来记录日期数据)

常量描述
public static final int YEAR
public static final int MONTH
public static final int DAY_OF_MONTH天,以月为单位
public static final int DAY_OF_YEAR天,以年为单位
public static final int HOUR_OF_DAY小时
public static final int MINUTE分钟
public static final int SECOND
public static final int MILLSECOND毫秒

2、调用相关方法进行运算

方法描述
public static Calendar getInstance()获取Calendar实例化对象
public void set(int field,int value)给静态常量赋值
public int get(int field)获取静态常量的值
public final Date getTime()将Calendar转为Date对象
public class Calendar01 {
    public static void main(String[] args) {
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
        //计算今天所在周是2021的第几周
        Calendar calendar=Calendar.getInstance();
        System.out.println(format.format(calendar.getTime()));//2021-08-25当前时间
        calendar.set(Calendar.YEAR,2021);
        calendar.set(Calendar.MONTH,7);//1月为零,八月为七
        calendar.set(Calendar.DAY_OF_MONTH,24);
        int week = calendar.get(Calendar.WEEK_OF_YEAR);
        System.out.println(week);//35
        //计算今天之后的63天是几月几号
        int day=calendar.get(Calendar.DAY_OF_YEAR);
        System.out.println(day);//今天是几年的第一天 236
        day+=63;
        calendar.set(Calendar.DAY_OF_YEAR,day);
        Date date=calendar.getTime();
        System.out.println(format.format(date));//2021-10-26
        //计算今天之前的63天是几月几号
        calendar.set(Calendar.DAY_OF_YEAR,calendar.get(Calendar.DAY_OF_YEAR)-63);
        date=calendar.getTime();
        System.out.println(format.format(date));//2021-08-24
    }
}

IO流

1、File类

  • java.io.File 使用该类的构造函数就可以创建构造函数,将硬盘中的一个具体文件以java对象的形式来表示
方法描述
public File(String pathname)根据路径创建对象
public String getName()获取文件名
public String getParent()获取文件所在的目录
public File getParentFile()获取文件所在目录对应的File对象
public String getPath()获取文件路径
public boolean exists()判断文件是否存在
public boolean isDirectory()判断对象是否为目录
public boolean isFile()判断对象是否为文件
public long length()获取文件的大小
public boolean createNewFile()根据当前对象创建新文件
public boolean delete()删除对象
public boolean mkdir()根据当前对象创建目录
public boolean renameTo(File file)为已存在的对象重命名
  • 按照方向分,可以分为输入流和输出流
  • 按照单位分,可以分为字节流和字符流
  • 按照功能分,可以分为节点流和处理流

2、字节流

按照方向可以分为输入字节流和输出字节流

InputSteam,OutputSteam

1 byte = 8 位二进制数 01010101

InputStream常用方法

方法描述
int read()以字节为单位读取数据
int read(byte b[])将数据存入 byte 类型的数组中,返回数组中有效数据的长度
int read(byte b[],int off,int len)将数据存入 byte 数组的指定区间内,返回数组长度
byte[] readAllBytes()将所有数据存入 byte 数组并返回
int available()返回当前数据流未读取的数据个数
void close()关闭数据流

OutputStream

方法描述
void write(int b)以字节为单位输出数据
void write(byte b[])将byte数组中的数据输出
void write(byte b[],int off,int len)将byte数组中指定区间的数据输出
void close()关闭数据流
void flush()将缓冲流中的数据同步到输出流中
//计算今天之前的63天是几月几号
    calendar.set(Calendar.DAY_OF_YEAR,calendar.get(Calendar.DAY_OF_YEAR)-63);
    date=calendar.getTime();
    System.out.println(format.format(date));//2021-08-24
}

}




# IO流

##  1、File类

- [ ] java.io.File 使用该类的构造函数就可以创建构造函数,将硬盘中的一个具体文件以java对象的形式来表示

| 方法                               | 描述                           |
| ---------------------------------- | ------------------------------ |
| public File(String pathname)       | 根据路径创建对象               |
| public String getName()            | 获取文件名                     |
| public String getParent()          | 获取文件所在的目录             |
| public File getParentFile()        | 获取文件所在目录对应的File对象 |
| public String getPath()            | 获取文件路径                   |
| public boolean exists()            | 判断文件是否存在               |
| public boolean isDirectory()       | 判断对象是否为目录             |
| public boolean isFile()            | 判断对象是否为文件             |
| public long length()               | 获取文件的大小                 |
| public boolean createNewFile()     | 根据当前对象创建新文件         |
| public boolean delete()            | 删除对象                       |
| public boolean mkdir()             | 根据当前对象创建目录           |
| public boolean renameTo(File file) | 为已存在的对象重命名           |

- 按照方向分,可以分为输入流和输出流
- 按照单位分,可以分为字节流和字符流
- 按照功能分,可以分为节点流和处理流

## 2、字节流

按照方向可以分为输入字节流和输出字节流

InputSteam,OutputSteam

1 byte = 8 位二进制数 01010101

InputStream常用方法

| 方法                               | 描述                                                   |
| ---------------------------------- | ------------------------------------------------------ |
| int read()                         | 以字节为单位读取数据                                   |
| int read(byte b[])                 | 将数据存入 byte 类型的数组中,返回数组中有效数据的长度 |
| int read(byte b[],int off,int len) | 将数据存入 byte 数组的指定区间内,返回数组长度         |
| byte[] readAllBytes()              | 将所有数据存入 byte 数组并返回                         |
| int available()                    | 返回当前数据流未读取的数据个数                         |
| void close()                       | 关闭数据流                                             |

OutputStream

| 方法                                 | 描述                           |
| ------------------------------------ | ------------------------------ |
| void write(int b)                    | 以字节为单位输出数据           |
| void write(byte b[])                 | 将byte数组中的数据输出         |
| void write(byte b[],int off,int len) | 将byte数组中指定区间的数据输出 |
| void close()                         | 关闭数据流                     |
| void flush()                         | 将缓冲流中的数据同步到输出流中 |

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值