JavaSE基础——(20)IO流&字节流

目录

一、IO流

1.1 IO流概述及分类

1.2 IO流常用父类

1.3程序书写

二、字节流

2.1FileInputStream

2.2FileOutputStream

2.3FileOutputStream追加

2.4拷贝文件

2.4.1按字节逐个拷贝

2.4.2字节数组整体拷贝

2.4.3小数组拷贝

2.4.4缓冲字节流拷贝

2.5flush和close方法

2.6字节流操作中文

2.7流的异常处理

2.7.1老版本(1.6版本及以前)

2.7.2新版本(1.7版本)

2.8给图片加密

2.9拷贝文件

2.10录入数据拷贝到文件


一、IO流

1.1 IO流概述及分类

IO流是用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,

Java用于操作流的类都在IO包中,流按照流向可以分为输入流(InputStream)和输出流(OutputStream),

按照操作类型可以分为:字节流和字符流,

  • 字节流:字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的
  • 字符流:字符流只能操作纯字符数据,比较方便

1.2 IO流常用父类

  • 字节流抽象父类
    • InputStream
    • OutputStream
  • 字符流抽象父类
    • Reader
    • Writer

1.3程序书写

  • 使用前,导入IO包中的类
  • 使用时,进行IO异常处理
  • 使用后,释放资源

二、字节流

字节流主要分为InputStream和OutputStream,因为这两个是抽象类,我们在使用的时候只能使用它的子类,

JAVA中针对文件的读写操作设置了一系列的流,

其中主要有FileInputStream、FileOutputStream、FileReader、FileWriter四种最为常用的流,

我们首先学习一下字节流FileInputStream和FileOutputStream。

2.1FileInputStream

FileInputStream是从文件系统中的某个文件中获得输入字节,用于读取诸如图像数据之类的原始数据流,

我们一般使用read方法来读取字节,read()可以从输入流中读取一个数据字节,并且以int类型返回,当文件到末尾时会返回-1,

我们利用这个特点来读取一个txt文件测试一下,

import java.io.FileInputStream;
import java.io.IOException;

public class IOTest {
    public static void main(String[] args) throws IOException {
        FileInputStream f=new FileInputStream("data.txt");
        int ch;
        while((ch=f.read())!=-1){
            System.out.println(ch);
        }
        f.close();
    }
}

这里有一个小问题,既然read方法每次读取的是一个字节,为什么不用byte类型接收而用int类型,

因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式存储的,

如果每次读取返回的类型都是byte,有可能读到11111111,这个二进制补码代表的数字为-1,而程序碰到-1后就会停止读文件,

后面的数据就没有办法读到了,所以在读取的时候用int类型接收,如果是11111111,那么前面也会补足24个0凑够4个字节,然后返回

这样就可以保证正常读取后面的数据了。

2.2FileOutputStream

FileOutputStream是文件输出流,是用于将数据写入File或者FileDescriptor的输出流。

FileOutputStream用于写入诸如图像数据之类的原始字节的流,我们一般用的是write方法,

void write(byte b[])//将b.length个字节从指定的byte数组写入文件输出流中
void write(byte b[],int off,int len)//将指定byte数组从偏移量off开始的len个字节写入文件输出流
void write(int b)//将指定字节写入文件输出流

我们往txt文件写入字符串"java"试一下,

        FileOutputStream f=new FileOutputStream("dataOutput.txt");
        f.write(106);//字符j(虽然写入的是一个int数,但是会自动去除前3个八位,变为byte一个字节)
        f.write(97);//字符a
        f.write(118);//字符v
        f.write(97);//字符a

        f.close();

2.3FileOutputStream追加

当我们想保留原来文件的内容,并在原来内容的基础上添加一些字符,这时就不能只用文件名构造FileOutputStream()了,

因为用文件名构造FileOutputStream对象,在创建对象的时候如果没有文件会帮我们创建文件,如果有则会将文件清空再重新写入,

我们需要添加一个额外的布尔参数append,append如果为true ,则字节将被写入文件的末尾而不是开头,也就是不会覆盖前面的内容

FileOutputStream(File file, boolean append)//创建文件输出流以写入由指定的 File对象表示的文件
FileOutputStream(String name, boolean append)//创建文件输出流以指定的名称写入文件

我们来测试一下,接着上面的内容"java",继续再写入一个"java"。

        FileOutputStream f=new FileOutputStream("dataOutput.txt",true);
        f.write(106);
        f.write(97);
        f.write(118);
        f.write(97);

        f.close();

2.4拷贝文件

在java文件流中,拷贝文件一般有四种方式。

2.4.1按字节逐个拷贝

        FileInputStream fis=new FileInputStream("luffy.jpg");
        FileOutputStream fos=new FileOutputStream("luffyCopy.jpg");

        int b;
        while((b=fis.read())!=-1){
            fos.write(b);//逐个字节拷贝,复制图片
        }
        fis.close();
        fos.close();

可以看到图片已经拷贝成功了。

但是这种速度非常慢,是逐个字节进行拷贝的,如果碰到比较大的文件效率就会很低。

2.4.2字节数组整体拷贝

如果遇到字节数比较多的文件,拷贝的时候时间消耗就会比较大,这里我们可以用到available方法,

int availabe()//返回文件可以读取的字节个数

int read(byte[] b)//从输入流中将最多b.length个字节的数据读取到byte数组中,并返回读取的有效字节个数
int read(byte[] b,int off, int len)//从输出流中将最多len个字节的数据读取到byte数组中,开始位置为off,并返回读取的有效字节个数

int write(byte[] b)//将b.length个字节从指定byte数组写入文件输出流,并返回写入的有效字节个数
int write(byte[] b,int off, int len)//将指定byte数组从偏移量off开始的len个字节写入文件输出流,并返回写入的有效字节个数

那么我们看看如何使用available方法拷贝字节数组,

        FileInputStream fis=new FileInputStream("testVideo.avi");
        FileOutputStream fos=new FileOutputStream("testVideoCopy.avi");

        byte arr[]=new byte[fis.available()];
        fis.read(arr);
        fos.write(arr);

        fis.close();
        fos.close();

这种方式相比于一个字节一个字节读取,效率快了很多,但是在开发中这种方式还是不常用的,

因为如果文件大小过大的话,一次将文件读取到程序中,JVM可能会出现内存溢出的错误,所以在开发中这两种方法都不常用。

2.4.3小数组拷贝

小数组拷贝就是定义一个容量相对小的数组,让数组一次无法接收完文件流中的数据,

然后不断读取写入,读取写入直到文件流中没有数据了,我们来看看代码如何实现,

        FileInputStream fis=new FileInputStream("testVideo.avi");
        FileOutputStream fos=new FileOutputStream("testVideoCopy.avi");

        byte arr[]=new byte[1024];//这里一般定义为1024,也可以自己设置为其他较小的数字(最好为1024的整数倍)
        int len;
        while((len=fis.read(arr))!=-1){//当文件流中还有数据时
            fos.write(arr,0,len);//写入读取进来的有效数据
        }

        fis.close();
        fos.close();

2.4.4缓冲字节流拷贝

缓冲字节流就是用到了BufferedInputStream和BufferedOutputStream,其构造方法如下,

BufferedInputStream(InputStream in)//使用输入流in创建一个BufferedInputStream对象,供以后使用
BufferedInputStream(InputStream in, int size)//使用输入流in创建BufferedInputStream,具有指定缓冲区大小size

BufferedOutputStream(OutputStream out)//创建一个新的缓冲输出流,以将数据写入指定的底层输出流
BufferedOutputStream(OutputStream out, int size)//创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流

当创建BufferedInputStream时,将创建一个内部缓冲区数组,大小一般为1024*8个字节,装满后一次刷新到文件上,

当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节,

然后我们使用缓冲字节流对象进行读写操作,其功能与前面的读写相同,我们看看如何具体使用的,

        FileInputStream fis=new FileInputStream("data.txt");
        FileOutputStream fos=new FileOutputStream("dataCopy.txt");
        BufferedInputStream bis=new BufferedInputStream(fis);
        BufferedOutputStream bos=new BufferedOutputStream(fos);

        int len;
        while((len=bis.read())!=-1){//当文件流中还有数据时
            bos.write(len);//写入读取进来的有效数据
        }

        bis.close();
        bos.close();
        fis.close();
        fos.close();

2.5flush和close方法

  • flush()方法
    • 用来刷新缓冲区的,将缓冲区的字节全部都刷新到文件上,刷新之后可以再次写出数据
  • close()方法
    • 用于关闭流释放资源的,如果是带缓冲区的流对象使用close方法,会先刷新缓冲区再关闭流,将缓冲区的字节全部都刷新到文件上,关闭之后不可以写出数据

2.6字节流操作中文

字节流在读取中文的时候,因为一个中文用两个字节表示,字节流一个字节一个字节读取时有可能会读到半个中文,造成乱码,

如果我们用小数组来读取,那么怎么设置小数组的大小也是一个问题,如果大小设置为两个,那么两个中文之间要是有一个标点符号一样还是会乱码,

所以我们一般使用字符流读取中文,在用字节流读取中文时可能会出现乱码的情况,

在字节流写出中文时,字节流直接操作的是字节,所以写出中文也必须将字符串转换为字节数组再写出,

比如write("你好".getBytes()),再比如写回车换行为write("\r\n".getBytes())  

2.7流的异常处理

2.7.1老版本(1.6版本及以前)

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        try{//try finally语句保证了创建输出流有异常时,输入流可以正常关闭
            fis=new FileInputStream("data.txt");
            fos=new FileOutputStream("dataCopy.txt");

            int b;
            while((b=fis.read())!=-1){
                fos.write(b);
            }
        }finally {//里面的try finally嵌套保证fis关闭异常时可以正常关闭fos
            try{
                if(fis!=null)
                    fis.close();
            }finally {
                if(fos!=null)
                    fos.close();
            }
        }
    }
}

2.7.2新版本(1.7版本)

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        try(
            FileInputStream fis=new FileInputStream("data.txt");
            FileOutputStream fos=new FileOutputStream("dataCopy.txt");
        ){
            int b;
            while((b=fis.read())!=-1) {
                fos.write(b);
            }
        }
        //不用显示写close关闭流,在try后的小括号中创建流对象,
        //执行完大括号的代码后,流对象会自动关闭
    }
}

注意,小括号中不能创建不具备自动关闭功能的对象(需要实现AutoCloseable接口)。

2.8给图片加密

给图片加密实现起来比较简单,将读出来的字节通过自己设计的编码规则对应到另外的一个数,再将结果保存输出,

解码的时候按照编码规计算出原来的字节码,即可得到原来的照片了,我们看看具体如何实现,

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream("luffy.jpg"));
        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("luffyEncrypt.jpg"));

        int b;
        while((b=bis.read())!=-1){
            //异或操作使用两次就会得到数本身,解码的时候再异或一次即可得到原字节码
            bos.write(b^123);//将得到的字节码异或123
        }
        bis.close();
        bos.close();

        Decode();
    }

    public static void Decode() throws IOException {
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream("luffyEncrypt.jpg"));
        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("luffyDecode.jpg"));

        int b;
        while((b=bis.read())!=-1){
            bos.write(b^123);//异或123进行解码
        }
        bis.close();
        bos.close();
    }
}

我们看看原图片,加密后图片和解密后的图片(顺序为依次从左至右),

2.9拷贝文件

要求在控制台录入文件的路径,将文件拷贝到当前项目下,

我们定义一个方法,获取键盘录入的文件路径,并且封装成File对象返回,

import java.io.*;
import java.util.Scanner;

public class IOTest {
    public static void main(String[] args) throws IOException {
        File file = getFile();//获取文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getName()));

        int b;
        while ((b = bis.read()) != -1) {
            bos.write(b);
        }

        bis.close();
        bos.close();
        System.out.println("拷贝成功");
    }

    public static File getFile() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入文件路径名:");
        while (true) {
            String fileDir = scanner.nextLine();
            File file = new File(fileDir);
            if (!file.exists()) {
                System.out.println("路径下不存在该文件,请重新输入文件路径名:");
            } else if (file.isDirectory()) {
                System.out.println("输入的路径为文件夹路径,请重新输入文件路径名:");
            } else {
                return file;//返回文件
            }
        }
    }
}

我们检查一下当前目录下有没有刚刚的ico.ico文件,可以看到正确拷贝过来了。 

2.10录入数据拷贝到文件

要求将键盘录入的数据拷贝到当前项目下的data.txt文件中,键盘录入的数据当遇到quit时退出,

import java.io.*;
import java.util.Scanner;

public class IOTest{
    public static void main(String[] args) throws IOException {
        Scanner sc=new Scanner(System.in);
        FileOutputStream fos=new FileOutputStream("data.txt");
        System.out.println("请键盘输入数据,输入quit结束:");

        while(true){
            String line=sc.nextLine();
            if("quit".equals(line)){
                break;
            }else{
                fos.write(line.getBytes());//字符串写出必须转换为字节数组
                fos.write("\r\n".getBytes());//写入换行
            }
        }

        fos.close();
        System.out.println("程序已退出");
    }
}

我们检查一下文本文件的内容,发现正确写入了内容,

猜你喜欢

转载自blog.csdn.net/weixin_39478524/article/details/112884408