java中的数据可以使用一套输入/输出流的通信系统来存储和获取。这个系统是在java.io包中实现的。I/O系统主要通过创建输入/输出流来读取和存储数据。通常,I/O系统分为字节流和字符流。字节流用来处理字节、整数和其他简单的数据类型,字符流用来处理文本文件和其他文本数据源。
字节流 | 字符流 |
---|---|
InputStream | Reader |
OutputStream | Writer |
13.1 I/O流概述
输入/输出(Input/Output)是指对某个物理或逻辑设备或某种㡳进行数据的输入或输出。例如:对服务器主机数据的读/写、摄像头视频数据的输入、本地文件的输入/输出等。由于数据存取的环境和设备不同,所以数据的读/写方案具有多样性。输入/输出问题在程序射击中是一个复杂的问题。java针对这个问题,提出了自己的解决方案——流(Stream)对象。对不同的输入/输出问题提供了不同的流对象。所有的数据都可以使用流写入和读出。流是程序中数据传输的一条路径。
数据流一般分为输入流(InputStream)和输出流(OutputStream)两种,这两种数据流是两个抽象类,意味着其方法在不同的子类中,有多种不同的实现方法。如操作文件,当向文件写入数据时,它是一个输出流(从内存输出到文件),当从文件中读取数据的时候,它是一个输入流(从文件输入到内存)。键盘只是一个产生输入流的工具,屏幕只是一个输出流的体现工具。
java标准的数据流程序与系统在字符方式(如Dos)下的交互,可分为三种:
-
System.in 标准输入流。他已经打开并准备输入数据。此种数据流通常应用于键盘输入和指定另外的输入源。
public final static InputStream in = new InputStream();
-System.out是标准输出流。它已经打开并且准备输出数据。这种数据流通常应用于显示器屏幕输出或指定另外一个输出目标。
public final static PrintStream out = new PrintStream();
-
System.err是标准错误输出流。他已经打开并准备输出数据。这种数据流通常应用于显示器屏幕输出或指定另外一个输出目标。
public final static PrintStream err = new PrintStream();
InputStream抽象类是所有输入字节流的超类,它必须提供返回下一个输入字节的方法。输入字节流的基本处理单元是字节。
OutStream抽象类是所有输出字节流的超类,它按字节接收数据,并将这些数据发送给接收器。OutputStream抽象类的子类必须提供至少一种写入单个输入字节的方法才能使用。
Reader是读取字符流的抽象类。子类必须实现的方法只有两个:
read(char[],int,int)
和close()
.多数情况下需要重写一些方法,提高效率。
Writer是写入字符流的抽象类。其子类必须实现的方法只有三个`
write(char[],int off,int len)
\ flush()
\ close()
13.2 文件
在I/O处理中,首先想到的是如何进行文件的读/写。java中有关文件处理的类有:File、FileInputStream、FileOutputStream、RandomAccessFile、FileDescriptor;接口:FilenameFilter。
这节主要讲述File类和RandomFile类的常用方法。
13.2.1 File 类
不同操作系统的路径名称是有差别的。例如:
Windows | Linux |
---|---|
D:\workspace\Capther13 | /home/workspace/Capther13 |
windows的路径使用UNC(通用命名约定(Universal Naming Convention))路径名,
以\\
开始的目录表示上下文环境所在的目录的硬盘根目录。
如果没有以\\
作为路径的开始,则表示相对于当前工作目录的路径,并通过盘符(C/D/E/…)形式表示硬盘指定。
Linux没有Windows系统硬盘驱动器的概念。他的路径以/
开始,表示从根目录开始的绝对路径,不以/
开始的路径是相对于当前路径的相对路径。
一个File对象的实例被创建之后,他的内容不能被修改。File实例对象可以用于表示一个文件,还可以用于表示一个目录。甚至可以查询文件系统。
在java中,无论是文件还是目录,都使用File类的实例表示
1.文件或目录的生成
2.文件名的处理
3.文件属性测试
4.普通文件信息和工具
5.目录操作
13.2.2 File 类的应用
【例1】遍历文件夹下的文件,并且输出文件夹的信息
package example;import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;public class FileDemo {public static <ReadFileList> void main(String[] args) {
// String filename = args[0]; //由参数获取文件名String filename = "D:\\eclipse-eclipse\\Capther11-\\src\\main\\java\\example"; //由参数获取文件名File file = new File(filename);Filter filter = new Filter("java");//创建并初始化文件过滤器//创建FileDemo实例,并调用ReadFlieList()方法new FileDemo().ReadFileList(file, filter);}public void ReadFileList(File file ,Filter filter){if(file.isDirectory()) {//判断文件是不是目录try{//列出所有文件及目录File[] files = file.listFiles(filter);//创建目录数组//通过数组创建数组列表ArrayList<File> ArrayList = new ArrayList<File>();for (int i = 0;i<files.length;i++){//先列出目录if (files[i].isDirectory()){//判断是否为目录//输出路径名System.out.println("【"+files[i].getPath()+"】");ReadFileList(files[i],filter); //递归调用ReadFileList方法}else{//文件先存入fileList,待会再列出ArrayList.add(files[i]);}}//列出文件for (File f : ArrayList){ReadFileList(f,filter);}System.out.println();//输出换行符}catch (Exception e){e.printStackTrace();}}else if(file.isFile()){ //当file是文件时FileDesc(file); //调用文件排序方法}}public void FileDesc(File file){if (file.isFile()){System.out.print(file.toString()+"\n该文件");System.out.print(file.canRead()?"可读":"不可读");System.out.print(file.canWrite()?"可写":"不可写");System.out.println(file.length()+"字节");}}}class Filter implements FilenameFilter{String extent; //拓展名public Filter(String extent) {this.extent = extent;}public boolean accept(File dir, String name) {return name.endsWith("."+extent);//返回文件的拓展名}
}
13.2.3 RandomAccessFile类
通常,文件的操作都是循环进行的,在文件中进行一次存取操作,它的读取/写入位置就医相对于目前的位置移动一次。实际上,如果需要读取或写入的动作只是在某一个区段内,则可以采用随机存取(Random Access)的方法。随机存取可在文件中任意地移动存取的位置。
在Java中, RandomAccessFile类可以给用户提供随机访问的方法,使用它的seek()方法来指定存取的位置。位置移动的单位是字节。
接口DataInput用于在二进制流中读取字节,并根据基本数据类型进行重构。
接口DataOutput用于将数据从任意Java的基本类型转换为一系列的字节,并写入二进制流。
1.构造方法
2.文件指针的操作
为了保证移动存取位置的正确性,通常在随机文件读取之前固定每个数据的长度。如固定为每一个雇员的数据大小,java中并没有提供直接的方法来取得固定长度的数据,必须自己计算获得。
13.2.4 RandomAccessFile类的应用
RandomAccessFile类在随机(相对顺序而言)读/写等长记录数据的格式时有很大的优势,比如,读取数据库中某一条记录时。但是RandomAccessFile类只能用于操作文件,不能访问其他的I/O设备,如网络、内存映像等。
【例2】一个演示保存和访问雇员对象的例子
package org.example;import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;public class RandomAccessFileDemo {public static void RandomWriteFile(File file) throws IOException {Employee[] employees = new Employee[4]; //创建数组//初始化数组employees[0] = new Employee("张三",24);employees[1] = new Employee("李四",22);employees[2] = new Employee("王五",24);employees[3] = new Employee("钱六",22);RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");;//创建RandomAccessFiletry {RandomAccessFile randomAccessFile1 = new RandomAccessFile(file, "rw");}catch (FileNotFoundException e) {throw new RuntimeException(e);}try {for (Employee e:employees){randomAccessFile.writeChars(e.getName());randomAccessFile.writeInt(e.getAge());}randomAccessFile.close();//关闭randomAccessFile} catch (IOException e) {throw new RuntimeException(e);}}private static String readName(RandomAccessFile randomAccessFile)throws IOException {char[] name = new char[8];for (int i = 0;i< name.length;i++){name[i] = randomAccessFile.readChar();//读取字符}//将空字符取代为空格符并返回return new String(name).replace('\0',' ');}public static Employee[] RandomReadFile(File file) throws Exception{RandomAccessFile randomAccessFile;//创建RandomAccessFile对象randomAccessFile = new RandomAccessFile(file,"r");Employee[] employee = new Employee[4];//Employee类的占用空间int num = (int) randomAccessFile.length()/Employee.size();for (int i = 0;i<num;i++){randomAccessFile.seek((i)*Employee.size());//使用对应的read()方法读出数据employee[i] = new Employee(readName(randomAccessFile),randomAccessFile.readInt());}randomAccessFile.close();//关闭randomaccessfilereturn employee;}public static void main(String[] args) throws Exception{String filename = "employeeExample";//创建并初始化文件名称File file = new File(filename);//创建并初始化File对象RandomWriteFile(file);//调用RandomWriteFile()方法Employee[] employee = RandomReadFile(file);//返回文件中保存的employee//使用for循环遍历employee数组for (Employee e : employee){System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());}}
}class Employee {String name;int age;final static int LEN = 8;//创建并初始化静态public Employee(String name, int age) {if (name.length()>LEN){name = name.substring(0,8); //截取字符串的子字符串}else {while (name.length()<LEN)name = name+"\u0000";}this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", age=" + age +'}';}//获取类占用的空间public static int size(){return 2*8 + 4; //字符串长度是8.一个字符占用2个字节,一个整型占用4个字节。}
}
13.3 字节流InputStream、OutputStream
计算机中所有数据都以0和1的方式保存,但是如果在两个不同设备或者环境之间进行数据的读/写,则也必须以0与1的方式进行。java中数据源与目的地之间的数据流动抽象为一个流(Stream),流中间流动的是位数据。
java中有两个类用于流的抽象表示:
java.io.InputStream&java.io.OutputStream
13.3.1 字节输入、输出流
**字节输入流(InputStream)**是所有字节输入流的超类,它是一个抽象类, 它的派生类必须重新定义字节输入流中声明的抽象方法。
read()方法用于从输入流中读取一个字节的内容并以整型返回。如果遇到流的结束符则返回-1;如果流没有结束,但暂时又没有数据可读,那么该方法就会处于阻塞情况,直到流中有了新的可读数据。
1.从流中读取数据
2.关闭流
3.使用输入流中的标记
字节输出流OutputStream是所有字节输出流的超类,它是一个抽象类,它的派生类必须重新定义字节输出流中定义的抽象方法。
1.输出数据
2.清空输出流
3.关闭流
【例3】in对象输入和out对象输出的实例
package example;import java.io.IOException;public class ReadCharacter {public static void main(String[] args) {try {System.out.print("请输入字符:");//输出字符串信息//获取键盘数据并输出System.out.println("输入字符十进制表示为:"+System.in.read());}catch(IOException e){e.printStackTrace();}}
}
通常情况下,由于InputStream或OutputStream的方法都比较底层,所以很少直接使用,而是通过实现他们的子类来实现更高级的操作,使输入/输出更便捷。
13.3.2 字节文件输入、输出流
- 字节文件输入流java.io.FileInputStream是InputStream的子类。该类指从某个磁盘文件中获得输入字节,并读取数据到目的地,至于哪些文件可用则取决于主机环境。
- 字节文件输出流java.io.FileOutputStream是OutputStream类的子类,它用于将数据写入磁盘文件或者FileDescriptor的输出流,文件是否可用取决于平台。
- 当创建一个FileInputStream或FileOutputStream实例对象时,指定文件应该是存在而且可读的。在创建一个FileInputStream实例对象时,如果指定文件已经存在,那么这个文件将被清除。如果这个文件不存在,就会创建新的文件。
不能指定被打开的文件。 - FileInputStream的read()方法每次可读取一个字节,并以Int类型返回。或者使用read()方法时读取一个byte数组。读取的字节个数取决于数组的长度。
byte数组通常被视为一个缓冲区,扮演数据中转的角色。
【例4】实现文件复制功能
package org.example;import java.io.*;// File f =new File("D:\\LOLFolder\\lol2.txt");public class FileInputAndOutputStream {public static void main(String[] args) {try {byte[] buffer = new byte[1024];//源文件
// FileInputStream fileInputStream = new FileInputStream(new File(args[0]));FileInputStream fileInputStream = new FileInputStream(new File("D:\\LOLFolder\\lol.txt"));//目的文件
// FileOutputStream fileOutputStream = new FileOutputStream(new File(args[0]));FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\LOLFolder\\lol2.txt"));//avaliable()获取未读取的数据长度System.out.println("复制文件"+fileInputStream.available()+"字节");while(true) {if(fileInputStream.available()<1024) {//剩余的数据比1024小//一位一位读出再写入目的文件int remain = -1;while((remain=fileInputStream.read())!=-1){fileOutputStream.write(remain);}break;//终止循环}else {//从源文件读取数据至缓冲区fileInputStream.read(buffer);//将数据数组写入目的文件fileOutputStream.write(buffer);}}//关闭流fileInputStream.close();fileOutputStream.close();System.out.println("复制完成");}catch(ArrayIndexOutOfBoundsException e) {e.printStackTrace();}catch(IOException e) {e.printStackTrace();}}
}
FileOutputStream默认以创建新文件的方式启动流。如果创建的文件存在,则文件将被覆盖。如果实例化时指定FileOutputStream为附加模式(构造方法第二个append参数为true),如果创建的文件存在,则直接打开源文件并启动流,写入数据附加到文件末端。如果不存在,则创建新文件并启动流。
13.3.3 字节缓冲输入、输出流
- 在 13.3.2中,FileOutputStream和fileInputStreamsm中使用了一个byte数组作为数据缓冲区。这是因为硬盘文件的存取速度远远低于内存中的数据存取速度。为了 减少对磁盘的访问,通常在读入文件的时候不以一个字节作为读取单位,而是读入一定长度的数据。同样写入数据也需要写入一定长度的数据,这样可以提高文件的处理效率。
- 字节缓冲输入流java.io.BufferedInputStream和字节缓冲输出流java.io.BufferedOutputStream为InputStream、OutputStream类增加缓冲区功能。通常通过创建一个InputStream、OutputStream类型的实例来构建BufferedInputStream、BufferedOutputStream实例。
- BufferedInputStream的数组成员buf是一个个位数组,默认是2048字节。当读取数据来源时(例:文件),他会尽量将buf填满。当使用read()方法时,实际上是先读取buf里的数据,而不是直接读取数据来源。当buf中的数据不足时, BufferedInputStream才会调用给定的InputStream对象的read()方法,从指定的装置中提取数据。
- BufferedOutputStream的数组成员buf是一个个位数组,默认是512字节。当使用write()方法写入数据时,实际上会现将数据写入buf中,当buf已满时才会调用给定的OutputStream对象的write()方法将buf数据写入至目的地。
- 使用 BufferedOutputStream、BufferedInputStream不需要自行设置缓冲区,还能使文件操作变简单,效率变高。
【例5】BufferedOutputStream、BufferedInputStream类的方法演示
package org.example;import java.io.*;// File f =new File("D:\\LOLFolder\\lol2.txt");public class FileInputAndOutputStream {public static void main(String[] args) {try {byte[] data = new byte[1];//创建byte类型的数组//源文件
// FileInputStream fileInputStream = new FileInputStream(new File(args[0]));File srcFile = new File("D:\\LOLFolder\\lol.txt");//目的文件
// FileOutputStream fileOutputStream = new FileOutputStream(new File(args[0]));File desFile = new File("D:\\LOLFolder\\lol2.txt");//创建并初始化对象BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFile));BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(desFile));System.out.println("复制文件"+srcFile.length()+"字节");while(bufferedInputStream.read(data)!=-1) {bufferedOutputStream.write(data);}//关闭流bufferedOutputStream.flush();bufferedInputStream.close();bufferedOutputStream.close();System.out.println("复制完成");}catch(ArrayIndexOutOfBoundsException e) {e.printStackTrace();}catch(IOException e) {e.printStackTrace();}}
}
- BufferedOutputStream、BufferedInputStream类并没有改变InputStream、OutputStream类的接口方法,读入和写出还是由InputStream类的read()方法和OutputStream类的write()方法负责。
- BufferedOutputStream、BufferedInputStream类在对数据进行操作之前,动态的为他们添加了缓冲区的功能。
- 为了保证缓冲区中的数据可以全部被写入目的地,建议在关闭流之前执行flush()方法,将缓冲区中的数据全部写入目的流。
13.3.4 字节数据输入、输出流
- 字节数据输入流java.io.DataInputStream 和字节数据输出流java.io.DataOutStream提供了一些针对Java基本数据类型的写入和读出操作。
- 由于Java的数据类型占用空间大小有规定,在对基本数据类型数据进行写入和读出操作时,不需要担心不同平台间数据大小差异的问题。
- 例如,一个对象的成员数据都是Java的基本数据类型,要对这些数据存取就可以通过使用DataInputStream和DataOutputStream类来实现。
【例6】DataInputStream和DataOutputStream类的方法演示
package org.example;import java.io.*;// File f =new File("D:\\LOLFolder\\lol2.txt");public class DataIOStreamDemo {public static void main(String[] args) throws IOException {String filename = "D:\\LOLFolder\\lol3.txt" ; //创建并初始化文件名字符串//创建并初始化Employee类型数组Employee[] employees = {new Employee("张三",23),new Employee("张三",23),new Employee("张三",23),new Employee("张三",23),};try{//创建DataOutputStream对象DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(filename));for(Employee Employee:employees){//写入UTF字符串dataOutputStream.writeUTF(Employee.getName());//写入int数据dataOutputStream.writeInt(Employee.getAge());}//读出所有数据至目的地dataOutputStream.flush();//关闭流dataOutputStream.close();DataInputStream dataInputStream = new DataInputStream(new FileInputStream(filename));for (int i = 0; i <employees.length ; i++) {//读取UTF字符串String name = dataInputStream.readUTF();//读取int数据Integer sorce = dataInputStream.readInt();employees[employees.length-1-i] = new Employee(name,sorce);}//关闭流dataInputStream.close();//输出还原后数据//使用for循环遍历employee数组for (Employee e : employees){System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());}}catch (IOException e){e.printStackTrace();}}
}class Employee {String name;int age;final static int LEN = 8;//创建并初始化静态public Employee(String name, int age) {if (name.length()>LEN){name = name.substring(0,8); //截取字符串的子字符串}else {while (name.length()<LEN)name = name+"\u0000";}this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", age=" + age +'}';}//获取类占用的空间public static int size(){return 2*8 + 4; //字符串长度是8.一个字符占用2个字节,一个整型占用4个字节。}
}
- DataInputStream 和 DataOutStream类并没有改变InputStream、OutputStream类的方法,数据的读取仍由nputStream、OutputStream类的方法负责实现。
- DataInputStream 和 DataOutStream类只是在实现对应的方法时,添加了动态判断数据类型的功能。
- 其他流对象也可以使用DataInputStream 和 DataOutStream类。
13.3.5 字节对象输入、输出流
- 通常情况下,java中的数据以对象的形式放在内存中,所以希望数据也可以以对象的形式保存于硬盘文件,并且在下次运行程序时可以读取硬盘文件和数据文件,并还原为对象。
- java提供了字节对象输入流java.io.ObjectInputStream、字节对象输出流java.io.ObjectOutputStream来实现这样的操作。
- 如果想实现直接存储数据对象,则在定义类的时候必须实现java.io.Serializable 接口。但是java.io.Serializable接口没有规定必须实现的方法,所以这里的实现实际上就是给对象贴上一个标识,代表对象是可以序列化的
- 对象序列化是指对象能够将自己的状态信息数据保存下来的特性。对象序列化的目的,是使得程序中的一个对象在存储或者进行网络传输的时候,另一个程序可以将对象还原。
【例7】ObjectOutputStream、ObjectInputStream类的方法演示
package org.example;import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;// File f =new File("D:\\LOLFolder\\lol2.txt");public class DataIOStreamDemo {public static void main(String[] args) throws IOException {String filename = "D:\\LOLFolder\\lol3.txt" ; //创建并初始化文件名//创建并初始化Employee类型数组Employee[] employees = {new Employee("张三",23),new Employee("张三",23),new Employee("张三",23),new Employee("张三",23),};//写入新文件writeObjectToFile(employees,filename);try{//读取文件数据employees = readObjectsFromFile(filename);for (Employee e : employees){System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());}System.out.println();//换行符//初始化Employee数组employees = new Employee[2];employees[0] = new Employee("李四",24);employees[1] = new Employee("王五",24);//附加新对象到附件appendObjectsToFile(employees,filename);//读取文件数据employees = readObjectsFromFile(filename);for (Employee e : employees){System.out.println("name = "+e.getName() +"\t"+"||"+"\t"+"age = "+e.getAge());}}catch (Exception e){e.printStackTrace();}}private static void appendObjectsToFile(Employee[] employees, String filename) throws FileNotFoundException {File file = new File(filename);if(!file.exists()){throw new FileNotFoundException();}try {//附加模式ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file,true)){//如果要附加对象至文件后,必须重新定义这个方法@Overrideprotected void writeStreamHeader() throws IOException {}//抛出异常};for (Employee employee:employees){outputStream.writeObject(employee);}outputStream.close();}catch (IOException e){e.printStackTrace();}}//将指定文件中的对象数据返回private static Employee[] readObjectsFromFile(String filename) throws FileNotFoundException{File file = new File(filename);//如果文件不存在就抛出异常if(!file.exists()){throw new FileNotFoundException();}//使用List先存储读回的对象List<Employee> list = new ArrayList<Employee>();try {FileInputStream fileInputStream = new FileInputStream(filename);ObjectInputStream objInputstream =new ObjectInputStream(fileInputStream);while (fileInputStream.available()>0){//读取一个对象到列表中list.add((Employee)objInputstream.readObject());}}catch (Exception e){e.printStackTrace();}Employee[] employee = new Employee[list.size()];return list.toArray(employee);}//将指定的对象写入指定的文件private static void writeObjectToFile(Employee[] employees, String filename) {File file = new File(filename);try{ObjectOutputStream objoutputStream = new ObjectOutputStream(new FileOutputStream(filename));for (Object obj :employees){//将对象写入文件objoutputStream.writeObject(obj);}//关闭objoutputStreamobjoutputStream.close();} catch (Exception e){e.printStackTrace();}}
}//将对象附加到指定文件之后class Employee implements Serializable {String name;int age;final static int LEN = 8;//创建并初始化静态public Employee(){}public Employee(String name, int age) {if (name.length()>LEN){name = name.substring(0,8); //截取字符串的子字符串}else {while (name.length()<LEN)name = name+"\u0000";}this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", age=" + age +'}';}//获取类占用的空间public static int size(){return 2*8 + 4; //字符串长度是8.一个字符占用2个字节,一个整型占用4个字节。}
}
13.4 字符流Reader、Writer
java.io.Reader和java.io.Writer及其子类用于处理字符流(Character Stream)。它们对流数据的操作是以一个字符(2字节)的长度为单位进行处理的,并且可以进行字符编码处理。
也就是说Reader、Writer及其子类可以用于纯文本文件的读/写、
13.4.1 字符读、写流
- 字符读流java.io.Reader和字符写流java.io.Writer支持Unicode标准字符集。在进行文本文件读/写时,一般使用Reader/Writer子类,子类通常会重新定义相关的方法。
- Reader&Writer是抽象类,他们只是提供了用于字符流处理的接口,不能生成实例,只能通过他们的子类对象处理字符流。
1.字符读流Reader抽象类
该类是处理所有字符流输入类的父类。
Reader的子类必须实现read(char[],int,int)和close()方法
2.字符写流Writer抽象类
该类是处理所有字符流输出的父类。
该类的子类必须实现write(char[],int,int),flush(),和close()方法。
13.4.2 字符输入、输出流
字符输入流InputStreamReader和字符输出流OutputStreamWriter分别是Reader、Writer子类,可以用InputStream、OutputStream类进行字符处理,即以字符为基本单位进行读/写操作。
类InputStreamReader、OutputStreamWriter实现字符流与字节流间的转换,字符流操作效率比字节流高。
想显示纯文本内容,则不用自己判断字符编码。直接操作InputStreamReader和OutputStreamWriter对象来读取文本。
【例8】ObjectOutputStream、ObjectInputStream类的方法演示
package org.example;import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;// File f =new File("D:\\LOLFolder\\lol2.txt");public class IOStreamRWDemo {public static void main(String[] args) {try {FileInputStream fileInputStream = new FileInputStream("D:\\LOLFolder\\lol2.txt");InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);FileOutputStream fileOutputStream = new FileOutputStream("D:\\LOLFolder\\lol3.txt");OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);int ch = 0;while (((ch = inputStreamReader.read())!=-1)){System.out.print((char) ch);outputStreamWriter.write(ch);}System.out.println();inputStreamReader.close();outputStreamWriter.close();}catch (Exception e){e.printStackTrace();}}}
- InputStreamReader、OutputStreamWriter在流中存取是以系统默认编码进行字符转换的。也可以自己指定编码类型。
13.4.3 文件读、写字符流
- 存取文本文件还有更为便捷的方式——直接视图文件读字符类java.io.FileReader 和文件写字符类java.io.FileWriter。他们分别继承自InputStreamReader和OutputStreamWriter类,并且可以直接指定文件名称或File对象打开指定的文件。
- 字符转换根据系统默认的编码类型来实现,如果用户要指定编码,就需要使用InputStreamReader和OutputStreamWriter类。
- FileReader和FileWriter类的使用非常简单,仅需在创建对象的时候指定文件即可。
- Linux系统中板鞋的文本文件换行字符为‘\n’,而windows系统重编写的文本文件换行字符为“\r\n”
【例9】FileReader 、FileWriter类的方法演示
package org.example;import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;// File f =new File("D:\\LOLFolder\\lol2.txt");public class IOStreamRWDemo {public static void main(String[] args) {try {FileReader fileReader = new FileReader("D:\\LOLFolder\\lol2.txt");FileWriter fileWriter = new FileWriter("D:\\LOLFolder\\lol3.txt");int in = 0;char[] wlnChar = {'\r','\n'};//声明并初始化字符数组while ((in = fileReader.read())!=-1){if (in =='\n'){fileWriter.write(wlnChar);}else {fileWriter.write(in);}}fileReader.close();fileWriter.close();}catch (Exception e){e.printStackTrace();}}}
13.4.4 字符缓冲区读、写流
- 字符缓冲读流java.io.BufferedReader 与字符缓冲区写流java.io.BufferedWriter的默认缓冲区大小为8192个字符。
- BufferedReader类在读取文本文件时,会先从文件读取字符到缓冲区,然后使用read()方法从缓冲区读取。如果缓冲区数据不足,则会再从文件中读取字符到缓冲区。
- 同样,BufferedWriter类在写入数据时,不直接写到目的地,而是先写入缓存区,如果缓冲区已满,则进行一次对目的地的写操作。因此,通过缓冲区域可以减少对磁盘的输入/输出操作。提高文件的读取效率。
【例10】BufferedReader 、BufferedWriter类的方法演示
package org.example;import java.io.*;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;// File f =new File("D:\\LOLFolder\\lol2.txt");public class IOStreamRWDemo {public static void main(String[] args) {try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\LOLFolder\\lol2.txt"));String input = null;while (!(input = bufferedReader.readLine()).equals("quit")){bufferedWriter.write(input);bufferedWriter.newLine();//写入与操作系统相关的换行符}bufferedReader.close();bufferedWriter.close();}catch (Exception e){e.printStackTrace();}}}
13.5 拓展训练
13.5.1 训练一:按顺序创建文件
BufferWriter类将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
【例11】使用BufferedWriter类实现字符串的顺序写入
package example;import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;public class OrderWriteFile {public static void main(String[] args) {FileWriter fw ;try {fw = new FileWriter("D:\\LOLFolder\\lol2.txt");BufferedWriter bf = new BufferedWriter(fw); //创建缓冲字符输出流对象for(int i = 0 ; i < 10 ; i++) {bf.write("Java"+i +"\n");}bf.close();}catch(IOException e){e.printStackTrace();}}
}
13.5.2 训练二:将一个大文件分割为多个小文件
【例12】in和out输入/输出
为了方便携带和传输(比如邮箱的邮件都有大小限制),可以将文件进行分割。
package org.example;import java.io.*;// File f =new File("D:\\LOLFolder\\lol2.txt");public class SplitFile {static final String SUFFIX = ".txt";//分割后的文件拓展名//将指定的文件按照给定的文件的字节数进行分割public static String[] divide(String name,long size)throws Exception{File file = new File(name);if (!file.exists()||(!file.isFile())){throw new Exception("指定文件不存在");}//获得被分割父文件,将来被分割成的小文件存放在这个目录下。File parentFile =file.getParentFile();long fileLength = file.length();if (size<=0){size = fileLength/2;}//获得被分割后的小文件数目int num = (fileLength%size != 0)?(int) (fileLength/size + 1):(int) (fileLength/size);String[] fileNames = new String[num];//存放被分割后的小文件名FileInputStream in = new FileInputStream(file);//输入文件流,即被分割的文件long end = 0; //输入文件流的开始和结束下标long begin = 0;for (int i = 0; i < num; i++) { //根据要分割的数目输出文件//对于前num - 1 个小文件,大小都为指定的sizeFile outFile = new File(parentFile,file.getName() + i +SUFFIX );FileOutputStream out = new FileOutputStream(outFile);//构建小文件的输出流end += size;//将结束下标后移sizeend = (end>fileLength)?fileLength:end;for (; begin < end ; begin++) {out.write(in.read());//从输入流中读取字节存储到输出流中}out.close();fileNames[i] = outFile.getAbsolutePath();}in.close();return fileNames;}public static void readFileMessage(String fileName){ //读出分割成的小文件中的内容File file = new File(fileName);BufferedReader reader = null ;try {reader = new BufferedReader(new FileReader(file));String string = null;//按行读取内容,直到读入null则表示读取文件结束while ((string = reader.readLine())!=null){System.out.println(string);}reader.close();}catch (IOException e){e.printStackTrace();}finally {if (reader!=null){try {reader.close();}catch (IOException e1){}}}}public static void main(final String[] args)throws Exception{String name = "D:\\LOLFolder\\lol2.txt";long size = 250;String[] fileNames = SplitFile.divide(name,size);System.out.println("文件"+name+"分割结果如下“");for (int i = 0;i<fileNames.length;i++){System.out.println(fileNames[i]+"内容如下");SplitFile.readFileMessage(fileNames[i]);System.out.println();}}}
13.6 技术解惑
13.6.1 把InputStream转换成String的几种方法
在java中遇到如何把InputStream转换成String的情形。比如从文件或网络中得到一个InputStream,需要转换成字符串输出或赋予其他变量。
常用的方法就是按字节一次次读到缓冲区,或建立BufferedReader逐行读取。
13.6.2 读取大文件用哪个类合适
- 读取文件行的标准方式是在内存中读取。这种方法带来的问题,是文件的所有行都被放在内存中。当文件足够大时,会很快导致程序抛出OutOfMemoryError异常。
- 把文件所有的内容都放在内存中会很快耗尽可用内存,不论实际可用的内存有多大。
- 实际上,大多数情况下不需要把文件的所有行一次性的放入内存中。相反,只需要遍历文件的每一行,然后进行相应的处理,处理完后把它扔掉。
使用java.util.Scanner 类扫描文件内容,可以一行一行的连续读取文件。
【例13】使用java.util.Scanner 类扫描文件内容
package org.example;import java.io.*;
import java.util.Scanner;// File f =new File();public class ReadBigFileDemo {public static void main(String[] args) {FileInputStream inputStream = null;Scanner sc = null;String path = "D:\\LOLFolder\\lol2.txt";try {inputStream = new FileInputStream(path) {@Overridepublic int read() throws IOException {return 0;}};sc = new Scanner(inputStream);while (sc.hasNextLine()){String line = sc.nextLine();System.out.println(line);}if (sc.ioException()!=null){throw sc.ioException();}}catch (IOException e){e.printStackTrace();}finally {if (inputStream!=null){try {inputStream.close();}catch (IOException e){e.printStackTrace();}}if (sc!=null){sc.close();}}}}