文章目录

  • 一、IO流概念
  • 二、IO流的分类
  • 1、输入流和输出流
  • 2、字节流和字符流
  • 3、节点流和处理流
  • 三、IO流的类结构
  • 注意:
  • 〇、File类
  • ①、字节输入流InputStream
  • 1.1、FileInputStream
  • 1.2、DataInputStream
  • 1.3、PushBackInputStream
  • ②、字节输出流OutputStream
  • 2.1、FileOutputStream
  • 2.2、ByteArrayOutputStream
  • 2.3、PipedOutputStream
  • 2.4、DataOutputSteam
  • 2.5、ZipOutputStream
  • 2.5.1、压缩单个文件
  • 2.5.2、一次性压缩多个文件
  • 2.5.3、ZipFile类展示
  • 2.5.4、解压缩文件(压缩文件中只有一个文件的情况)
  • 2.5.5、解压缩文件(一个压缩文件中包含多个文件的情况)
  • ③、几个特殊的输入流类
  • 3.1、LineNumberInputStream
  • 3.2、PushbackInputStream
  • ~~3.3、StringBufferInputStream~~
  • 3.4、SequenceInputStream
  • 3.5、PrintStream
  • ④、字符输入流Reader
  • 4.1、FileReader
  • 4.2、BufferedReader
  • ⑤、字符输出流Writer
  • 5.1、FileWriter
  • ⑥、字符流与字节流转换
  • 转换流的特点:
  • 何时使用转换流?
  • 具体的对象体现:
  • 字节流和字符流转换实例:
  • 四、Java IO流的高级概念
  • ①、编码问题
  • 1、取得本地的默认编码
  • 2、乱码的产生
  • 3、解决方法
  • ②、对象的序列化
  • 1、实现具有序列化能力的类
  • 2、序列化一个对象:ObjectOutputStream
  • 3、反序列化:ObjectInputStream
  • 4、使用Externalizable来定制序列化和反序列化操作
  • 5、使用transient关键字定制序列化和反序列化操作
  • 6、序列化一组对象


一、IO流概念

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

二、IO流的分类

1、输入流和输出流

根据数据流向不同分为:输入流和输出流。

  • 输入流:只能从中读取数据,而不能向其写入数据。
  • 输出流:只能向其写入数据,而不能从中读取数据。

2、字节流和字符流

处理数据单位不同分为:字节流和字符流。

(1)读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

(2)处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

(3)字节流在操作的时候本身是不会用到缓冲区的,是对文件本身直接操作的;而字符流在操作的时候是会用到缓冲区的,是通过缓冲区来操作文件的。

结论:字符流本质其实就是基于字节流读取时。优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。

3、节点流和处理流

按照流的角色来分,可以分为:节点流和处理流。

  • 节点流:可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,节点流也被成为低级流。
  • 处理流:处理流是对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能,处理流也被称为高级流。

当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入/输出节点连接。使用处理流的一个明显好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际所访问的数据源也相应地发生变化。

实际上,Java使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能。

三、IO流的类结构

根据流的流向以及操作的数据单元不同,将流分为了四种类型,每种类型对应一种抽象基类

四种基类下,对应不同的实现类,具有不同的特性。在这些实现类中,又可以分为节点流和处理流。

Java IO 流的4个抽象基类:

字节流

字符流

输入流

InputStream

Reader

输出流

OutputStream

Writer

下面就是整个由着四大基类支撑下,整个IO流的框架图:

java中的i o流 java中io流详解_字节流

注意:

在执行完流操作后,要调用close()方法来关闭输入流,因为程序里打开的IO资源不属于内存资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。

使用Java的IO流执行输出时,也不要忘记关闭输出流,关闭输出流除了可以保证流的物理资源被回收之外,还能将输出流缓冲区的数据flush到物理节点里(因为在执行close()方法之前,自动执行输出流的flush()方法)

〇、File类

File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。

  • 创建一个文件
class Test{
   public static void main(String[] args) {
       File f=new File("D:\\hello.txt");
       try{
           f.createNewFile();
       }catch (Exception e) {
           e.printStackTrace();
       }
    }
}
  • File类的两个常量
class Test{
   public static void main(String[] args) {
       System.out.println(File.separator);		//Windows下:\
       System.out.println(File.pathSeparator);	//Windows下:;
    }
}

**注意:**为什么不推荐在windows下直接使用\进行分割,因为在Windows下路径分隔符是 \ ,但是Linux下的路径分隔符就不是 \ ,所以,要想使得我们的代码跨平台,更加健壮,推荐大家都采用这两个常量来替代 \ 。

  • 删除一个文件(或者文件夹)
class Test{
   public static void main(String[] args) {
       String fileName="D:" + File.separator + "hello.txt";
       File file = new File(fileName);
       if(f.exists()){
           file.delete();
       }else{
           System.out.println("文件不存在");
       }
    }
}
  • 创建一个文件夹
class Test{
   public static void main(String[] args) {
       String fileName = "D:" + File.separator + "hello";
       File file = new File(fileName);
       file.mkdir();
    }
}
  • 列出目录下的所有文件
class Test{
   public static void main(String[] args) {
       String fileName="D:" + File.separator;
       File file = new File(fileName);
       String[] str = file.list();
       for (int i = 0; i < str.length; i++) {
           System.out.println(str[i]);
       }
    }
}

**注意:**使用list返回的是String类型的数组,并且列出的不是完整路径,如果想列出完整路径的话,需要使用listFiles,它返回的是File类型的数组。

  • 列出指定目录的全部文件(包括隐藏文件)
class Test{
   public static void main(String[] args) {
       String fileName = "D:" + File.separator;
       File file = new File(fileName);
       File[] str = file.listFiles();
       for (int i = 0; i < str.length; i++) {
           System.out.println(str[i]);
       }
    }
}
  • 判断一个指定的路径是否为目录
class Test{
   public static void main(String[] args) {
       String fileName = "D:" + File.separator;
       File file = new File(fileName);
       if(file.isDirectory()){
           System.out.println("YES");
       }else{
           System.out.println("NO");
       }
    }
}
  • 递归搜索指定目录的全部内容,包括文件和文件夹
public class Test {
    private static int count = 0;

    public static void main(String[] args) throws IOException {
        String fileName = "D:" + File.separator;
        File file = new File(fileName);
        print(file);
    }

    public static void print(File file) {
        if (file != null) {
            if (file.isDirectory()) {
                ++count;
                File[] fileArray = file.listFiles();
                if (fileArray != null) {
                    for (File temp : fileArray) {
                        print(temp);
                    }
                }
            }
            else {
                for(int i = 0; i < count; i++){
                    System.out.print("-");
                }
                System.out.println(file.getName());
            }
        }
    }

}

①、字节输入流InputStream

InputStream 是所有的输入字节流的父类,它是一个抽象类,主要包含三个方法

方法

方法描述

int read()

读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。

int read(byte[] buffer)

读取一系列字节并存储到一个数组buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回-1。

int read(byte[] buffer, int off, int len)

读取length个字节并存储到一个字节数组buffer,从off位置开始存,最多len, 返回实际读取的字节数,如果读取前以到输入流的末尾返回-1。

  • ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。
  • PipedInputStream 是从与其它线程共用的管道中读取数据。
  • ObjectInputStream 和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。意思是FileInputStream类可以通过一个String路径名创建一个对象,FileInputStream(String name)。而DataInputStream必须装饰一个类才能返回一个对象,DataInputStream(InputStream in)。

1.1、FileInputStream

/**
* 字节流
* 读文件
**/
class FileInputStreamTest{
   public static void main(String[] args) throws IOException {
       String fileName = "D:" + File.separator + "hello.txt";
       File file = new File(fileName);
       InputStream in = new FileInputStream(file);
       byte[] b = new byte[1024];
       int count = 0;
       int temp = 0;
       while((temp=in.read())!=(-1)){
           b[count++]=(byte)temp;
       }
       in.close();
       System.out.println(new String(b));
    }
}

1.2、DataInputStream

数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。

public class DataInputStreamTest{
   public static void main(String[] args) throws IOException{
       File file = new File("d:" + File.separator + "hello.txt");
       DataInputStream input = new DataInputStream(new FileInputStream(file));
       char[] ch = new char[10];
       int count = 0;
       char temp;
       while((temp = input.readChar()) != 'C'){
           ch[count++] = temp;
       }
       System.out.println(ch);
    }
}

1.3、PushBackInputStream

回退流

/**
 * 回退流操作
 * */
public class PushBackInputStreamTest{
    public static void main(String[] args) throwsIOException{
       String str = "hello,WhoisXxq";
       //结果:hello(回退,)WhoisXxq
       PushbackInputStream push = null;
       ByteArrayInputStream bat = null;
       bat = new ByteArrayInputStream(str.getBytes());
       push = new PushbackInputStream(bat);
       int temp = 0;
       while((temp = push.read()) != -1){
           if(temp == ','){
                push.unread(temp);
                temp = push.read();
                System.out.print("(回退" +(char) temp + ")");
           }else{
                System.out.print((char) temp);
           }
       }
    }
}

②、字节输出流OutputStream

OutputStream 是所有的输出字节流的父类,它是一个抽象类,主要包含如下四个方法:

方法

方法描述

void write(int b)

向输出流中写入一个字节数据,该字节数据为参数b的低8位。

void write(byte[] b)

将一个字节类型的数组中的数据写入输出流。

void write(byte[] b, int off, int len)

将一个字节类型的数组中的从指定位置(off)开始的,len个字节写入到输出流。

void flush()

将输出流中缓冲的数据全部写出到目的地。

  • ByteArrayOutputStream、FileOutputStream是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。
  • PipedOutputStream 是向与其它线程共用的管道中写入数据,
  • ObjectOutputStream 和所有FilterOutputStream的子类都是装饰流。具体例子跟InputStream是对应的。

2.1、FileOutputStream

/**
 * 字节流
 * 向文件中追加新内容:
 * */
import java.io.*;
class FileOutputStreamTest{
   public static void main(String[] args) throws IOException {
       String fileName = "D:" + File.separator + "hello.txt";
       File file = new File(fileName);
       OutputStream out = new FileOutputStream(file, true);//true表示追加模式,否则为覆盖
       String str = "WhoisXxq";
       //String str = "\r\nWhoisXxq"; 可以换行
       byte[] b = str.getBytes();
       for (int i = 0; i < b.length; i++) {
           out.write(b[i]);
       }
       out.close();
    }
}

2.2、ByteArrayOutputStream

/**
 * 使用内存操作流将一个大写字母转化为小写字母
 * */
import java.io.*;
class ByteArrayOutputStreamTest{
   public static void main(String[] args) throws IOException {
       String str="Hello world!";
       ByteArrayInputStream input=new ByteArrayInputStream(str.getBytes());
       ByteArrayOutputStream output=new ByteArrayOutputStream();
       int temp=0;
       while((temp=input.read())!=-1){
           char ch=(char)temp;
           output.write(Character.toLowerCase(ch));
       }
       String outStr=output.toString();
       input.close();
       output.close();
       System.out.println(outStr);
    }
}

2.3、PipedOutputStream

/**
 * 验证管道流
 */

import java.io.*;

/**
 * 消息发送类
 */
class Send implements Runnable {
    private PipedOutputStream out = null;

    public Send() {
        out = new PipedOutputStream();
    }

    public PipedOutputStream getOut() {
        return this.out;
    }

    public void run() {
        String message = "hello world!";
        try {
            out.write(message.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
 * 接受消息类
 */
class Recive implements Runnable {
    private PipedInputStream input = null;

    public Recive() {
        this.input = new PipedInputStream();
    }

    public PipedInputStream getInput() {
        return this.input;
    }

    public void run() {
        byte[] b = new byte[1024];
        int len = 0;
        try {
            len = this.input.read(b);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("接收的内容为: " + (new String(b, 0, len)));
    }
}

/**
 * 测试类
 */
class Test {
    public static void main(String[] args) throws IOException {
        Send send = new Send();
        Recive recive = new Recive();
        try {
			//管道连接
            send.getOut().connect(recive.getInput());
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(send).start();
        new Thread(recive).start();
    }
}

2.4、DataOutputSteam

public class DataOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("d:" + File.separator + "hello.txt");
        char[] ch = {'A', 'B', 'C'};
        DataOutputStream out = null;
        out = new DataOutputStream(new FileOutputStream(file));
        for (char temp : ch) {
            out.writeChar(temp);
        }
        out.close();
    }
}

2.5、ZipOutputStream

先看一下它的继承关系:

java.lang.Object ← java.io.OutputStream ← java.io.FilterOutputStream ← java.util.zip.DeflaterOutputStream ← java.util.zip.ZipOutputStream

  • ZipEntry :表示压缩文件的条目 (就相当与java文件中的directory目录一样)
  • putNextEntry:开始编写新的ZIP文件条目并将流定位到条目数据的开头(换一个新的开始从头写).如果仍然有效,则关闭当前的目录,如果没有为目录指定相关的压缩方法,则使用默认的压缩方法。如果没有设置时间,则默认使用当前时间 (就是将条目放到zip包中)
2.5.1、压缩单个文件
//使用ZipOutputStream压缩单个文件
public class ZipOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("d:" + File.separator + "hello.txt");
        File zipFile = new File("d:" + File.separator + "hello.zip");
        InputStream input = new FileInputStream(file);
        ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(
                zipFile));
        zipOut.putNextEntry(new ZipEntry(file.getName()));
        // 设置注释
        zipOut.setComment("hello");
        int temp = 0;
        while ((temp = input.read()) != -1) {
            zipOut.write(temp);
        }
        input.close();
        zipOut.close();
    }
}
2.5.2、一次性压缩多个文件
/**
 * 一次性压缩多个文件
 */
public class ZipOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        // 要被压缩的文件夹
        File file = new File("d:" + File.separator + "temp");
        File zipFile = new File("d:" + File.separator + "zipFile.zip");
        InputStream input = null;
        ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(
                zipFile));
        zipOut.setComment("hello");
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; ++i) {
                input = new FileInputStream(files[i]);
                zipOut.putNextEntry(new ZipEntry(file.getName()
                        + File.separator + files[i].getName()));
                int temp = 0;
                while ((temp = input.read()) != -1) {
                    zipOut.write(temp);
                }
                input.close();
            }
        }
        zipOut.close();
    }
}
2.5.3、ZipFile类展示
/**
 * ZipFile演示
 */
public class ZipFileDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("d:" + File.separator + "hello.zip");
        ZipFile zipFile = new ZipFile(file);
        System.out.println("压缩文件的名称为:" + zipFile.getName());
    }
}
2.5.4、解压缩文件(压缩文件中只有一个文件的情况)
/**
 * 解压缩文件(压缩文件中只有一个文件的情况)
 */
public class ZipFileDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("d:" + File.separator + "hello.zip");
        File outFile = new File("d:" + File.separator + "unZipFile.txt");
        
        ZipFile zipFile = new ZipFile(file);
        ZipEntry entry = zipFile.getEntry("hello.txt");
        
        InputStream input = zipFile.getInputStream(entry);
        OutputStream output = new FileOutputStream(outFile);
        
        int temp = 0;
        while ((temp = input.read()) != -1) {
            output.write(temp);
        }
        
        input.close();
        output.close();
    }
}
2.5.5、解压缩文件(一个压缩文件中包含多个文件的情况)
/**
 * 解压缩一个压缩文件中包含多个文件的情况
 */
public class ZipFileDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("d:" + File.separator + "zipFile.zip");
        File outFile = null;
        
        ZipFile zipFile = new ZipFile(file);
        ZipInputStream zipInput = new ZipInputStream(new FileInputStream(file));
        ZipEntry entry = null;
        
        InputStream input = null;
        OutputStream output = null;
        
        while ((entry = zipInput.getNextEntry()) != null) {
            System.out.println("解压缩" + entry.getName() + "文件");
            
            outFile = new File("d:" + File.separator + entry.getName());
            if (!outFile.getParentFile().exists()) {
                outFile.getParentFile().mkdir();
            }
            if (!outFile.exists()) {
                outFile.createNewFile();
            }
            
            input = zipFile.getInputStream(entry);
            output = new FileOutputStream(outFile);
            
            int temp = 0;
            while ((temp = input.read()) != -1) {
                output.write(temp);
            }
            input.close();
            output.close();
        }
    }
}

③、几个特殊的输入流类

3.1、LineNumberInputStream

主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由该类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。

3.2、PushbackInputStream

其功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分与BufferedOutputStream 几乎实现相近的功能。

3.3、StringBufferInputStream

已经被Deprecated,本身就不应该出现在InputStream部分,主要因为String 应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。

3.4、SequenceInputStream

可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO 包中去除,还完全不影响IO 包的结构,却让其更“纯洁” —— 纯洁的Decorator 模式。

/**
 * 将两个文本文件合并为另外一个文本文件
 * */
public class SequenceInputStreamDemo {
    public static voidmain(String[] args) throws IOException {
        File file1 = new File("d:" + File.separator + "hello1.txt");
        File file2 = new File("d:" + File.separator + "hello2.txt");
        File file3 = new File("d:" + File.separator + "hello.txt");
        
        InputStream input1 = new FileInputStream(file1);
        InputStream input2 = new FileInputStream(file2);
        OutputStream output = new FileOutputStream(file3);
        
        // 合并流
        SequenceInputStreamsis = new SequenceInputStream(input1, input2);
        
        int temp = 0;
        while ((temp = sis.read()) != -1) {
            output.write(temp);
        }
        
        input1.close();
        input2.close();
        output.close();
        sis.close();
    }
}

3.5、PrintStream

也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO 包!System.errSystem.out 就是PrintStream 的实例!

  • 使用PrintStream进行输出
/**
 * 使用PrintStream进行输出
 * */
import java.io.*;
  
class Test {
    public static void main(String[] args) throws IOException {
        PrintStream print = new PrintStream(new FileOutputStream(new File("d:" + File.separator + "hello.txt")));
        print.println(true);
        print.println("WhoisXxq");
        print.close();
    }
}
  • 使用PrintStream进行格式化输出
/**
 * 使用PrintStream进行输出
 * 并进行格式化
 * */
import java.io.*;
class Test {
    public static void main(String[] args) throws IOException {
        PrintStream print = new PrintStream(new FileOutputStream(newFile("d:" + File.separator + "hello.txt")));
        String name = "WhoisXxq";
        int age = 20;
        print.printf("姓名:%s. 年龄:%d.", name, age);
        print.close();
    }
}
  • 使用OutputStream向控制台上输出内容
/**
 * 使用OutputStream向屏幕上输出内容
 * */
import java.io.*;
class Test {
   public static void main(String[] args) throws IOException {
       OutputStream out = System.out;
       try{
           out.write("hello".getBytes());
       }catch (Exception e) {
           e.printStackTrace();
       }
       try{
           out.close();
       }catch (Exception e) {
           e.printStackTrace();
       }
    }
}
  • System.out输出重定向
/**
 * 为System.out.println()重定向输出
 * */
public class SystemDemo{
   public static void main(String[] args){
       // 此刻直接输出到屏幕
       System.out.println("hello");
       File file = new File("d:" + File.separator +"hello.txt");
       try{
           System.setOut(new PrintStream(new FileOutputStream(file)));
       }catch(FileNotFoundException e){
           e.printStackTrace();
       }
       System.out.println("这些内容在文件中才能看到哦!");
    }
}
  • 使用System.err重定向
/**
 * System.err重定向这个例子也提示我们可以使用这种方法保存错误信息
 * */
public class systemErr{
   public static void main(String[] args){
       File file = new File("d:" + File.separator + "hello.txt");
       System.err.println("这些在控制台输出");
       try{
           System.setErr(new PrintStream(new FileOutputStream(file)));
       }catch(FileNotFoundException e){
           e.printStackTrace();
       }
       System.err.println("这些在文件中才能看到哦!");
    }
}
  • System.in重定向
/**
 * System.in重定向
 * */
public class systemIn{
   public static void main(String[] args){
       File file = new File("d:" + File.separator +"hello.txt");
       if(!file.exists()){
           return;
       }else{
           try{
                System.setIn(new FileInputStream(file));
           }catch(FileNotFoundException e){
                e.printStackTrace();
           }
           byte[] bytes = new byte[1024];
           int len = 0;
           try{
                len = System.in.read(bytes);
           }catch(IOException e){
                e.printStackTrace();
           }
           System.out.println("读入的内容为:" + new String(bytes, 0, len));
       }
    }
}

④、字符输入流Reader

Reader 是所有的输入字符流的父类,它是一个抽象类,主要包含三个方法:

方法

方法描述

int read()

读取一个字符并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。

int read(char[] cbuf)

读取一系列字符并存储到一个数组buffer,返回实际读取的字符数,如果读取前已到输入流的末尾返回-1。

int read(char[] cbuf, int off, int len)

读取length个字符,并存储到一个数组buffer,从off位置开始存,最多读取len,返回实际读取的字符数,如果读取前以到输入流的末尾返回-1。

  • 对比InputStream和Reader所提供的方法,就不难发现两个基类的功能基本一样的,只不过读取的数据单元不同。
  • CharReader、StringReader是两种基本的介质流,它们分别将Char 数组、String中读取数据。
  • PipedReader 是从与其它线程共用的管道中读取数据。
  • BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
  • FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
  • InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。

4.1、FileReader

/**
 * 字符流
 * 从文件中读出内容
 * */
import java.io.*;

class FileReaderTest {
    public static void main(String[] args) throws IOException {
        String fileName = "D:" + File.separator + "hello.txt";
        File file = new File(fileName);
        char[] ch = new char[100];
        Reader read = new FileReader(file);
        int temp = 0;
        int count = 0;
        while ((temp = read.read()) != (-1)) {
            ch[count++] = (char) temp;
        }
        read.close();
        System.out.println("内容为" + new String(ch, 0, count));
    }
}

4.2、BufferedReader

  • 注意:BufferedReader只能接受字符流的缓冲区,因为每一个中文需要占据两个字节,所以需要将System.in这个字节输入流变为字符输入流,采用:
    BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
/**
 * 使用缓冲区从键盘上读入内容
 * */
public class BufferedReaderDemo{
   public static void main(String[] args){
       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String str = null;
       System.out.println("请输入内容:");
       try{
           str = reader.readLine();
       }catch(IOException e){
           e.printStackTrace();
       }
       System.out.println("你输入的内容是: " + str);
    }
}

⑤、字符输出流Writer

Writer 是所有的输出字符流的父类,它是一个抽象类,主要包含如下六个方法:

方法

方法描述

void write(int c)

向输出流中写入一个字符数据,该字节数据为参数b的低16位。

void write(char[] cbuf)

将一个字符类型的数组中的数据写入输出流。

void write(char[] cbuf, int offset, int length)

将一个字符类型的数组中的从指定位置(offset)开始的,length个字符写入到输出流。

void write(String string)

将一个字符串中的字符写入到输出流。

void write(String string, int offset, int length)

将一个字符串从offset开始的length个字符写入到输出流

void flush()

将输出流中缓冲的数据全部写出到目的地。

  • CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。
  • PipedWriter 是向与其它线程共用的管道中写入数据,
  • BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
  • PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。
  • OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究 一 SourceCode)。功能和使用和OutputStream 极其类似。

5.1、FileWriter

/**
 * 字符流
 * 写入数据
 * */
import java.io.*;
class FileWriterTest{
   public static void main(String[] args) throws IOException {
       String fileName="D:" + File.separator + "hello.txt";
       File file = new File(fileName);
       Writer out =new FileWriter(file);
       
       String str="hello";
       out.write(str);
       
       out.close();
    }
}

**注意:**这个例子上之前的例子没什么区别,只是你可以直接输入字符串,而不需要你将字符串转化为字节数组。当你如果想问文件中追加内容的时候,可以使用将上面的声明out的哪一行换为:

Writer out =new FileWriter(f,true);

⑥、字符流与字节流转换

转换流的特点:

(1)其是字符流和字节流之间的桥梁

(2)可对读取到的字节数据经过指定编码转换成字符

(3)可对读取到的字符数据经过指定编码转换成字节

何时使用转换流?

当字节和字符之间有转换动作时;

流操作的数据需要编码或解码时。

具体的对象体现:

InputStreamReader:字节到字符的桥梁

OutputStreamWriter:字符到字节的桥梁

这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。

字节流和字符流转换实例:

  • 将字节输入流转换为字符输入流
/**
 * 将字节输入流变为字符输入流
 * */
import java.io.*;
class InputStreamReaderTest{
   public static void main(String[] args) throws IOException {
       String fileName = "d:" + File.separator + "hello.txt";
       File file = new File(fileName);
       
       Reader read = new InputStreamReader(new FileInputStream(file));
       
       char[] b = new char[100];
       int len = read.read(b);
       System.out.println(new String(b, 0, len));
       
       read.close();
    }
}
  • 将字节输出流转化为字符输出流
/**
 * 将字节输出流转化为字符输出流
 * */
import java.io.*;
class OutputStreamWriterTest{
   public static void main(String[] args) throws IOException {
       String fileName = "d:" + File.separator + "hello.txt";
       File file = new File(fileName);
       
       Writer out = new OutputStreamWriter(new FileOutputStream(file));
       out.write("hello");
       
       out.close();
    }
}

四、Java IO流的高级概念

①、编码问题

1、取得本地的默认编码

/**
 * 取得本地的默认编码
 * */
public class CharSetDemo{
    public static void main(String[] args){
        System.out.println("系统默认编码为:" + System.getProperty("file.encoding"));
    }
}

2、乱码的产生

public class CharSetDemo{
    public static void main(String[] args) throws IOException{
        File file = new File("d:" + File.separator + "hello.txt");
        OutputStream out = new FileOutputStream(file);
        byte[] bytes = "你好".getBytes("ISO8859-1");
        out.write(bytes);
        out.close();
    }//输出结果为乱码,系统默认编码为GBK,而此处编码为ISO8859-1
}

3、解决方法

1、使用转换流 InputStreamReader 和 OutputStreamWriter 按指定字符集解码文件,可解决这一问题。

2、直接使用转换流的子类FileReader,在创建其实例时给一个Charset值(编码格式)

public class Test {
    public static void main(String[] args) throws IOException {
        File file = new File("d:" + File.separator + "test.txt");
        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
        String s = reader.readLine();
        System.out.println(s);

        /*Charset cs = Charset.forName("UTF-8");
        FileReader reader = new FileReader(file, cs);
        char[] a = new char[100];
        int len = reader.read(a);
        System.out.println(new String(a, 0, len));*/
        
        reader.close();
    }
}

②、对象的序列化

对象序列化就是把一个对象变为二进制数据流的一种方法。

一个类要想被序列化,就行必须实现java.io.Serializable接口。虽然这个接口中没有任何方法,就如同之前的cloneable接口一样。实现了这个接口之后,就表示这个类具有被序列化的能力。

1、实现具有序列化能力的类

/**
 * 实现具有序列化能力的类
 * */
import java.io.Serializable;

public class SerializableDemo implements Serializable {
    private String name;
    private int age;

    public SerializableDemo() {

    }

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

    @Override
    public String toString() {
        return "姓名:" + name + "  年龄:" + age;
    }

}

2、序列化一个对象:ObjectOutputStream

import java.io.*;

/**
 * 实现具有序列化能力的类
 */
public class Person implements Serializable {
    private String name;
    private int age;
    
    public Person() {
    }

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

    @Override
    public String toString() {
        return "姓名:" + name + "  年龄:" + age;
    }
}

/**
 * 示范ObjectOutputStream
 */
public class ObjectOutputStreamDemo {
    public static voidmain(String[] args) throws IOException {
        File file = newFile("d:" + File.separator + "hello.txt");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(new Person("xxq", 20));	
        oos.close();
    }
}

当我们查看产生的hello.txt的时候,看到的是乱码,因为存入的是二进制。

3、反序列化:ObjectInputStream

/**
 * ObjectInputStream示范
 */
public class ObjectInputStreamDemo {
    public static voidmain(String[] args) throws Exception {
        File file = new File("d:" + File.separator + "hello.txt");
        ObjectInputStream input = new ObjectInputStream(new FileInputStream(file));
        Object obj = input.readObject();
        input.close();
        System.out.println(obj); //运行结果:姓名:xxq 年龄:20
    }
}

**注意:**被Serializable接口声明的类的对象的属性都将被序列化但是如果想自定义序列化的内容的时候,就需要实现Externalizable接口。

当一个类要使用Externalizable这个接口的时候,这个类中必须要有一个无参的构造函数,如果没有的话,在构造的时候会产生异常,这是因为在反序列话的时候会默认调用无参的构造函数。

4、使用Externalizable来定制序列化和反序列化操作

import java.io.*;

/**
 * 序列化和反序列化的操作
 */
public class ExternalizableDemo {
    public static voidmain(String[] args) throws Exception {
        ser(); // 序列化
        dser(); // 反序列话
    }

    public static void ser() throws Exception {
        File file = newFile("d:" + File.separator + "hello.txt");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(newPerson("xxq", 20));
        out.close();
    }

    public static void dser() throws Exception {
        File file = newFile("d:" + File.separator + "hello.txt");
        ObjectInputStreaminput = new ObjectInputStream(new FileInputStream(file));
        Object obj = input.readObject();
        input.close();
        System.out.println(obj);	//运行结果:姓名:xxq 年龄:20
    }
}

class Person implements Externalizable {
    private String name;
    private int age;
    
    public Person() {
    }

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

    @Override
    public String toString() {
        return "姓名:" + name + "  年龄:" + age;
    }

    // 复写这个方法,根据需要可以保存的属性或者具体内容,在序列化的时候使用
    @Override
    public voidwriteExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.name);
        out.writeInt(age);
    }

    // 复写这个方法,根据需要读取内容 反序列话的时候需要
    @Override
    public voidreadExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        this.name = (String) in.readObject();
        this.age = in.readInt();
    }
}

**注意:**Serializable接口实现的操作其实是吧一个对象中的全部属性进行序列化,当然也可以使用我们上使用是Externalizable接口以实现部分属性的序列化,但是这样的操作比较麻烦。

当我们使用Serializable接口实现序列化操作的时候,如果一个对象的某一个属性不想被序列化保存下来,那么我们可以使用transient关键字进行说明

5、使用transient关键字定制序列化和反序列化操作

import java.io.*;

/**
 * 序列化和反序列化的操作
 */
public class serDemo {
    public static voidmain(String[] args) throws Exception {
        ser(); // 序列化
        dser(); // 反序列话
    }

    public static void ser() throws Exception {
        File file = newFile("d:" + File.separator + "hello.txt");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(newPerson1("xxq", 20));
        out.close();
    }

    public static void dser() throws Exception {
        File file = newFile("d:" + File.separator + "hello.txt");
        ObjectInputStreaminput = new ObjectInputStream(new FileInputStream(file));
        Object obj = input.readObject();
        input.close();
        System.out.println(obj);	//运行结果:姓名:null  年龄:20
    }
}

class Person implements Serializable {
    // 注意这里
    private transient name;
    private int age;
    
    public Person() {

    }

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

    @Override
    public String toString() {
        return "姓名:" + name + "  年龄:" + age;
    }
}

6、序列化一组对象

/**
 * 序列化一组对象
 */
public class SerDemo {
    public static void main(String[] args) throws Exception {
        Student[] stu = {new Student("hello", 20), new Student("world", 30),
                new Student("xxq", 40)};
        ser(stu);
        Object[] obj = dser();
        for (int i = 0; i < obj.length; ++i) {
            Student s = (Student) obj[i];
            System.out.println(s);
        }
        /*
        	运行结果:
        	姓名:hello  年龄:20
			姓名:world  年龄:30
			姓名:xxq  年龄:40
        */
    }

    // 序列化
    public static void ser(Object[] obj) throws Exception {
        File file = new File("d:" + File.separator + "hello.txt");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(obj);
        out.close();
    }

    // 反序列化
    public static Object[] dser() throws Exception {
        File file = new File("d:" + File.separator + "hello.txt");
        ObjectInputStream input = new ObjectInputStream(new FileInputStream(file));
        Object[] obj = (Object[]) input.readObject();
        input.close();
        return obj;
    }
}

class Student implements Serializable {
    private String name;
    private int age;
    
    public Student() {
    }

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

    @Override
    public String toString() {
        return "姓名:" + name + "  年龄:" + age;
    }
}