【Java学习笔记】13. I/O系统
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");;//创建RandomAccessFile
try {
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();//关闭randomaccessfile
return 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;
}
@Override
public 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;
}
@Override
public 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)){
//如果要附加对象至文件后,必须重新定义这个方法
@Override
protected 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);
}
//关闭objoutputStream
objoutputStream.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;
}
@Override
public 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 个小文件,大小都为指定的size
File outFile = new File(parentFile,file.getName() + i +SUFFIX );
FileOutputStream out = new FileOutputStream(outFile);//构建小文件的输出流
end += size;//将结束下标后移size
end = (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) {
@Override
public 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();
}
}
}
}