文章目录

  • 1 IO流概述
  • 1 IO流原理
  • 2 流的分类
  • 2 流的使用
  • 1 字符流
  • 1.1 FileReader & FileWriter
  • 1.2 BufferedReader & BufferedWriter
  • 1.3 转换流
  • 2 字节流
  • 2.1 FileInputStream & FileOutputStream
  • 2.2 BufferedInputStream & BufferedOutputStream
  • 2.3 打印流
  • 2.4 数据流
  • 2.5 对象流
  • 3 标准输入、输出流
  • 4 随机存取文件流
  • 3 java之NIO
  • 4 关于字符编码


1 IO流概述

1 IO流原理

I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。Java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行。java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

java 文件io流 详解 java中io流详解_System

2 流的分类

  • 按操作数据单位不同分为:字节流(8bit),字符流(16bit)。一般处理文本文件用字符流,图片、视频、音频等非文本其他文件用字节流
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为:节点流,处理流

抽象基类

字节流

字符流

输入流

InputStream

Reader

输出流

OutputStream

Writer

java 文件io流 详解 java中io流详解_java 文件io流 详解_02

1、Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。

2、由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

java 文件io流 详解 java中io流详解_字符流_03

java 文件io流 详解 java中io流详解_System_04

2 流的使用

1 字符流

1.1 FileReader & FileWriter

public void FileReaderTest() {
        // 1 File类的实例化。指明要操作的文件
        File file = new File("src\\main\\resources\\io_stream.txt");
        FileReader fileReader = null;
        try {
            // 2 流的实例化。提供具体的流
            fileReader = new FileReader(file);

            // 3 对数据的处理。读入/写出数据
            int data = fileReader.read();//read() 读取一个字符并返回,读到文件结尾返回-1
            while (data != -1) {
                System.out.print((char) data);
                data = fileReader.read();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4 资源的关闭。
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
public void FileReaderTest1() {
        File file = new File("src\\main\\resources\\io_stream.txt");
        FileReader fileReader = null;
        try {
            fileReader = new FileReader(file);

            char[] cbuf = new char[20];
            int len;
            System.out.println("******************方式 1");
            // read(char[] cbuf) 读取字符存入数组cbuf,返回读入数组中的字符数,读到文件按结尾返回-1
            while ((len = fileReader.read(cbuf)) != -1) {
                for (int i = 0; i < len; i++) {
                    System.out.print(cbuf[i]);
                }
            }

            // System.out.println("******************方式 2");
            // while ((len = fileReader.read(cbuf)) != -1) {
            //     String str = new String(cbuf, 0, len);
            //     System.out.print(str);
            // }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
public void fileWriterTest() {
        try {
            File file = new File("src/main/resources/io_stream1.txt");
            // FileWriter(File file, boolean append) append-true:追加到原有文件末尾;false:先清除原内容,再写入。不写此参数则默认覆盖原文件
            FileWriter fw = new FileWriter(file, false);
            fw.write("Hello World!!!\n");
            fw.write("你好 世界!!!");
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

读写综合测试

public void fileReaderAndWriterTest() throws IOException{
        File srcFile = new File("src/main/resources/io_stream.txt");
        File destFile = new File("src/main/resources/io_stream1.txt");
        FileReader fr = new FileReader(srcFile);
        FileWriter fw = new FileWriter(destFile);
        char[] cbuf = new char[20];
        int len = 0;
        while ((len = fr.read(cbuf)) != -1) {
            fw.write(cbuf, 0, len);
        }
        fr.close();
        fw.close();
    }

1.2 BufferedReader & BufferedWriter

public void bufferedReaderAndBufferedWriterTest() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(new File("src/main/resources/io_stream.txt")));
        BufferedWriter bw = new BufferedWriter(new FileWriter(new File("src/main/resources/io_stream1.txt")));
        char[] cbuf = new char[1024];
        int len;
        // // 方法一
        // while ((len = br.read(cbuf)) != -1) {
        //     bw.write(cbuf, 0, len);
        // }

        // 方法二
        String str;
        while ((str = br.readLine()) != null) {
            // // 方法1
            // bw.write(str + "\n");
            // 方法2
            bw.write(str);
            bw.newLine();
        }

        br.close();
        bw.close();
    }

1.3 转换流

转换流(属于字符流)提供了在字节流和字符流之间的转换。JavaAPI提供了两个转换流:

  • InputStreamReader:将InputStream转换为Reader,即将一个字节输入流转换为字符输入流
  • OutputStreamWriter:将Writer转换为OutputStream,即将一个字符输出流转换为字节输出流

字节流中的数据都是字符时,转成字符流操作更高效。很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。

  • 解码:字节、字节数组 —> 字符数组、字符串 对应于 InputStreamReader
  • 编码:字符数组、字符串 —> 字节、字节数组 对应于 OutputStreamWriter

java 文件io流 详解 java中io流详解_java_05

/**
     * 读取一个用 utf-8 方式编码的文件 io_stream.txt,并将其内容输出到控制台
     */
	public void inputStreamReaderTest() throws IOException {
        FileInputStream fis = new FileInputStream("src/main/resources/io_stream.txt");

        // // 使用系统默认的字符集
        // InputStreamReader isr = new InputStreamReader(fis);
        // 显式指明字符集。保存文件时使用的什么字符集,这里就用什么字符集,否则会出现乱码。utf-8也行,不区分大小写
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");

        char[] cbuf = new char[20];
        int len;
        String str;
        while ((len = isr.read(cbuf)) != -1) {
            str = new String(cbuf, 0, len);
            System.out.print(str);
        }

        isr.close();
    }
/**
     * 读取一个用 utf-8 方式编码的文件 io_stream.txt,并将其内容其以 gbk 方式编码写入到文件 io_stream_gbk 中
     */
    public void inputStreamReaderAndOutputStreamWriterTest() throws IOException {
        FileInputStream fis = new FileInputStream("src/main/resources/io_stream.txt");
        FileOutputStream fos = new FileOutputStream("src/main/resources/io_stream_gbk.txt");

        InputStreamReader isr = new InputStreamReader(fis, "utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");

        char[] cbuf = new char[20];
        int len;
        while ((len = isr.read(cbuf)) != -1) {
            osw.write(cbuf, 0, len);
        }

        isr.close();
        osw.close();
    }

io_stream.txt

Java IO流根据处理数据类型的不同分为字符流和字节流,根据数据流向不同分为输入流和输出流,对输
入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

io_stream_gbk.txt

Java IO�����ݴ����������͵IJ�ͬ��Ϊ�ַ������ֽ�����������������ͬ��Ϊ�������������������
����ֻ�ܽ��ж��������������ֻ�ܽ���д��������������Ҫ���ݴ��������ݵIJ�ͬ���Զ�ʹ�ò�ͬ������

2 字节流

2.1 FileInputStream & FileOutputStream

public void fileInputStreamAndOutputStreamTest() throws IOException {
        File srcFile = new File("src/main/resources/src_picture.jpg");
        File destFile = new File("src/main/resources/dest_picture.jpg");
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
        byte[] bytes = new byte[1024];
        int len;
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, len);
        }
        fis.close();
        fos.close();
    }

2.2 BufferedInputStream & BufferedOutputStream

public void BufferedStreamTest() throws IOException {
        // 1 造文件
        File srcFile = new File("src/main/resources/src_picture.jpg");
        File destFile = new File("src/main/resources/dest_picture2.jpg");
        // 2 造流
            // 2.1 造节点流
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
            // 2.2 造缓冲流(处理流)
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        // 3 数据处理
        byte[] buf = new byte[1024];
        int len;
        while ((len = bis.read(buf)) != -1) {
            bos.write(buf, 0, len);
        }
        // 4 关闭资源
        bis.close();
        bos.close();
        // 当关闭外层流后,内层流会自动关闭。可省略内层流的关闭操作
        // fis.close();
        // fos.close();

    }

2.3 打印流

实现将基本数据类型的数据格式转化为字符串输出。打印流:PrintStream和PrintWriter

  • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
  • PrintStream和PrintWriter的输出不会抛出IOException异常
  • PrintStream和PrintWriter有自动flush功能
  • PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用PrintWriter类。
  • System.out返回的是PrintStream的实例
/**
     * 使用打印流打印ascii字符,并将字符写入文件 ascii.txt
     */
    public void pringStreamTest() throws IOException {
        FileOutputStream fos = new FileOutputStream("src/main/resources/ascii.txt");
        // 创建打印输出流,设置自动刷新模式(写入换行符或字符'\n'时都会刷新输出缓冲区)
        PrintStream ps = new PrintStream(fos, true);
        // 把标准输出流(控制台输出)改成文件
        if (ps != null) {
            System.setOut(ps);
        }

        // 输出ascii字符
        for (int i = 0; i <= 255; i++) {
            System.out.print((char) i);
            // 每50字符换一行
            if (i % 50 == 0) {
                System.out.println();
            }
        }
    }
!"#$%&'()*+,-./012
3456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcd
efghijklmnopqrstuvwxyz{|}~€‚ƒ„
†‡ˆ‰Š‹ŒŽ‘’“”•–
—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈ
ÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùú
ûüýþÿ

2.4 数据流

为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。数据流有两个类(用于读取和写出基本数据类型、String类的数据):DatalnputStream和DataOutputStream,它们分别“套接”在InputStream和OutputStream子类的流上。

DatalnputStream中的方法

DataoutputStream中的方法

boolean readBoolean()

boolean writeBoolean()

byte readByte()

byte writeByte()

char readChar()

char writeChar()

float readFloat()

float writeFloat()

double readDouble()

double writeDouble()

short readShort()

short writeShort()

long readLong()

long writeLong()

int readlnt()

int writelnt()

String readUTF()

String writeUTF()

void readFully(byte[] b)

void writeFully(byte[] b)

/**
     * 将几个java基本类型的数据写入到文件中,再从文件中读入到内存,保存到变量中
     * 注意:操作中数据读入的顺序必须与写入的顺序一致
     */
    public void dataStreamTest() throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/main/resources/data.txt"));

        dos.writeUTF("陈胜");
        dos.flush();//刷新操作,将内存缓冲区中的数据写入文件
        dos.writeInt(33);
        dos.flush();
        dos.writeChar('m');
        dos.flush();
        dos.close();

        DataInputStream dis = new DataInputStream(new FileInputStream("src/main/resources/data.txt"));
        String name = dis.readUTF();
        int age = dis.readInt();
        char sex = dis.readChar();
        System.out.println(name + ", " + age + ", " + sex);
        dis.close();
    }

2.5 对象流

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

  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
  • 反序列化:用ObjectlnputStream类读取基本类型数据或对象的机制

ObjectOutputStream和ObjectlnputStream不能序列化static和transient修饰的成员变量。

对象的序列化

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程获取了这种二进制流,就可以恢复成原来的Java对象
  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
  • 序列化是RMl(Remote Method Invoke-远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础
  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现Serializable、Externalizable两个接口之一。否则,会抛出NotSerializableException异常
import java.io.*;
import java.util.ArrayList;

class Person implements Serializable {
    private static final long serialVersionUID = 3105998660576699470L;

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略Getter、Setter、toString
}
public class ObjectStreamTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String str = new String("你好,我是汤姆!");
        Person jack = new Person("jack", 33);
        ArrayList<Person> persons = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            persons.add(new Person(String.valueOf(i + 1), (i + 1) * 10));
        }

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/main/resources/obj_stream.dat"));
        oos.writeObject(str);
        oos.writeObject(jack);
        oos.writeObject(persons);
        oos.flush();
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/main/resources/obj_stream.dat"));
        String str1 = (String) ois.readObject();
        Person jack1 = (Person) ois.readObject();
        ArrayList<Person> persons1 = (ArrayList<Person>) ois.readObject();
        System.out.println(str1);
        System.out.println(jack1);
        System.out.println(persons1);
    }
}
你好,我是汤姆!
Person{name='jack', age=33}
[Person{name='1', age=10}, Person{name='2', age=20}, Person{name='3', age=30}, Person{name='4', age=40}]

Process finished with exit code 0

关于seaialVersionUID

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态常量:

  • private static final long serialVersionUID;
  • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
  • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,显式声明。

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

3 标准输入、输出流

System.in和System.out分别代表了系统标准的输入和输出设备默认输入设备是:建盘,输出设备是:显示器。

  • System.in的类型是InputStream
  • System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream的子类

重定向:通过System类的setln,setOut方法对默认设备进行改变:

  • public static void setin(InputStream in)
  • public static void setOut(PrintStream out)
/**
     * 使用System.in实现:从键盘输入字符,要求将读取到的整行字符事转成大写输出。然后继续进行输入操
     * 作,直至当输入“e”或"exit”时,退出程序。
     *
     * 分析:
     *      由于System.in的类型是InputStream,属于字节流,而我们需要的时字符流,因此需要使用转
     *      换流InputStreamReader将其转换为字符流,然后使用BufferedReader类的readLine()
     *      方法,将字符串转换成大写
     */
    public void standardStreamTest() throws IOException {
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);

        String inputStr;
        while (true) {
            System.out.println("请输入字符串,输入 e 或 exit 结束程序:");
            inputStr = br.readLine();
            if ("e".equals(inputStr) || "exit".equals(inputStr)) {
                System.out.println("程序结束……");
                break;
            }

            inputStr = inputStr.toUpperCase();
            System.out.println(inputStr);
        }
    }

4 随机存取文件流

RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了Datalnput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件:

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

RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile类对象可以自由移动记录指针:

  • long getFilePointer():获取文件记录指针的当前位置
  • void seek(long pos):将文件记录指针定位到pos位置

构造函数

public RandomAccessFile(File file,String mode)

public RandomAccessFile(String name,String mode)

创建 RandomAccessFile类实例需要指定一个mode参数,该参数指定RaidomAccessFile的访问模式:

  • r:以只读方式打开
  • rw:打开以便读取和写入
  • rwd:打开以便读取和写入;同步文件内容的更新
  • rws:打开以便读取和写入;同步文件内容和元数据的更新

说明

1、如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。

2、JDK 1.6上面写的每次write数据时,“rw"模式,数据不会立即写到硬盘中;而“rwd”,数据会被立即写入硬盘。如果写数据过程发生异常,“rwd"模式中已被write的数据被保存到硬盘而rw"则全部丢失。

public void randomAccessFileTest1() throws IOException{
        RandomAccessFile inRaf = new RandomAccessFile(new File("src/main/resources/src_picture.jpg"), "r");
        RandomAccessFile outRaf = new RandomAccessFile("src/main/resources/dest_pict.jpg", "rw");

        byte[] buf = new byte[1024];
        int len;
        while ((len = inRaf.read(buf)) != -1) {
            outRaf.write(buf, 0, len);
        }
        
        inRaf.close();
        outRaf.close();
    }
/**
     * 有一个文件 demo.txt,内容如下左侧所示:
     *              
     *  abcdefg             abcde123456789fg
     *  hijklmn             hijklmn
     *  opq rst             opq rst
     *  uvw xyz             uvw xyz    
     * 要求从原文件第5字节处后面插入数字 123456789。
     */
    public void randomAccessFileTest2() throws IOException{
        RandomAccessFile outRaf = new RandomAccessFile("src/main/resources/demo.txt", "rw");
        outRaf.seek(5);


        byte[] buf = new byte[20];
        int len;

        // // 方法一:使用StringBuilder
        // StringBuilder builder = new StringBuilder((int) new File("src/main/resources/demo.txt").length());
        // while ((len = outRaf.read(buf)) != -1) {
        //     // 先保存第5字节后面的文件内容,否则原内容会被覆盖
        //     builder.append(new String(buf, 0, len));
        // }
        // outRaf.seek(5);
        // outRaf.write("123456789".getBytes());
        // outRaf.write(builder.toString().getBytes());

        // 方法二:使用ByteArrayOutputStream
        ByteArrayOutputStream baos = new ByteArrayOutputStream((int) new File("src/main/resources/demo.txt").length());
        while ((len = outRaf.read(buf)) != -1) {
            baos.write(buf, 0, len);
        }
        outRaf.seek(5);
        outRaf.write("123456789".getBytes());
        outRaf.write(baos.toByteArray());

        outRaf.close();
    }

3 java之NIO

Java NIO(New IO,Non-Blocking IO)是从Java1.4版本开始引入的一套新的IO API,可以替代标准的Java lOAPI。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。JavaAPl中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO:

|----java.nio.channels.Channel
	|----FileChannel:处理本地文件
	|----SocketChannel:TCP网络编程的客户端的Channel
	|----ServerSocketChannel:TCP网络编程的服务器端的Channel
	|----DatagramChannel:UDP网络编程中发送端和接收端的Channel

NIO.2

随着JDK7的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持以至于我们称他们为NIO.2。因为NIO提供的一些功能,NIO已经成为文件处理中越来越重要的部分。

4 关于字符编码

编码表的由来

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。常见的编码表如下:

编码方式

说明

ASCIl

美国标准信息校换码。用一个字节的7位可以表示

ISO8859-1

拉丁码表,欧洲码表。用一个字节的8位表示

GB2312

中国的中文编码表。最多两个字节编码所有字符

GBK

中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码

Unicode

国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。

UTF-8

变长的编码方式,可用1-4个字节来表示一个字符。

关于Unicode

补充:字符编码
Unicode不完美,这里就有三个问题,一个是,我们已经知道,英文字母只用一个字节表示就够了,第二个问题是如何才能区别Unicode和ASCIl?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第三个,如果和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。Unicode在很长一段时间内无法推广,直到互联网的出现。

面向传输的众多UTF(UCS|Transfefr Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。

关于ANSI

ANSI:美国国家标准学会(AMERICAN NATIONAL STANDARDS INSTITUTE)

java 文件io流 详解 java中io流详解_System_06