线程+IO流专题

多线程并发编程

1、什么是进程?什么是线程?什么是并发?

程序是静止的,运行中的程序就是进程

线程属于进程,一个进程可以包括多个线程,这就是多线程

  •  线程是属于进程的。一个进程可以包含多个线程,这就是多线程。
  •  线程是进程中的一个独立执行单元。
  •  线程创建开销相对于进程来说比较小。
  •  线程也支持“并发性”。

并发:同一时刻有多个程序进行

并发就是随机性,均有可能!!!

线程作用

  • 可以提高程序的效率,线程也支持并发性,可以有更多机会得到CPU。
  • 多线程可以解决很多业务模型。
  • 大型高并发技术的核心技术。

2、进程的三个特征

  • 独立性:进程之间是相互独立的,彼此有自己独立的内存区域
  • 动态性:进程是运行中的程序,要动态占用该内存、CPU和网路等资源
  • 并发性:CPU会分时轮询切换一次为每个进程服务,因为切换的速度非常快,给我们的感觉是在同时进行,这就是并发性

3、线程的创建方式

多线程是很有用的,我们在进程中创建线程的方式有三种:

继承Thread类、实现Runnable接口、实现Callable接口

  • 继承Thread类

       继承Thread类的方式:

  1. 定义一个线程类继承Thread类
  2. 重写run()方法
  3. 创建一个新的线程对象
Thread t = new MyThread();
  1. 调用线程对象的start()方法启动线程

线程启动注意事项:

线程的启动必须调用start()方法。否则当成普通类处理。
        -- 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!
        -- start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
 建议线程先创建子线程,主线程的任务放在之后。否则主线程永远是先执行完!

public class ThreadDemo {
    // 启动后的ThreadDemo当成一个进程。
    // main方法是由主线程执行的,理解成main方法就是一个主线程
    public static void main(String[] args) {
        // 3.创建一个线程对象
        Thread t = new MyThread();
        // 4.调用线程对象的start()方法启动线程,但本质最终还是执行run()方法!但是调用run()会被当做普通方法
        t.start();
        
        
        for(int i = 0 ; i < 100 ; i++ ){
            System.out.println("main线程输出:"+i);
        }
    }
}

// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
     //MyThread并不是线程,他是线程类
    // 2.重写run()方法,来自于Thread类
    @Override
    public void run() {
        // 线程的执行方法。
        for(int i = 0 ; i < 100 ; i++ ){
            System.out.println("子线程输出:"+i);
        }
    }
}
  • 实现Runnable接口
  1. 创建一个线程任务类实现Runnable接口
  2. 重写run()方法
  3. 创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
Runnable target = new MyRunnable();

      4.把线程任务对象包装成线程对象,且可以指定线程名称

// Thread t = new Thread(target);
Thread t = new Thread(target,"1号线程");

      5. 用线程对象的start()方法启动线程

public class ThreadDemo {
    public static void main(String[] args) {
        // 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
        Runnable target = new MyRunnable();
        // 4.把线程任务对象包装成线程对象.且可以指定线程名称
        // Thread t = new Thread(target);
        Thread t = new Thread(target,"1号线程");
        // 5.调用线程对象的start()方法启动线程
        t.start();

        Thread t2 = new Thread(target);
        // 调用线程对象的start()方法启动线程
        t2.start();

        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

// 1.创建一个线程任务类实现Runnable接口。
class MyRunnable implements Runnable{
    // 2.重写run()方法
    @Override
    public void run() {
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

 Thread的构造器

  1. public Thread(){}
  2. public Thread(String name){}
  3. public Thread(Runnable target){}:分配一个新的Thread对象
  4. public Thread(Runnable target,String name):分配一个新的Thread对象,且可以指定新的线程名称
  • 实现Callable接口
  1. 定义一个线程任务类实现Callable接口,申明线程返回的结果类型
  2. 重写线程任务类的call方法,这个方法可以直接返回执行的结果
  3. 创建一个Callable的线程任务对象
  4. 把Callable的线程任务对象包装成一个未来任务对象
  5. 把未来任务对象包装成线程对象
  6. 调用线程的start()方法启动线程
public class ThreadDemo {
    public static void main(String[] args) {
        // 3.创建一个Callable的线程任务对象
        Callable call = new MyCallable();
        // 4.把Callable任务对象包装成一个未来任务对象
        //      -- public FutureTask(Callable<V> callable)
        // 未来任务对象是啥,有啥用?
        //      -- 未来任务对象其实就是一个Runnable对象:这样就可以被包装成线程对象!
        //      -- 未来任务对象可以在线程执行完毕之后去得到线程执行的结果。
        FutureTask<String> task = new FutureTask<>(call);
        // 5.把未来任务对象包装成线程对象
        Thread t = new Thread(task);
        // 6.启动线程对象
        t.start();

        for(int i = 1 ; i <= 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => " + i);
        }

        // 在最后去获取线程执行的结果,如果线程没有结果,让出CPU等线程执行完再来取结果
        try {
            String rs = task.get(); // 获取call方法返回的结果(正常/异常结果)
            System.out.println(rs);
        }  catch (Exception e) {
            e.printStackTrace();
        }

    }
}

// 1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型
class MyCallable implements Callable<String>{
    // 2.重写线程任务类的call方法!
    @Override
    public String call() throws Exception {
        // 需求:计算1-10的和返回
        int sum = 0 ;
        for(int i = 1 ; i <= 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => " + i);
            sum+=i;
        }
        return Thread.currentThread().getName()+"执行的结果是:"+sum;
    }
}

小结

如何创建线程?哪种好?
  有 4 种方式可以用来创建线程 :
1. 继承 Thread 类
2. 实现 Runnable 接口
3. 应用程序可以使用 Executor 框架来创建线程池
4. 实现 Callable 接口
实现 Runnable 接口比继承 Thread 类所具有的优势 :
1 ):适合多个相同的程序代码的线程去处理同一个资源
2 ):可以避免 java 中的单继承的限制
3 ):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4 ):线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承 Thread 的类
5 ): runnable 实现线程可以对线 程进行复用,因为 runnable 是轻量级的对象,重复 new 不会耗 费太大资源,而 Thread 则不然,它是重量级对象,而且线程执行完就完了,无法再次利用

4、线程API

Thread 类的 API

public void setName(String name): 给当前线程取名字
public void getName(): 获取当前线程的名字
线程存在默认名称,子线程的默认名称是:Thread - 索引
主线程的默认名称是:main
public static Thread currentThread(): 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象
public static void sleep(long time):让当前线程休眠多少毫秒再继续执行
public Thread(String name):创建对象并取名字

public class ThreadDemo {
    // 启动后的ThreadDemo当成一个进程。
    // main方法是由主线程执行的,理解成main方法就是一个主线程
    public static void main(String[] args) {
        // 创建一个线程对象
        Thread t1 = new MyThread();
        t1.setName("1号线程");
        t1.start();
        //System.out.println(t1.getName()); // 获取线程名称

        Thread t2 = new MyThread();
        t2.setName("2号线程");
        t2.start();
        //System.out.println(t2.getName());  // 获取线程名称

        // 主线程的名称如何获取呢?
        // 这个代码在哪个线程中,就得到哪个线程对象。
        Thread m = Thread.currentThread();
        m.setName("最强线程main");
        //System.out.println(m.getName()); // 获取线程名称

        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(m.getName()+"==>"+i);
        }
    }
}

// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
    // 2.重写run()方法
    @Override
    public void run() {
        // 线程的执行方法。
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

线程休眠api

public class ThreadDemo02 {
    public static void main(String[] args) {
        for(int i = 0 ; i < 10 ; i++ ) {
            System.out.println(i);
            try {
                // 项目经理让我加上这行代码
                // 如果用户交钱了,我就去掉。
                Thread.sleep(1000); // 让当前线程休眠1s.
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

通过Thread类的有参构造器为当前线程对象取名字 

public class ThreadDemo03 {
    // 启动这个类,这个类就是进程,它自带一个主线程,
    // 是main方法,main就是一个主线程的执行!!
    public static void main(String[] args) {
        Thread t1 = new MyThread02("1号线程");
        t1.start();

        Thread t2 = new MyThread02("2号线程");
        t2.start();

        Thread.currentThread().setName("主线程");
        for(int i = 0 ; i < 10 ; i++ ) {
            System.out.println(Thread.currentThread().getName()+" => "+i);
        }
    }
}

// 1.定义一个线程类继承Thread。线程类并不是线程对象,用来创建线程对象的。
class MyThread02 extends Thread{

    public MyThread02(String name) {
        //  public Thread(String name):父类的有参数构造器
        super(name); // 调用父类的有参数构造器初始化当前线程对象的名称!
    }

    // 2.重写run()方法
    @Override
    public void run() {
        for(int i = 0 ; i < 10 ; i++ ) {
            System.out.println(Thread.currentThread().getName()+" => "+i);
        }
    }
}

5、线程安全


线程安全问题:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题

线程同步_同步代码块

  • 线程同步的作用:就是为了解决线程安全问题,让多个线程实现先后依次访问共享资源,这样就解决了安全问题
  • 线程安全:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题
  • 线程同步的做法:加锁(就是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来)

线程同步的方法

  • 同步代码块
  • 同步方法
  • lock 显示锁

同步代码块作用:是把出现线程安全问题的核心代码给上锁,每次只能一个线程进入,执行完毕之后自动解锁,其他线程才可以进来执行

// 格式
synchronized(锁对象){
    // 访问共享资源的核心代码    
}
  • 在实例方法中建议用this作为锁对象
  • 在静态方法中建议用类名.class字节码作为锁对象

线程同步_同步方法
作用:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待

用法:直接给方法加上一个修饰符 synchronized

public synchronized void 方法名(){
    
}

线程同步_lock显示锁
Lock锁也称同步锁,加锁与释放锁方法化了,如下

  • public void lock(): 加同步锁
  • public void unlock(): 释放同步锁
// 创建一把锁对象
private final Lock lock = new ReentrantLock();
// 上锁
lock.lock();
// 解锁
lock.unlock();

优点:线程安全,但是性能差

假如开发中不会存在多线程安全问题,建议使用线程不安全的设计类

线程通信
线程通信一定是多个线程在操作同一个资源才需要通信

线程通信方法

  • public void wait(): 让当前线程进入到等待状态,此方法必须锁对象调用
  • public void notify():唤醒当前锁对象上等待状态的某个线程,此方法必须锁对象调用
  • public void notifyAll():唤醒当前锁对象上等待状态的全部线程,此方法必须锁对象调用

6、线程池
什么是线程池?

答:线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源

为什么要用线程池?

答:

  • 降低资源消耗,减少了创建和销毁线程的次数
  • 提高响应速度,不需要频繁的创建线程
  • 提高线程的可管理性线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机,使得线程池可以进行统一的调优和分配,方便监控,这样避免无限制的创建,可能会消耗系统资源和降低稳定性的缺点)

怎么实现?

线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
 
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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

重要参数如下:

  1. corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  2. maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  3. keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  4. unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  5. workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  6. threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  7. handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                             MAXIMUM_POOL_SIZE,
                                             KEEP_ALIVE,
                                             TimeUnit.SECONDS,
                                             sPoolWorkQueue,
                                             sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        ... // 线程执行的任务
    }
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

线程池工作原理剖析

7、sleep wait yield notify notifyAll join详解


一.Sleep 与 wait 区别
1. sleep 是线程类( Thread )的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。 sleep() 使当前线程进入阻塞状态,在指定时间内不会执行。
2. wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll )后本线程才进入对象锁定池准备获得对象锁进入运行状态。
区别比较 :
1 、这两个方法来自不同的类分别是 Thread 和 Object
2 、最主要是 sleep 方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3 、 wait , notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可 以在任何地方使用( 使用范围 )
4 、 sleep 必须捕获异常,而 wait , notify 和 notifyAll 不需要捕获异常
(1)sleep 方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程 的运行也需要间,一个线程对象调用了 sleep 方法之后,并不会释放他所持有的所有对象 锁,所以也就不会影响其他进程对象的运行。但在 sleep 的过程中过程中有可能被其他对象 调用它的 interrupt(), 产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程 就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继 续执行 catch 语句块 ( 可能还有 finally 语句块 ) 以及以后的代码。
        注意 sleep() 方法是一个静态方法,也就是说他只对当前对象有效,通过 t.sleep() 让 t 对象进入 sleep ,这样的做法是错误的,它只会是使当前线程被 sleep 而不是 t 线程
(2)wait 属于 Object 的成员方法,一旦一个对象调用了 wait 方法,必须要采用 notify() notifyAll() 方法唤醒该进程 ; 如果线程拥有某个或某些对象的同步锁,那么在调用了 wait() 后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了 wait() 方法的对象。wait()方法也同样会在 wait 的过程中有可能被其他对象调用 interrupt() 方法而产生
二 yield join notify notifyAll
        yield() 方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。 如果没有的话,那么 yield() 方法将不会起作用,并且由可执行状态后马上又被执行。
         join 方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执 行结束后,再继续执行当前线程。如:t.join(); // 主要用于等待 t 线程运行结束,若无此句, main 则会执行完毕,导致结果不可预测。
        notify 方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程 等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管 理的实现。
        notifyAll 会唤醒所有等待 ( 对象的 ) 线程,尽管哪一个线程将会第一个处理取决于操作系 统的实现。

IO流

1、什么是IO流?

IO输入输出流:输入/输出流。
        Input:输入。
        Output:输出。

    引入:
        File类只能操作文件对象本身,不能读写文件对象的内容。
        读写数据内容,应该使用IO流。

    IO流是一个水流模型:IO理解成水管,把数据理解成水流

   2、 IO流的分类
        按照流的方向分为:输入流,输出流。
           (1)输出流:以内存为基准,把内存中的数据写出到磁盘文件或者网络介质中去的流称为输出流。
                   输出流的作用:写数据到文件,或者写数据发送给别人。

           (2)输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据读入到内存中去的流称为输入流。
                   输入流的作用:读取数据到内存。

        按照流的内容分为: 字节流,字符流。
           (1)字节流:流中的数据的最小单位是一个一个的字节,这个流就是字节流。
           (2)字符流:流中的数据的最小单位是一个一个的字符,这个流就是字符流。(针对于文本内容)


    所以流大体分为四大类:
        字节输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据以一个一个的字节的形式读入到内存中去的流称为字节输入流。
        字节输出流:以内存为基准,把内存中的数据以一个一个的字节写出到磁盘文件或者网络介质中去的流称为字节输出流。
        字符输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据以一个一个的字符的形式读入到内存中去的流称为字符输入流。
        字符输出流:以内存为基准,把内存中的数据以一个一个的字符写出到磁盘文件或者网络介质中去的流称为字符输出流。


    小结:
        IO流是读写传输数据的,IO流有很多种,每种流有自己的功能特点。

字节输入流的使用 - 按照一个字节读取

public class FileInputStreamDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.创建文件对象定位dlei01.txt
        File file = new File("Day09Demo/src/dlei01.txt");
        // 2.创建一个字节输入流管道与源文件接通
        InputStream is = new FileInputStream(file);
        // 3.读取一个字节的编号返回,读取完毕返回-1
//        int code1 = is.read(); // 读取一滴水,一个字节
//        System.out.println((char)code1);
//
//        int code2 = is.read(); // 读取一滴水,一个字节
//        System.out.println((char)code2);
//
//        int code3 = is.read(); // 读取一滴水,一个字节
//        System.out.println((char)code3);
//
//        int code4 = is.read(); // 读取一滴水,一个字节 ,读取没有字节返回-1
//        System.out.println(code4);

        // 4.使用while读取字节数
        // 定义一个整数变量存储字节

//一滴一滴读不靠谱,要是引入了字符,比如我abc,会造成乱码,所以采取循环
        int ch = 0 ;
        while((ch = is.read())!= -1){
            System.out.print((char) ch);
        }

    }
}

字节输入流的使用-按照字节数组读取

public class FileInputStreamDemo02 {
    public static void main(String[] args) throws Exception {
        // 需求:读取文件中的数据输出。
        // 1.创建一个文件对象
        //File srcFile = new File("Day09Demo/src/dlei02.txt");
        // 2.创建一个字节输入流管道与源文件对象接通。
        //InputStream is = new FileInputStream(srcFile);

        // 3.简化写法:直接创建一个字节输入流管道与源文件路径接通。
        InputStream is = new FileInputStream("Day09Demo/src/dlei02.txt");

//        // 4.定义一个字节数组读取数据(定义一个桶)
//        byte[] buffer = new byte[3];
//        // 从is管道中读取字节装入到字节数组中去,返回读取字节的数量。
//        int len = is.read(buffer);
//        System.out.println("读取了字节数:"+len);
//        String rs = new String(buffer);
//        System.out.println(rs); // abc
//
//        int len1 = is.read(buffer);
//        System.out.println("读取了字节数:"+len1);
//        String rs1 = new String(buffer);
//        System.out.println(rs1); // xyz
//
//        int len2 = is.read(buffer);
//        System.out.println("读取了字节数:"+len2);
//        // 倒出字节数组中的全部字符
//        //String rs2 = new String(buffer);
//        // 读取了多少就倒出多少!
//        String rs2 = new String(buffer, 0 , len2);
//        System.out.println(rs2); // iyz
//
//        int len3 = is.read(buffer);
//        System.out.println("读取了字节数:"+len3); // -1 数据没有了返回-1


        // 读法优化,必须使用循环     // abc xyz i
        // a.定义一个字节数组代表桶   // ooo ooo o
        byte[] buffer = new byte[3];
        int len ; // 存储每次读取的字节数。
        while((len = is.read(buffer)) != -1){
            // 读取了多少就倒出多少!
            String rs = new String(buffer , 0 , len);
            System.out.print(rs);
        }

    }
}

FileInputStream文件字节输入流
        -- 作用:以内存为基准,把磁盘文件中的数据按照字节的形式读入到内存中的流。
                简单来说,就是按照字节读取文件数据到内存。
        -- 构造器:
           1.public FileInputStream(File path):创建一个字节输入流管道与源文件对象接通。
           2.public FileInputStream(String pathName):创建一个字节输入流管道与文件路径对接。
        -- 方法:
           1.public int read():每次读取一个字节返回!读取完毕会返回-1。
           2.public int read(byte[] buffer):从字节输入流中读取字节到字节数组中去,
                返回读取的字节数量,没有字节可读返回-1。
        小结

  •           public int read(byte[] buffer):从字节输入流中读取字节到字节数组中去,
  •                 返回读取的字节数量,没有字节可读返回-1。
  •            使用字节数组读取内容,效率可以。
  •            但是使用字节数组读取文本内容输出,也无法避免中文读取输出乱码的问题。

   解决字节输入流读取中文内容输出乱码的问题

    引入
        一个一个字节读取中文输出
        一个一个字节数组读取中文输出均无法避免乱码。
    如何实现读取可以避免乱码呢?
        1.定义一个字节数组与文件的大小刚刚一样大,然后一桶水读取全部字节数据再输出!
    小结

  •         定义一个字节数组与文件的大小刚刚一样大,然后一桶水读取全部字节数据再输出!
  •         可以避免中文读取输出乱码,但是如果读取的文件过大,会出现内存溢出!!
  •         字节流并不适合读取文本文件内容输出,读写文件内容建议使用字符流。
public class FileInputStreamDemo03 {
    public static void main(String[] args) throws Exception {
        // 0.定位文件对象
        File f = new File("Day09Demo/src/dlei03.txt");
        // 1.定义一个字节输入流通向源文件路径,简化写法!
        InputStream is = new FileInputStream(f);

        // 2.定义一个字节数组与文件的大小刚刚一样大
//        System.out.println("文件大小:"+f.length());
//        byte[] buffer = new byte[(int) f.length()];
        //这里你直接输入byte[] buffer = new byte[ f.length()];报错,要强转,因为数组对应length,这里输出int
//        int len = is.read(buffer);
//        System.out.println("读取了:"+len);
//        String rs = new String(buffer);
//        System.out.println(rs);

      /* byte[] buffer = is.readAllBytes();//这里正常,jdk9以上才支持,jdk
      8报错
       String rs = new String(buffer);
       System.out.println(rs);*/

    }
}

 字节输出流的使用

 a.FileOutputStream文件字节输出流
        -- 作用:以内存为基准,把内存中的数据,按照字节的形式写出到磁盘文件中去。
                 简单来说,把内存数据按照字节写出到磁盘文件中去。
        -- 构造器:
            public FileOutputStream(File file):创建一个字节输出流管道通向目标文件对象。
            public FileOutputStream(String file):创建一个字节输出流管道通向目标文件路径。
            public FileOutputStream(File file , boolean append):创建一个追加数据的字节输出流管道通向目标文件对象。
            public FileOutputStream(String file , boolean append):创建一个追加数据的字节输出流管道通向目标文件路径。
     

       -- 方法:
           public void write(int a):写一个字节出去 。
           public void write(byte[] buffer):写一个字节数组出去。
           public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。
                        参数一,字节数组;参数二:起始字节索引位置,参数三:写多少个字节数出去。
    小结

  •         字节输出流只能写字节出去。
  •         字节输出流默认是覆盖数据管道。
  •         换行用: os.write("\r\n".getBytes());
  •         关闭和刷新:刷新流可以继续使用,关闭包含刷新数据但是流就不能使用了!
public class OutputStreamDemo04 {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个文件对象定位目标文件(写数据到文件,文件会自动创建)
        // File file = new File("Day09Demo/src/dlei04.txt");
        // 2. 创建一个字节输出流管道与目标文件对象接通
        // OutputStream os = new FileOutputStream(file);
        // 3.简化写法:创建一个字节输出流管道与目标文件路径接通
        // 追加数据管道,第二个参数是true,这样在写入字节进去就不会清空管道
        // OutputStream os = new FileOutputStream("Day09Demo/src/dlei04.txt",true);
        OutputStream os = new FileOutputStream("Day09Demo/src/dlei04.txt");
        // 4. 写数据出去
        // a.写一个字节出去(写一滴水出去)
        os.write(97); // 字节a
        os.write('b'); // 字节b
        // os.write('磊'); // [ooo] 只会写出中文的第一个字节,写出去就乱码!

        os.write("\r\n".getBytes()); // 换行

        // b.写一个字节数组出去(写一个桶出去)
        byte[] bytes = new byte[]{99,100,101,104};
        os.write(bytes);
        byte[] bytes1 = "Java语言很优美".getBytes(); // 默认以当前代码编码UTF-8提取字节数组
        // byte[] bytes1 = "Java语言很优美".getBytes("GBK"); // 指定编码获取字节数组
        os.write(bytes1);

        // c. 写一个字节数组的一部分出去
        byte[] bytes2 = "Java是很优美的语言".getBytes();
        os.write(bytes2,0,19);

        os.write("\r\n".getBytes()); // 换行
        

        // os.flush(); // 立即刷新数据到文件中去,刷新后管道可以继续使用
        os.close(); // 关闭资源管道,关闭包含了刷新,关闭后管道不能使用

    }


}

字符流一个一个字符的读取文本内容输入

public class FileReaderDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.创建一个文件对象定位源文件
        // File f = new File("Day10Demo/src/dlei01.txt");
        // 2.创建一个字符输入流管道与源文件接通
        // Reader fr = new FileReader(f);
        // 3.简化写法:创建一个字符输入流管道与源文件路径接通
        Reader fr = new FileReader("Day10Demo/src/dlei01.txt");
        // 4.按照字符读取,每次读取一个字符的编号返回。
//        int code1 = fr.read();
//        System.out.print((char)code1);
//        int code2 = fr.read();
//        System.out.print((char)code2);
//        int code3 = fr.read();
//        System.out.print((char)code3);
//        int code4 = fr.read(); // 读取完毕返回-1
//        System.out.print(code4);

        // 5.while循环一个一个字符读取。
        // 定义一个变量存储一个字符的编号
        int ch ;
        while ((ch = fr.read()) != -1){
            System.out.print((char)ch);
        }
    }

}
  • 字符流一个一个字符的读取文本内容输入,可以解决中文读取输出乱码的问题
  • 字符流很适合操作文本文件内容
  • 但是一个一个字符读取文本内容性能较差

字符流按照字符数组循环读取数据

public class FileReaderDemo02 {
    public static void main(String[] args) throws Exception {
        // 1.创建一个字符输入流管道与源文件接通
        Reader fr = new FileReader("Day10Demo/src/dlei02.txt");
        // 2.按照字符数组读取内容
        
          // a.按照字符数组读取数据使用循环
          char[] buffer = new char[1024]; // 1K
          // b.定义一个整数记录每次桶读取的字符数据量。
          int len;
          while((len = fr.read(buffer)) != -1 ) {
              // 读取多少倒出多少字符
              System.out.print(new String(buffer, 0 , len));
          }
    }

}

FileWriter文件字符输出流作用:把内存的数据以字符的形式写出到文件中去

构造器:

public FileWriter(File file): 创建一个字符输出流管道通向目标文件对象
public FileWriter(String filePath):创建一个字符输出流管道通向目标文件路径
public FileWriter(File file,boolean append):创建一个追加数据的字符输出流管道通向目标文件对象
public FileWriter(String filePath,boolean append):创建一个追加数据的字符输出流管道通向目标文件路径
方法:

public void write(int c):写一个字符出去
public void write(String c):写一个字符串出去
public void write(String c,int pos,int len):写字符串的一部分出去
public void write(char[] buffer,int pos,int len):写字符数组的一部分出去

public class FileWriterDemo03 {
    public static void main(String[] args) throws Exception {
        // 1.创建一个字符输出流管道通向目标文件路径
        //Writer fw = new FileWriter("Day10Demo/src/dlei03.txt"); // 覆盖数据管道
        Writer fw = new FileWriter("Day10Demo/src/dlei03.txt",true); // 追加数据管道

        // 2.写一个字符出去:public void write(int c):写一个字符出去
        fw.write(97);   // 字符a
        fw.write('b');  // 字符b
        fw.write('磊'); // 字符磊,此时没有任何问题。
        fw.write("\r\n"); // 换行

        // 3.写一个字符串出去:public void write(String c)写一个字符串出去:
        fw.write("Java是最优美的语言!");
        fw.write("我们在黑马学习它!");
        fw.write("\r\n"); // 换行

        // 4.写一个字符数组出去:public void write(char[] buffer):写一个字符数组出去
        fw.write("我爱中国".toCharArray());
        fw.write("\r\n"); // 换行

        // 5.写字符串的一部分出去: public void write(String c ,int pos ,int len):写字符串的一部分出去
        fw.write("Java是最优美的语言!",0,9);
        fw.write("\r\n"); // 换行

        // 6.写字符数组的一部分出去:public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
        fw.write("我爱中国".toCharArray(),0 ,2);
        fw.write("\r\n"); // 换行

        fw.close();
    }
}

缓冲流

什么是缓冲流?

答:缓冲流可以提高字节流和字符流的读写数据的性能

字节缓冲输入流:BufferedInputStream

作用:可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能

构造器:

public BufferedInputStread(InputStream in)
原理:

缓冲字节输入流管道自带了一个8KB的缓冲池,每次可以直接借用操作系统的功能最多提取8KB的数据到缓冲池中去,以后我们直接从缓冲池读取数据,所以性能较好

public class BufferedInputStreamDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.定义一个低级的字节输入流与源文件接通
        InputStream is = new FileInputStream("Day10Demo/src/dlei04.txt");

        // 3.把低级的字节输入流包装成一个高级的缓冲字节输入流。
        BufferedInputStream bis = new BufferedInputStream(is);

        // 2.定义一个字节数组按照循环读取。
        byte[] buffer = new byte[3];
        int len ;
        while((len = is.read(buffer)) != -1){
            String rs = new String(buffer, 0 , len);
            System.out.print(rs);
        }
    }
}

字节缓冲输出流BufferedOutputStream

字节缓冲输出流作用:可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高读写数据的性能

构造器:

public BufferedOutputStream(OutputStream os)
原理:

缓冲字节输出流自带了8KB缓冲池,数据就直接写入到缓冲池中去,性能极高

public class BufferedOutputStreamDemo02 {
    public static void main(String[] args) throws Exception {
        // 1.写一个原始的字节输出流
        OutputStream os = new FileOutputStream("Day10Demo/src/dlei05.txt");
        // 3.把低级的字节输出流包装成一个高级的缓冲字节输出流
        BufferedOutputStream bos =  new BufferedOutputStream(os);
        // 2.写数据出去
        bos.write('a');
        bos.write(100);
        bos.write('b');
        bos.write("我爱中国".getBytes());
        bos.close();
    }
}

字符缓冲输入流BufferedReader

字符缓冲输入流作用:字符缓冲输入流可以把字符输入流包装成一个高级的缓冲字符输入流,可以提高字符输入流读数据的能力

构造器:

public BufferedReader(Reader reader)
原理:

缓冲字符输入流默认会有一个8K的字符缓冲池,可以提高读字符的性能

除了提高字符输入流的读数据性能,缓冲字符输入流还多了一个按照行读取数据的功能

public String readLine(): 读取一行数据返回,读取完毕返回null

public class BufferedReaderDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.定义一个原始的字符输入流读取源文件
        Reader fr = new FileReader("Day10Demo/src/dlei06.txt");

        // 3.把低级的字符输入流管道包装成一个高级的缓冲字符输入流管道
        BufferedReader br = new BufferedReader(fr);
        // 定义一个字符串变量存储每行数据
        String line;
        // 使用一个循环读取数据(经典代码)
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        br.close();
    }
}

对象字节输出流ObjectOutputStream

什么是对象序列化

答:对象序列化就是把Java对象数据直接存储到文件中去 (对象 => 文件中)

      对象序列化流(也就是对象字节输出流)

对象字节输出流的作用: 把内存中的Java对象数据保存到文件中去

构造器:

public ObjectOutputStream(OutputStream out)
序列化方法:

public final void writeObject(Object obj)
注意:

如果对象想参与序列化,对象必须实现序列化接口 implements Serializable,否则序列化失败

public class SerializeDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.创建User用户对象
        User user = new User("tsgz","003197","铁扇公主");
        // 2.创建低级的字节输出流通向目标文件
        OutputStream os = new FileOutputStream("Day10Demo/src/obj.dat");
        // 3.把低级的字节输出流包装成高级的对象字节输出流ObjectOutputStream,因为低级的字节输出流没有保存对象的能力
        ObjectOutputStream oos = new ObjectOutputStream(os);
        // 4.通过对象字节输出流序列化对象:
        oos.writeObject(user);
        // 6.释放资源
        oos.close();
        System.out.println("序列化对象成功~~~~");
    }
}

对象字节输入流ObjectInputStream

对象字节输入流作用:把Java对象的文件恢复到Java对象中(文件中 => 对象)

构造器:

public ObjectInputStream(InputStrean is)
方法:

public final Object readObject()
如果一个字段不想参数序列化:

transient修饰该成员变量,它将不参与序列化

public class SerializeDemo02 {
    public static void main(String[] args) throws Exception {
        // 1.定义一个低级的字节输入流通向源文件
        InputStream is = new FileInputStream("Day10Demo/src/obj.dat");
        // 2.把字节输入流包装成高级对象字节输入流
        ObjectInputStream ois = new ObjectInputStream(is);
        // 3.反序列化
        User user = (User) ois.readObject();
        System.out.println(user);
        System.out.println("反序列化完成!");
    }
}


打印流PrintStream、PrintWriter

打印流的作用:

可以方便,快速的写数据出去
可以实现打印啥出去,就是啥出去

构造器:

public PrintStream(OutputStream os)
public PrintStream(String filepath)

public class PrintStreamDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.打印流PrintStream
        //OutputStream os = new FileOutputStream("Day10Demo/src/dlei08.txt");
        //PrintStream ps = new PrintStream(os);
        PrintStream ps = new  PrintStream("Day10Demo/src/dlei08.txt");
        //PrintWriter pw = new  PrintWriter("Day10Demo/src/dlei08.txt");

        ps.println(97); // 写97
        ps.println(110); // 写110
        ps.println("我在学校快乐的调皮~~");
        ps.println(99.8);
        ps.println(false);
        ps.println('徐');

        // 写字节数据出去
        // ps.write("我爱你".getBytes());

        ps.close();

    }
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值