Java的IO流及其使用

1 IO流概述

1 IO流原理

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

在这里插入图片描述

2 流的分类

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

在这里插入图片描述

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

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

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

	/**
     * 读取一个用 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)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值