java高级之IO流 -2

java高级之IO流

IO流原理

  • I/OInput/Output的缩写,I/O技术用于处理设备之间的数据传输。如读/写文件,网络通讯等。
  • 在Java程序中,我们使用“流(stream)” 的方式对数据进行输入/输出的操作。
  • 在java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
  • 在编写java程序时,站在程序的角度上来说:
    • 输入(input):读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
    • 输出(output):将程序(内存)数据输出到磁盘、光盘等存储设备中。

分类

流的分类

  • 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)

  • 按数据流的流向不同分为:输入流,输出流

  • 按流的角色的不同分为:节点流,处理流

  • 节点流和处理流

    • 节点流:直接从数据源或目的地读写数据;
    • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能(可以配合装饰者模式来理解处理流)。

I/O 类的分类

  • 字节的操作:InputStreamOutPutStream

  • 字符的操作:WriterReader

  • 磁盘的操作:File

  • 网络的操作:Socket。注意:Socket 类不在 java.io 包中。

    操作类型 JAVA类
    字节 InputStreamOutPutStream
    字符 WriterReader
    磁盘 File
    网络 Socket

IO流的结构

  • 在字节流的类中,抽象类 InputstreamOutputStream 定义了一些关于字节数据读写的基本操作。它们主要的实现类如下图:

image-20201010230415080

分类 作用
InputstreamOutputStream 抽象基类,定义了一些关于字节数据读写的基本操作
FileInputStreamFileOutputStream 对“文件”进行操作(写入/读出)
ByteArrayInputStreamByteArrayOutputStream 对“字节数组”进行操作(写入/读出)
ObjectInputStreamObjectOutputStream 用于“对象”与“流”之间的转换
FilterInputStreamFilterOutputStream 装饰类,增加额外功能,应用了装饰者模式
BufferedInputStreamBufferedOutputStream 为被装饰的类增加缓冲功能
DataInputStreamDataOutputStream 为被装饰的类增加写读指定数据类型的功能
LineNumberInputStream 为被装饰的类增加获取行数的功能,现已被 LineNumberReader 替代
...... ......
  • 在字符流的类中,抽象类 ReaderWriter 定义了一些关于字符数据读写的基本操作。常见的实现类有:

    image-20201008164359866

  • InputStream是一个字节流,即以byte为单位读取,Reader是一个字符流,即以char为单位读取。

常用方法

InputStream 抽象类方法 作用
int read() 每次读取一个字节
int read(byte b[]) 最多读取 b.length 长度的字节,并储存到数组 b 中,返回实际读取长度。
int read(byte b[], int off, int len) 最多读取 len 个字节,从 off 开始保存。
OutputStream 抽象类方法 作用
void write(int b) 写入方法
void write(byte b[]) 写入一个字节数组
void write(byte b[], int off, int len) 最多写入 len 个字节,从 off 开始保存。
...... ......

IO流的练习

IO流的体系

  • 在IO流体系中,InputStreamReader 就是Java标准库提供的最基本的输入流,是所有输入流的超类。而OutputStreamWriter是Java标准库提供的最基本的输出流,是所有输出流的超类。

    image-20201010230850476

分类 字节输入流 字节输出流 字符输入流 字符输出流
超类 InputStream OutputStream Reader Writer
文件 FileInputStream FileOutputStream FileReader FileWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
打印流 PrintStream PrintWriter
......

访问文件

  • FileInputStream从文件流中读取数据,FileOutputStream将字节写入文件流;FileReader可以打开文件并获取字符流ReaderFileWriter可以向文件中写入字符流的Writer

  • 定义文件路径时,windowsDOS系统默认使用\来表示,在Java字符串中需要用\\表示一个\UNIXURL使用/来表示;也可以使用File类提供的常量separator

  • 写入文件时,构造器FileOutputStream(file),将覆盖该目录的同名文件;构造器FileOutputStream(file,true)不会覆盖,而是在文件内容末尾追加内容。

  • 读取文件时,需要保证该文件已存在,否则报异常。

  • 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt

  • 字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

  • FileInputStreamFileOutputStream

    //指定路径下文件的复制
    public void copyFile(String srcPath, String destPath){
       FileInputStream fis = null;
       FileOutputStream fos = null;
       try {
           File srcFile = new File(srcPath);
           File destFile = new File(destPath);
           fis = new FileInputStream(srcFile);
           fos = new FileOutputStream(destFile);
           //复制的过程
           byte[] buffer = new byte[1024];
           int len;
           while((len = fis.read(buffer)) != -1){
           //write(byte[] b)方法: 将b.length个字节从指定字节数组写入此文件输出流中
           //write(byte[] b, int off, int len)方法:将指定字节数组中从偏移量off开始的len个字节写入此文件输出流
               fos.write(buffer,0,len);
           }
       } catch (IOException e) {
           e.printStackTrace();
       } finally {
           if(fos != null){
               try {
                   fos.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
           if(fis != null){
               try {
                   fis.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }
    }
    
  • FileReaderFileWriter

    @Test
    public void testFileReaderFileWriter() {
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        try {
            //1.创建File类的对象,指明读入和写出的文件
            File srcFile = new File("hello.txt");
            File destFile = new File("hello2.txt");
            fileReader = new FileReader(srcFile);
            fileWriter = new FileWriter(destFile);
            char[] chars = new char[5];
            int len;
            while ((len = fileReader.read(chars)) != -1) {
                fileWriter.write(chars, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

缓冲流

  • 带缓冲功能的流类可以提高数据读写的速度,使用时,它会创建一个内部缓冲区数组,默认使用8192个字节(8Kb)的缓冲区。

    private static int DEFAULT_BUFFER_SIZE = 8192;
    
  • 在读取数据时,数据按块读入缓冲区,之后的读操作则直接访问缓冲区。例如当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。

  • 类似的向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用flush()方法可以强制将缓冲区的内容全部写入输出流。

  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。

  • flush()方法的作用是手动将buffer中内容写入文件。

  • 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。

    @Test
    public void BufferedStreamTest() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            //1.造文件
            File srcFile = new File("风景.jpg");
            File destFile = new File("风景1.jpg");
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream((srcFile));
            FileOutputStream fos = new FileOutputStream(destFile);
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            //3.复制的细节:读取、写入
            byte[] buffer = new byte[10];
            int len;
            while ((len = bis.read(buffer))!=-1){
                bos.write(buffer,0,len);
                bos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bis!=null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

转换流

  • 为了将字节流转换为字符流,Java API提供了转换流:InputStreamReader 用于将字节输入流转换为字符输入流,即将InputStream转换为ReaderOutputStreamWriter 用于将输出的字符流变为字节流,即将Writer转换为OutputStream

  • InputStreamReaderInputStream中读入字节流(byte),然后,根据编码设置,再转换为char就可以实现字符流,

  • OutputStreamWriter基于OutputStream构造的,它接收char,然后在内部自动转换成一个或多个byte,并写入OutputStream

    @Test
    public void test2() throws Exception {
        //1.造文件、造流
        File file1 = new File("dbcp.txt");
        File file2 = new File("dbcp_gbk.txt");
        FileInputStream fis = new FileInputStream(file1);
        FileOutputStream fos = new FileOutputStream(file2);
        InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
        //2.读写过程
        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            osw.write(cbuf,0,len);
        }
        //3.关闭资源
        isr.close();
        osw.close();
    }
    

打印流

  • IO体系中的打印流PrintStreamPrintWriter提供了一系列重载的print()println()方法,可以用于多种数据类型的输出。

    @Test
    public void test2(){
        PrintStream ps = null;
        try {
            FileOutputStream fos = new FileOutputStream(new File("print.txt"));
            ps = new PrintStream(fos, true);
            if (ps != null) {
                System.setOut(ps);
            }
            for (int i = 0; i <= 255; i++) {
                System.out.print((char) i);
                if (i % 50 == 0) {
                    System.out.println();
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
        }
    }
    

标准输入、输出流

  • System.inSystem.out代表系统标准的输入和输出设备,键盘是默认输入设备,显示器是默认的输出设备。

  • System.in的类型是InputStreamSystem.out的类型是PrintStream

    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);
        while(true){
            System.out.println("请输入相关的字符串:");
            String data = br.readLine();
            if("exit".equalsIgnoreCase(data)){
                System.out.println("程序已经结束!!!");
                break;
            }
            //转换为大写并输出
            String upperCase = data.toUpperCase();
            System.out.println(upperCase);
        }
    }
    

对象流

  • ObjectInputStreamOjbectOutputSteam。用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制,把一个Java对象变为byte[]数组,它可以把一个Java对象写入一个字节流。

  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制,它从一个字节流读取Java对象。

  • 注意一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口。

  • ObjectOutputStreamObjectInputStream不能序列化statictransient修饰的成员变量

    //序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去  使用ObjectOutputStream实现
    @Test
    public void testObjectOutputStream() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            oos.writeObject(new String("北京市"));
            //刷新操作
            oos.flush();
            oos.writeObject(new Person("张三", 23));
            oos.flush();
            oos.writeObject(new Person("李四", 33, 1001, new Account(5000)));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //反序列化:将磁盘文件中的对象还原为内存中的一个java对象  使用ObjectInputStream来实现
    @Test
    public void test2() throws IOException {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));
            Object obj = ois.readObject();
            String str = (String) obj;
            Person p = (Person) ois.readObject();
            Person p1 = (Person) ois.readObject();
            System.out.println(str);
            System.out.println(p);
            System.out.println(p1);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                ois.close();
            }
        }
    }
    

随机存取文件流

  • RandomAccessFile 在IO流体系中是功能最丰富的文件内容访问类,声明在java.io包下,但直接继承于java.lang.Object类。并且实现了DataInputDataOutput这两个接口,意味着既可以读取文件内容,也可以向文件输出数据。

  • RandomAccessFile支持跳到文件任意位置读写数据,它包含一个记录指针,用以标识当前读写处的位置,当程序创建一个新的RandomAccessFile对象时,该对象的文件记录指针对于文件头(也就是0处),当读写n个字节后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile可以自由移动该记录指针。

  • 支持只访问文件的部分内容;可以向已存在的文件后追加内容。

  • 操作文件记录的指针;指定mode参数,该参数指定RandomAccessFile的访问模式:

    指针 作用
    long getFilePointer() 获取文件记录指针的当前位置
    void seek(long pos) 将文件记录指针定位到 pos 位置
    mode参数 作用
    R 只读方式打开指定文件。
    RW 以读取、写入方式打开指定文件,文件不存在则创建。
    RWD 打开以便读取和写入;同步文件内容的更新。
    RWS 打开以便读取和写入;同步文件内容和元数据的更新
    @Test
    public void test3() throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");
        raf1.seek(5);
        StringBuilder builder = new StringBuilder((int)new File("hello.txt").length());
        byte[] bytes = new byte[20];
        int len;
        while ((len = raf1.read(bytes)) != -1){
            builder.append(new String(bytes,0,len));
        }
        //将指针调到角标为5的位置
        raf1.seek(5);
        raf1.write("zxcvb".getBytes());
        raf1.write(builder.toString().getBytes());
        raf1.close();
    }
    

总结

  • 本文先从IO流的简单原理说起:I/O技术用于处理设备之间的数据传输,过渡到IO流中常见的分类,使用图表对比了IO流中常见流的区别与功能,列举了常见的IO流方法,并对这些常用方法列举了简单的代码例子帮助理解。

原创不易,欢迎转载,转载时请注明出处,谢谢!
作者:潇~萧下
原文链接:https://www.cnblogs.com/manongxiao/p/14290796.html

欢迎关注
公众号三筒记简介:分享各种编程知识、excel相关技巧、读书笔记

posted @ 2021-01-17 23:35  潇~萧下  阅读(63)  评论(0编辑  收藏  举报