【java】IO详解之BIO

Java BIO - 阻塞式IO详解 文章目录重要概念传统的BIO通信方式传统的BIO的问题多线程方式 - 伪异步方式BIO通信方式深入分析 重要概念 阻塞和同步,非阻塞和异步,这两组词的关联度是很高的,是不能分开讲的,像多线程并发往往也会论述这些概念。下面会从IO的角度切入。 阻塞IO 和 非阻塞IO 这两个概念是程序(进程线程)级别的。主要描述的是程序请求操作系统IO操作后,如果IO资源没有准备好,那么程序该如何处理的问题: 前者等待;后者继续执行(并且使用线程一直轮询,直到有IO资源准备好了) 同步IO 和 非同步IO 这两个 阅读详情

注:本篇所写的io均为java bio体系(即jdk1.0发布的io)

1.IO相关概念一览

1.1 什么是IO

所谓IO即input和output的缩写,是对数据的流入和流出的一种抽象,编程中很常见的一个概念。

1.2 什么是流

体会一下这几个词:水流(静止的水想必没人会叫水流),物流,人流(此人流非彼人流 = =!),可以发现流的特点:动态的,可转移的,从一处到另一处的

1.3 java io

java为了我们调用方便,而屏蔽输入/输出源和流动细节,抽象出的用于解决数据流动问题的类体系,这就是java的io流

1.4 输入流和输出流

用于读取的流称为输入流(输入流只能用来读),用于写入的流称为输出流(输出流只能用来写)。输入输出的概念一般是针对内存来说的,流(写)入内存,从内存流(读)出。

1.5 字节流和字符流

输入输出流可操作最小单位来区分字节流和字符流,最小操作单位是一个字节(8bit)的为字节流,最小操作单位为一个字符(16bit)的为字符流,java io体系中字节操作流以stream结尾,字符操作流以reader和writer结尾

1.6 节点流和包装(处理)流

1)节点流偏向实现细节,直接与细节打交道,比如FileInputStream,而包装(处理)流偏功能,以目标功能为抽象,比如PrintStream。 
2)区分节点流和包装(处理)流最简单的一个方式:处理流的构造方法中需要另一个流作为参数,而节点流构造方法则是具体的物理节点,如上FileInputStream构造法中需要一个文件路径或者File对象,而PrintStream构造方法中则需要一个流对象 
3)包装流使用了装饰器模式(什么是装饰器模式?传送门),包装流对节点流进行了一系列功能的强化包装,让包装后的流拥有了更多的操作手段或更高的操作效率,而隐藏节点流底层的复杂性。

1.7 低级流和高级流

低级流和高级流对应的概念即对应上面的节点流和包装(处理)流概念

1.8 普通流和缓冲流

普通流和缓冲流主要是针对读写性能上提出的相对概念。普通流与缓冲流的区别在于一个一个数据的流动还是一堆一堆数据的流动。

1.9 bio,nio,aio

bio:b有两说,一为base,jdk中最早抽象出的io体系;一为block,jdk 1.0 中的io体系是阻塞的。所以两说皆有道理,一般我们认为b取block之意 
nio:n也有两说,一为new,针对base而言;一为non-block,针对block而言。 
aio:a为asynchronous,异步的,异步io,aio还有个名字叫:nio2

发展历程:bio(jdk1.0) -> nio(jdk1.4) -> aio(jdk1.7)

2.BIO类体系

2.1 体系图

这里写图片描述

3.BIO体系中的类该怎么使用?

要回答这个问题,我们需要将这个问题进行拆分为下面四个问题

3.1 什么时候该用输入流,什么时候用输出流?

从流中读取信息使用输入流(xxxInputStream/xxxReader),写入信息使用输出流(xxxOutputStream/xxxWriter)

3.2 什么时候该用字节流,什么时候用字符流?

处理纯文本数据时使用字符流(xxxReader/xxxWriter),处理非纯文本时使用字节流(xxxStream)。最后其实不管什么类型文件都可以用字节流处理,包括纯文本,但会增加一些额外的工作量。所以还是按原则选择最合适的流来处理

3.3 什么时候该用节点流,什么时候用包装(处理)流?

不管你用什么包装(处理)流,都需要先使用节点流获取对应节点的数据流,然后根据具体需求来选择相应的包装(处理)流来对节点流进行包装修饰,从而获取相应的功能

3.4 什么时候该用普通流,什么时候用缓冲流?

一般如果对数据流不做加工处理,而是单纯的读写,如数据转移(拷贝,上传,下载),则需要使用缓冲流来提高性能,当然你也可以自己使用buff数组来提高读写效率。

3.5 使用小结

1)判断操作的数据类型 
纯文本数据:读用Reader系,写用Writer系 
非纯文本数据:读用InputStream系,写用OutputStream系 
如果纯文本数据只是简单的复制,下载,上传,不对数据内容本身做处理,那么使用Stream系 
2)判断操作的物理节点 
内存:ByteArrayXXX 
硬盘:FileXXX 
网络:http中的request和response均可获取流对象,tcp中socket对象可获取流对象 
键盘(输入设备):System.in 
显示器(输出设备):System.out 
3)搞清读写顺序,一般是先获取输入流,从输入流中读取数据,然后再写到输出流中。 
4)是否需增加特殊功能,如需要用缓冲提高读写效率则使用BufferedXXX,如果需要获取文本行号,则使用LineNumberXXX,如果需要转换流则使用InputStreamReader和OutputStreamWriter,如果需要写入和读取对象则使用ObjectOutputStream和ObjectInputStream

4.使用IO流一些注意点

4.1 关于流的read、write方法的使用

下面列出read和write的方法原型(这里以字节流为例,字符流道理相同):

// stream的read
int read();//返回值代表当前读取的字节(8bit)所对应的整形
int read(byte b[]);//将读入的数据装入缓冲区b,实际装了几个字节?实际装了返回值大小个字节,最常使用的read方法
int read(byte b[], int off, int len);//将读入的数据装入缓冲区b,从哪开始装?从off开始,装几个字节?装len个,实际装了几个?实际装了返回值大小个字节

// stream的write
void write(int b);//一次写入一个字节,写入的内容是整型b所对应的二进制数据写入流中
void write(byte b[]);//一次将b数组中的数据写入流中
write(byte b[], int off, int len);//将b数组中区间为[off,off+len]的数据写入流中,最常使用的write方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果不考虑性能会有如下两种形式来读写:读入一个字节,写入一个字节;使用缓冲区读入一堆字节,然后将缓冲区数据进行写入输出流。如下:

//读一个字节,写一个字节
int b = 0;
while((b = fis.read()) != -1){
    fos.write(b);
}

//读入一堆,写入一堆
int b = 0;
byte[] buff = new byte[size];
while((b = fis.read(buff)) != -1){
    fos.write(buff,0,b);//将缓冲数组索引区间为[0,b]的数据写入
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

上面第一种方式读一个字节写一个字节没什么说的;第二个方式则需要注意,调用write(buff,0,b)而不是write(buff,0,buff.length),为什么写入[0,b]而不是[0,buff.length]区间的数据呢?因为假如只剩余3个字节没有读取,而缓冲数组定义的大小是8个字节,那么使用[0,buff.length]则会造成多写入5个字节的脏数据

4.2 关于流读写性能问题

流的读写是比较耗时的操作,因此为了提高性能,便有缓冲的这个概念(什么是缓冲?假如你是个搬砖工,你工头让你把1000块砖从A点运到B点,你可以一次拿一块砖从A点运到B点放下砖,这样你要来回跑1000次,大多数的时间开销在路上了;你还可以使用一辆小车,在A点装满一车的砖,然后运到B点放下砖,如果一车最多可以装500块,那么你来回两次便可以把这些砖运完。这里的小车便是那个缓冲),在java bio中使用缓冲一般有两种方式。一种是自己申明一个缓冲数组,利用这个数组来提高读写效率;另一种方式是使用jdk提供的处理流BufferedXXX类。下面我们分别演示不使用缓冲读写,使用自定义的缓冲读写,使用BufferedXXX缓冲读写一个文件。

4.21 无缓冲读写文件
    /**
     * 拷贝文件(方法一)
     * @param src 被拷贝的文件
     * @param dest 拷贝到的目的地
     */
    public static void copyByFileStream(File src,File dest){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        long start = System.currentTimeMillis();
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            int b = 0;
            while((b = fis.read()) != -1){//一个字节一个字节的读
                fos.write(b);//一个字节一个字节的写
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            close(fis,fos);
        }
        System.out.println("使用FileOutputStream拷贝大小"+getSize(src)+"的文件未使用缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
4.22 自定义数组做缓冲读写文件
    /**
     * 拷贝文件(方法二)
     * @param src 被拷贝的文件
     * @param dest 拷贝到的目的地
     * @param size 缓冲数组大小
     */
    public static void copyByFileStream(File src,File dest,int size){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        long start = System.currentTimeMillis();
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            int b = 0;
            byte[] buff = new byte[size];//定义一个缓冲数组
            //读取一定量的数据(read返回值表示这次读了多少个数据)放入数组中
            while((b = fis.read(buff)) != -1){
                fos.write(buff,0,b);//一次将读入到数组中的有效数据(索引[0,b]范围的数据)写入输出流中
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            close(fos,fis);
        }
        System.out.println("使用FileOutputStream拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒,生成的目标文件大小为"+getSize(dest));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
4.23 使用BufferedXXX类使用默认大小缓冲来读写文件
    /**
     * 拷贝文件(方法三)
     * @param src
     * @param dest
     */
    public static void copyByBufferedStream(File src,File dest) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        long start = System.currentTimeMillis();
        try{
            bis = new BufferedInputStream(new FileInputStream(src));
            bos = new BufferedOutputStream(new FileOutputStream(dest));
            int b = 0;
            while( (b = bis.read())!=-1){
                bos.write(b);//使用BufferedXXX重写的write方法进行写入数据。该方法看似未缓冲实际做了缓冲处理
            }
            bos.flush();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            close(bis,bos);
        }
        System.out.println("使用BufferedXXXStream拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
4.24 使用BufferedXXX类自定义大小缓冲来读写文件
    /**
     * 拷贝文件(方法四)
     * @param src 被拷贝的文件对象
     * @param dest 拷贝目的地文件对象
     * @param size 自定义缓冲区大小
     */
    public static void copyByBufferedStream(File src,File dest,int size) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        long start = System.currentTimeMillis();
        try{
            bis = new BufferedInputStream(new FileInputStream(src));
            bos = new BufferedOutputStream(new FileOutputStream(dest));
            int b = 0;
            byte[] buff = new byte[size];
            while( (b = bis.read(buff))!=-1){//数据读入缓冲区
                bos.write(buff,0,b);//将缓存区数据写入输出流中
            }
            bos.flush();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            close(bos,bis);
        }
        System.out.println("使用BufferedXXXStream拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

方法测试:

    public static void main(String[] args) {
        File src = new File("E:\\iotest\\1.bmp");
        File dest = new File("E:\\iotest\\1_copy.bmp");
        //无缓冲区
        copyByFileStream(src,dest);
        sleep(1000);
        //32k缓冲区
        copyByFileStream(src,dest,32);
        sleep(1000);
        //64k缓冲区
        copyByFileStream(src,dest,64);
        sleep(1000);
        //BufferedOutputStream缓冲区默认大小为8192字节
        copyByBufferedStream(src, dest);
        sleep(1000);
        //BufferedOutputStream缓冲区默认大小为8192*2字节
        copyByBufferedStream(src, dest, 8192*2);
    }

    //我本地测试如下:
    使用FileOutputStream拷贝大小864054字节的文件未使用缓冲数组耗时:5092毫秒,生成的目标文件大小为864054字节
    使用FileOutputStream拷贝大小864054字节的文件使用了缓冲数组耗时:215毫秒,生成的目标文件大小为864054字节
    使用FileOutputStream拷贝大小864054字节的文件使用了缓冲数组耗时:124毫秒,生成的目标文件大小为864054字节
    使用BufferedXXXStream拷贝大小864054字节的文件使用了缓冲数组耗时:41毫秒,生成的目标文件大小为864054字节
    使用BufferedXXXStream拷贝大小864054字节的文件使用了缓冲数组耗时:8毫秒,生成的目标文件大小为864054字节
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

从上面可以看出来,如果不使用缓冲(4.21)拷贝一个八百多k的文件,竟然要5秒钟,这个速度让人捉急啊,所以我们拷贝文件时应该使用缓冲技术。由于这是常用功能,便提供了BuffereXXX类简化这个工作,上面我们使用BufferedOutputStream(4.23),即使调用write(int b)方法没有显示使用缓冲数组为什么性能也大大得到提升?来看看BufferedOutputStream中的部分源码:

public class BufferedOutputStream extends FilterOutputStream {
    /**
     * The internal buffer where data is stored.
     */
    protected byte buf[];

    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    } 

    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

从上面BufferedOutputStream中可以看出,构造实例的时候便创造了一个大小为8192字节的缓冲数组,当我们调用write(int b)方法时,并没有在调用完后直接写入,而是将每一个传入的int值放入到了buf中,只有count >= buf.length时,才调用flushBuffer将缓冲区的数据写入。

4.3 如何判断文件是否读写完毕?

我们一般在处理文件,一般是一边从输出流中读数据,然后将读出的部分进行处理,最后将处理好的数据写入到输出流中。那么要将一个文件完整的处理完,我们必须知道什么时候已经读到文件的末尾了。 
一般来说可以根据read方法返回的值,如果返回了-1表示没有可读取的字节了。 
另一种是使用available()方法查看还有多少可供读取的,当输入流每读一个字节,available()返回的值便减小1,这种模式很像游标的模式,但要注意的是available的适用场景是非阻塞读取,如本地文件读取,如果是网络io使用该方法,可能你拿到的值就不对了。 
总的来说一般输入流提供的读取方法是可以获得文件是否结束的标志,比如流默认的read方法,根据返回值是否非负,比如PrintReader和BufferedReader的readLine()方法,根据返回数据是否非空。

4.4 关于flush()的问题

为什么缓冲输出流写数据结束需要调用flush方法?我们以BufferedOutputStream的write(int b)方法源码为例,源码如下:

    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以得知,BufferedOutputStream在write时候,只有count >= buf.length,即缓冲区数据填满的时候才会自动调用flushBuffer()将缓冲区数据进行写入,也就是说如果缓冲区数据未满则将不会写入,这时我们需人为的调用flush()方法将未满的缓冲区数据进行写入,如下例,可以看出未flush和flush后的区别。

    public static void copyByBufferedStream(File src,File dest,int size) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        long start = System.currentTimeMillis();
        try{
            bis = new BufferedInputStream(new FileInputStream(src));
            bos = new BufferedOutputStream(new FileOutputStream(dest));
            int b = 0;
            byte[] buff = new byte[size];
            while( (b = bis.read(buff))!=-1){
                bos.write(buff,0,b);
            }
            System.out.println("拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒,未flush生成的目标文件大小为"+getSize(dest));
            bos.flush();
            System.out.println("拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒,flush后生成的目标文件大小为"+getSize(dest));
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    //测试
    public static void main(String[] args) {
        File src = new File("E:\\iotest\\1.bmp");
        File dest = new File("E:\\iotest\\1_copy.bmp");
        copyByBufferedStream(src, dest, 8192);
    }

    //测试结果:
    拷贝大小864054字节的文件使用了缓冲数组耗时:3毫秒,未flush生成的目标文件大小为860160字节
    拷贝大小864054字节的文件使用了缓冲数组耗时:3毫秒,flush后生成的目标文件大小为864054字节
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

从上面测试可以知道,如果未使用flush,带来的后果可能会造成部分数据丢失,为什么说是可能?因为如果文件大小刚好是缓冲区的整倍数,即最后一次写入的数据刚好填满缓冲区,write方法也会自动flushBuffer。另一种原因是,调用close方法后会自动将缓冲区的数据flush,我们看看close方法源码,由于BufferedOutputStream类中并没有重写close方法,因此我们去看看直接父类FilterOutputStream的close,源码如下:

    public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5

从上可以看出,在调用close后,其实内部调用了flush(),因此我们在调用close后,数据也能保证数据完整写入,我们验证下我们刚才的分析:

    public static void copyByBufferedStream(File src,File dest,int size) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        long start = System.currentTimeMillis();
        try{
            bis = new BufferedInputStream(new FileInputStream(src));
            bos = new BufferedOutputStream(new FileOutputStream(dest));
            int b = 0;
            byte[] buff = new byte[size];
            while( (b = bis.read(buff))!=-1){
                bos.write(buff,0,b);
            }
            System.out.println("拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒,未flush,未调close生成的目标文件大小为"+getSize(dest));
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            close(bos,bis);//一个工具方法,内部调用了输出流的close方法,文章后面部分可以看到该方法的源码
            System.out.println("拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒,未flush,调用close后生成的目标文件大小为"+getSize(dest));
        }
    }

    //测试
    public static void main(String[] args) {
        File src = new File("E:\\iotest\\1.bmp");
        File dest = new File("E:\\iotest\\1_copy.bmp");
        copyByBufferedStream(src, dest, 8192);
    }

    //测试结果如下:
    拷贝大小864054字节的文件使用了缓冲数组耗时:5毫秒,未flush,未调close生成的目标文件大小为860160字节
    拷贝大小864054字节的文件使用了缓冲数组耗时:6毫秒,未flush,调用close后生成的目标文件大小为864054字节
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

这样的结果确实符合我们对源码的分析

4.5 关于网络流中使用available()方法的问题

当你在网络io中,比如你用socket编程时获取到的流进行读写时,会发现使用available方法有问题,原因是网络io的特点是: 
1.非实时性。你调用available()方法判断剩余流的大小时,远端数据可能还未发送,或者要发送的数据处于队列中,因此通过available()拿到的可用长度可能是0 
2.非连续性。由于网络数据传输中,一般会分段多次发送,available仅仅能返回本次的可用长度。 
鉴于以上两个特点,使用available判断网络io还有多少数据可读是不合适的,因此解决该问题一般采用自定义协议,比如文件大小,文件名等信息放入流的头几个字节中,接收方根据收到的头信息来解析出对法传送的文件大小,根据大小来判断还剩多少字节需要读取,是否读取完毕。

4.6 关于关闭流的问题

1)为什么需要手动关闭? 
参见【java】手动释放资源问题

2) 关闭流的正确写法 
先来两个不规范的,上代码:

    /**
     * 案例一
     */
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream(new File("E:\\iotest\\1.bmp"));
            FileOutputStream fos = new FileOutputStream(new File("E:\\iotest\\1_copy.bmp"));
            //其他代码
            //......
            fos.close();
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 案例二
     */
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(new File("E:\\iotest\\1.bmp"));
            fos = new FileOutputStream(new File("E:\\iotest\\1_copy.bmp"));
            //其他代码
            //......
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            try {
                if(fos!=null){
                    fos.close();    
                }
                if(fis!=null){
                    fis.close();    
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

案例一写法不规范的原因是fis和fos对象执行一些方法时是可能发生异常的,一旦出现异常,虽然由于进行了try catch,但是执行流程会直接进入到catch中,会跳过流的关闭操作。

案例二写法不规范的原因是,虽然讲close操作放入了finally中,但是一旦fos.close执行出现异常,则fis无法正常关闭,修改方法是在finally块中的同时对每个close都单独try catch。

因此写一个关闭流的工具方法,写法如下:

    /**
     * 关闭给定的io流
     */
    public static void close(Closeable...closes){
        for (Closeable closeable : closes) {
            try {
                if(closeable!=null){
                    closeable.close();                  
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 调用close()方法
     */
     public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(new File("E:\\iotest\\1.bmp"));
            fos = new FileOutputStream(new File("E:\\iotest\\1_copy.bmp"));
            //其他代码
            //......
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            close(fos,fis);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

5.java io流到底能干些什么?

java io主要做两类事情: 
1)数据传输,实际例子如文件上传,下载,文件本地拷贝等 
2)数据处理,如文本内容加密,图片处理,文件压缩,音视频处理等

6.实践一下

下面就来个简单的实践

6.1 文件拷贝

本文中4.2中的几个方法均可进行文件拷贝,拷贝一般都会使用缓冲来提高性能

6.2 文本加密

因为我们通过java io流处理可以获得文本的原始数据,我们在数据上进行加工就可以加密文本,通过相反的方式就可以解密文本,实际生产中当然不会像下面这么简单的加密,下面我们只是做个示意

    public static void encrypt(File src, File dest) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader(src);
            fw = new FileWriter(dest);
            int tmp = 0;
            while ((tmp = fr.read()) != -1) {
                fw.write(tmp+1);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(fw, fr);
        }
    }

    public static void decrypt(File src, File dest) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader(src);
            fw = new FileWriter(dest);
            int tmp = 0;
            while ((tmp = fr.read()) != -1) {
                fw.write(tmp-1);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(fw, fr);
        }
    }
    public static void main(String[] args) {
        File src = new File("E:\\iotest\\1.txt");
        File dest = new File("E:\\iotest\\1_jiami.txt");
        File dest2 = new File("E:\\iotest\\1_jiemi.txt");
        encrypt(src, dest);
        decrypt(dest, dest2);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

上面代码中处理方式很简单,仅仅做了一个字符加1处理,然后解密在字符上进行减1处理,便能达到一个加密解密的效果,效果如下 
原始密文:

赵兄托你帮我办点事
  • 1

加密后的文本:

赶充扙佡帯戒功為二
  • 1

解密后的文本:

赵兄托你帮我办点事
  • 1
6.3 图片处理

图片文件中存放中图片的分辨率,以及每个像素的色值等信息。我们经常看见如photoshop这类软件对图片进行旋转,扭曲,滤镜等处理,就是对图片中的二进制信息进行一个矩阵变换。下面我们试着做一个图片反相效果。 
做这个处理前有两个问题需要考虑: 
1)由于平时使用的jpg采用了压缩算法,所以图片中的每个字节并不是图片本身的像素信息,因此我们选择一个bmp格式的图片,来对每一位进行处理,以便达到我们想要的效果。 
2)基本所有的二进制文件,图片,音频等文件都有一个头信息,存放该文件的一些基本信息,这些基本信息决定了这个文件是一个bmp图片或者是个gif图片或者是个jpg图片。因此我们对bmp图片做处理时不能破坏bmp文件的头信息。我们知道bmp文件很大,是因为他没有进行压缩,文件内容保存的是图片上对应的像素点的颜色值信息,因此我们队这些颜色值信息做一个处理,即可以得到一些特别的效果。 
代码如下:

    public static void imageFilter(File src, File dest) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            int b = 0;
            int hasRead = 0;
            int headSize = 8*12;
            while ((b = fis.read()) != -1) {
                hasRead++;
                if(hasRead>headSize){
                    fos.write(-b);
                }else{
                    fos.write(b);
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(fos, fis);
        }
    }

    public static void main(String[] args) {
        File src = new File("E:\\iotest\\1.bmp");
        File dest = new File("E:\\iotest\\1_copy.bmp");
        imageFilter(src, dest);//800k的文件执行时间大概好几秒,比较长,这里只是做个示意处理
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

上面代码中写数据时,如果字节长度小于headSize则原样输出(headSize我这里取的是12个字节,bmp头信息具体多大我并没有去查询。如果取太小头信息被破坏,bmp文件将不是bmp文件。因此取几十个字节只要保证头信息完整即可。),大于headSize的部分进行一个处理,我们对颜色值取反。效果如下: 
原图: 
这里写图片描述 
处理后图片: 
这里写图片描述

通过上面三个的示例可以大致了解java io能做些什么。当然上面只是一个简单的处理,并不能用于实际生产,这里只是说明持有二进制数据,你将可以对文件为所欲为。

Java IO模型:BIO、NIO、AIO讲解 IO,Input/Output 简写,是指内存和外设之间的数据复制的过程。输入是指数据从外设复制到内存中, 输出则是指数据从内存复制到外设。目前,IO 可以通过数据流、序列化和文件系统为系统提供输入和输出而Java中IO是以流为基础进行输入输出的,所有数据被串行化写入输出流,或者从输入流读入。Java IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。 阅读详情

相关推荐

Python实战:5分钟搞定N-CMAPSS航空发动机数据集解析(附完整代码)

本文提供了一份针对N-CMAPSS航空发动机数据集的快速解析实战指南。通过Python的h5py和Pandas库,详细演示了如何高效读取HDF5格式文件、理解数据结构、提取关键特征(如传感器数据与剩余使用寿命RUL),并整合为可用于预测性维护分析的DataFrame,帮助研究者和工程师快速上手这一重要的工业时序数据集。

weixin_29083373的博客 281

【JAVA基础】- BIO介绍及其使用

BIO(Blocking I/O)是传统java io编程既同步阻塞IO,服务器实现模式为一个连接一个线程。客户端有连接请求时服务器端就会新起一个线程进行处理。当线程空闲时为减少不必要的线程开销,可以通过线程池机制改善。BIO方式适合用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限应用中。

记录总结工作过往 1262

用友U8库龄分析避坑指南:物料出入库日期计算的5个常见错误

本文深入剖析了在用友U8系统中进行库龄分析时,物料出入库日期计算的五个常见陷阱。从数据源选择、业务类型过滤、计价方式逻辑、期初数据处理到查询性能优化,逐一解析错误根源并提供避坑指南,帮助企业获得准确的库龄数据,有效管理库存与资金风险。

weixin_29209801的博客 457

Java基础知识——BIO模式

文章目录一、Java的I/O总述1、I/O模型2、应用二、BIO模式1、传统的服务器、客户端通信(一对一):2、服务器和客户端的通信(一对多) 一、Java的I/O总述 I/O模型:就是用什么样的通道或者通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信的性能,包括BIO、NIO、AIO 1、I/O模型 BIO: 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即用户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情就会造成不必要的线程开销 NIO: 同步非阻

weixin_46025531的博客 3196

材料力学小白必看:纯弯曲正应力计算全解析(附实例演示)

本文详细解析了材料力学中纯弯曲正应力的计算原理与工程应用,通过实例演示帮助初学者掌握关键公式和计算流程。内容涵盖纯弯曲的基本假设、正应力分布规律、截面特性计算,以及现代工具如Excel、ANSYS和Python在应力分析中的应用,特别适合材料力学小白系统学习。

sea99的博客 1233

告别‘小爱同学’:用ESP32-S3和esp-sr SDK,从零打造一个离线语音助手(含完整代码)

本文详细介绍了如何利用ESP32-S3芯片和esp-sr SDK从零构建一个离线语音助手,涵盖硬件选型、声学设计、SDK配置及低功耗优化等关键步骤。通过实战案例和完整代码,帮助开发者实现自定义唤醒词和命令词,打造低成本、高隐私的本地语音控制解决方案。

weixin_29041195的博客 372

java BIO

ava BIO基本介绍 Java BIO工作机制 传统的BIO编程实例回顾 1、BIO模式下发送和接收消息 2、BIO模式下多发和多收消息 3、BIO模式下接收多个客户端 伪异步I/O编程 基于BIO形式下的文件上传

qi341500的博客 1186

从毫米波到计算机视觉:智能座舱监控系统的多模态技术融合

本文探讨了智能座舱监控系统中多模态技术融合的创新应用,涵盖毫米波雷达、红外传感与计算机视觉的协同架构。通过传感器时空对齐、低光照算法突破及隐私保护设计,系统在DMS、OMS等场景实现99.7%的检测准确率,同时优化成本至$45以内,为汽车智能化提供关键技术支撑。

d6e7f8的博客 904

Java IO 模型之 BIO、NIO、AIO 详解

在 Java 编程中,IO(Input/Output)操作是非常常见的操作,它涉及到文件读写、网络通信等方面。Java 提供了各种类来支持这些操作。本文将从 IO 的基础知识讲起,逐步深入,介绍 Java IO 的各个方面。Java IO(Input/Output)是 Java 语言中用于读写数据的 API,它提供了一系列类和接口,用于读取和写入各种类型的数据。

流华追梦的专栏 2117

Android Input——输入事件ANR执行(十六)

上一篇文章我们介绍了输入事件 ANR 的触发流程,这里我们再来看一下输入事件 ANR的执行流程。

小旭的专栏 944

Oracle EBS 帐套(Ledger/SOB)如何定义会计主体、如何支撑多准则 / 多报告、组织与帐套的绑定关系、以及项目落地时的设计差异。下面从实现逻辑、核心要素、组织模型、多账簿、科目设计、项

本文对比了Oracle EBS R12与SAP S/4HANA在帐套(Ledger)设计上的核心差异。Oracle采用4C架构(科目/币种/日历/准则),帐套作为独立会计主体,主辅账物理隔离;SAP则以公司代码为主体,通过平行分类账在同一凭证中实现多准则核算。关键差异体现在:Oracle强调边界隔离,适合差异大的集团;SAP注重统一视图,适合实时合并需求。实施时需注意:Oracle避免过度建账套,SAP需统筹科目与维度设计。两者分别体现了"盒子"与"滤镜"的设计哲学。

金牌架构师 419

Java IO - BIO 详解

客户端向服务器端发出请求后,客户端会一直等待(不会再做其他事情),直到服务器端返回结果或者网络出现问题。服务器端同样的,当在处理某个客户端A发来的请求时,另一个客户端B发来的请求会等待,直到服务器端的这个处理线程完成上一个处理。

珍惜当下 886

OV5645摄像头原理图设计,已量产

在嵌入式视觉项目里,摄像头小板真正决定量产稳定性的,往往不是把传感器接出来这件事本身,而是电源域有没有拆清、参考时钟够不够干净、主板调试时有没有留出可落地的接口。ATK-MC5645 这张原理图的价值,就在于它没有把 OV5645 裸接到一排连接器上,而是把 AFVDD_2V8、DOVDD_1V8、DVDD_1V5、24MHz 时钟和 MIPI/控制信号都收敛在一块独立模组上。

不脱发的程序猿 345

java aio详解_Java之IO , BIO , NIO , AIO详解

课程名称《Java之IO , BIO , NIO , AIO 详解与运用》课程介绍《Java之IO , BIO , NIO , AIO 知多少?》主要是针对有一定基础的Java学员。本课程主要围绕着IO,BIO,NIO,AIO等基础知识展开的,同时会讲解很多具体代码实例。在本课程中,学员可以快速掌握这些基础的概念和自己动手写出性能高的IO流代码。以及把这些知识运用到自己真实的项目中去。课程目标本课...

weixin_42525387的博客 185

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Java IO -BIO 详解

以前大多数网络通信方式都是阻塞模式的,即:客户端向服务器端发出请求后,客户端会一直等待(不会再做其他事情),直到服务器端返回结果或者网络出现问题。服务器端同样的,当在处理某个客户端A发来的请求时,另一个客户端B发来的请求会等待,直到服务器端的这个处理线程完成上一个处理。

qq_63353796的博客 1034

贝叶斯统计 [韦来生编]

贝叶斯(Bayes)统计是近几十年来迅速发展起来的数理统计的一个重要分支。贝叶斯方法与经典统计方法的主要不同之处是进行统计推断时除了利用样本信息外,还要利用参数的先验信息,因此可以提高统计推断或统计决策的效果。它在经济、金融、生物、医学、自然科学和社会科学等许多领域具有广泛的应用。贝叶斯方法的研究已渗透到了统计学的几乎所有领域。作者在给中国科学技术大学概率论与数理统计专业本科生开设的“数理统计”课程中有一章专门讲授“贝叶斯方法和统计决策理论”,近十年来也给中国科学技术大学概率统计专业研究生开设过几次“贝叶斯分析”课。本书是在对过去讲稿的内容作了适当的增减和调整的基础上完成的。  本书共分六章。第1章是绪论,介绍了贝叶斯统计的若干基本概念,同时对必要的数理统计的基础知识有重点地作了回顾。第二章介绍了确定先验分布的若干可供选择的方法。第三章介绍了几类常见统计模型参数的后验分布的主要结果和计算方法。第四章和第五章分别介绍了“贝叶斯统计推断”和“贝叶斯统计决策”的内容。第六章介绍了贝叶斯统计计算的若干方法,包括蒙特卡洛方法和马尔可夫链蒙特卡洛(MCMC)方法以及相关统计软件的简介。本书的每一章附右大量的例题和习题,书末附有常用的几个表格和部分习题解答供读者查用。  大约可在54学时内讲授本书的第1章至第六章的主要内容,教师可根据学时需要选讲本书第1章至第六章的部分内容。书中标“*”号的章节可略去不讲,留给读者作为阅读材料。第六章例题中的R代码和部分例题的数据文件等可从作者个人主页下载。  作者在编写本书的过程中参考了J.O.Berger教授的《统计决策论及贝叶斯分析》和茆诗松教授编写的《贝叶斯统计》等书中的一些内容,以及《贝叶斯分析》一书中由张伟平博士编写的有关贝叶斯统计计算的一些内容。在此表示衷心感谢。  本书编写准备过程中,中国科学技术大学统计与金融系的几个研究生帮助完成了本书部分中文的录入和排版,作者对他们的辛勤工作表示真诚的感谢。高等教育出版社为本书的出版给予了大力支持。在此一并致谢。

Java IO BIO(阻塞式IO)详解

摘要: Java BIO(阻塞式IO)是传统的同步阻塞IO模型,核心特点包括线程阻塞等待IO操作完成,以及为每个连接分配独立线程。其典型应用如ServerSocket.accept()和InputStream.read()均会阻塞线程。通过线程池可优化资源消耗,但高并发时仍面临线程数爆炸、上下文切换等问题。BIO适用于低并发场景或遗留系统,与NIO/AIO相比,其简单但性能受限。理解BIO有助于掌握IO模型演进,为学习高性能IO框架奠定基础。

csdn_tom_168的博客 1114

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Java的IO模型详解-BIO,NIO,AIO

Java IO模型,bio, nio以及不常用的aio

wendao76的专栏 2111

Java IO 与 BIO、NIO、AIO 详解

在 Java 中,I/O(输入/输出)操作是与外部系统(如文件、网络等)进行数据交换的基础。Java 提供了多种 I/O 机制,包括传统的阻塞式 I/O(BIO)、非阻塞式 I/O(NIO)和异步 I/O(AIO)。本文将详细探讨这三种 I/O 模型的特点、区别及其适用场景。

专注Java和MySQL领域知识分享 1359

Java IO模型 BIO,NIO,AIO 详解

阻塞式IO模型,BIO JDK1.4 之前都是采用BIO模式(blocking I/O) ,阻塞式IO,模型如图 解释: 应用程序需要从磁盘读取数据分为两个阶段,1将磁盘数据复制到内核,2将内核数据复制到应用程序空间: 准备数据:应用程序问cpu说:我需要一个aaa.txt 文件,你去给我取来。CPU告诉应用程序,你等着我去给你准备数据(应用程序一直傻傻等待)。CPU转身问磁盘:磁盘我需要aaa.txt 文件,磁盘开始准备数据,并将数据复制到内核空间中(耗时) 等磁盘把数据给到CPU之后,告...

qq_21355765的博客 286

java io bio nio aio 详解

BIO、NIO、AIO的区别: BIO就是基于Thread per Request的传统server/client实现模式, NIO通常采用Reactor模式, AIO通常采用Proactor模式, AIO简化了程序的编写,stream的读取和写入都有OS来完成,不需要像NIO那样子遍历Selector。Windows基于IOCP实现AIO,Linux只有eppoll模拟实

原创学无止尽 8523

Java IO,BIO,NIO,netty详解

基本概念 Java网络IO的演化,从最开始JDK1.4之前是基于阻塞的IO;发展到1.4发布后的Nio提供了selector多路复用的机制以及channel和buffer,再到1.7的NIO升级提供了真正的异步api; 经常听人提起,同步阻塞服务器或者异步非阻塞服务器,网上有很多的文章针对这个概念作出了讲解,每个人理解的貌似都不太一样。最容易把异步和非阻塞搞混…我这里简单的说下自己的理解: 同步synchronous、异步asynchronous,他们的区别就是发起任务后,本身的一个状态——如果是一直等待结

qq_40710276的博客 377

JAVA|IO(BIO,NIO,AIO)详解

目录 同步、异步、阻塞、非阻塞 1.同步 2.异步 3.阻塞 4.非阻塞 BIO NIO AIO BIO、NIO、AIO适用场景分析: 同步、异步、阻塞、非阻塞 同步、异步主要针对io数据读写来定义的,读写数据过程中不阻塞线程称为异步io,否则,称为同步io。 1.同步 用户进程触发IO操作并等待或者轮询的去查看IO操作是否完成 2.异步 用户触发IO操作以后,可以干别的事,IO操作完成以后再通知当前线程继续处理 阻塞、非阻塞主要是针对线程发起io请求后,是否立即返回来定义的

一名路过的小码农啊 727
上一篇: Tomcat服务器学习和使用
下一篇: sql 经典面试题
ys_code
博客等级 码龄10年 13粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值