Java 输入输出流
本章要点
u IO的分类
u 文件类
u 字节流
u 字符流
u 对象流
IO(输入/输出)是指的计算机与外部世界链接的桥梁,它对于任何计算机系统都非常关键。
本章就详细介绍在Java语言中,本章将详细介绍在Java语言中如何向来自不同的资源(例如文件标准输入输出设备)写入或者从这些资源中读取数据随后讲解字节的流类、字符的流类、面向对象流类操作。
1.1什么是流
在前面章节的学习中,我们知道,一个Java程序可以从控制台接收数据并输出到控制台中。数据保存在内存的对象里,一旦Java程序运行结束后,程序就会在内存中消失对象所包含的数学也就消失了。那么有没有什么方法可以将内存中的数据保存下来了,以便下次运行程序的时候可以使用?此外除了通过控制台设备接收和输出数据外,我们能不能从其他设备接收数据并将数据保存或输出到其他设备呢?
Java类库中提供了大量的类,可以帮助我们从不同的设备读取数据,并保存或输出到不同的设备中。这些累统一放在java.io包和java.nio包中,统称为Java I/O系统。 这里的I 是英文单词 Input的缩写表示输入,O 是英文单词 Output的缩写 表示输出。
要深入的学习Java I/O系统,首先我们要先理解流的概念。
那么什么是流?流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源设备的流,这个数据源设备可能是文件、内存或网络连接。
流是Java I/O的基础,是Java语言对I/O的一个最基本的抽象。之所以采用流作为I/O最基本的抽象,是应为流有2个最基本的特性:一个是它含有流质,一个是它的方向,在计算机中流质就是数据,方向就是读或者写。
2.1流的分类
对于Java I/O类库对应的java.io包,我们可以将它看成是存放各种类型流的仓库,根据流中的流质和流向不同,I/O包中的流分为了许多类型。
l 输入流和输出流
流按照数据流的方向分,可以分为2种类型:
² 输入流:从数据源读取数据到程序中。只能从中读取数据,而不能写入数据。I/O包中的输入流都是继承抽象类InputStream或Reader
² 输出流:讲数据从程序写入数据目的地。只能从中写入数据。而不能从中读取数据。I/O包中的输入流都是继承抽象类OutputStream或Writer
l 字节流和字符流
流按照处理数据的最小单位不同,可以分为2种类型:
² 字节流:字节流是以byte为最小单位进行数据传送。I/O包中的字节流都是继承都是继承自抽象类 InputStream或OutputStream
² 字符流:字符流是以char为最小单位进行数据传送。I/O包中的字符流都是继承自抽象类Reader或Writer
I/O库做出这种区分是为了满足处理文字型数据的需要。我们知道byte是8位的,char是16位。在Java中各种语言都采用Unicode形式编码Unicode编码是基于16位的char,所以在读写文字型的数据是通过字符流相率会更高,而一些二进制数据的读写,比如视频 、图形等,则需要使用字节流来完成。
现在我们已将对Java I/O有了大体的认识,但是应该字母操作呢?想像一下这样的情况,家里需要使用自来水,自来水公司要把水输到用户家里,应该先把自来水公司到用户家里的连接水管建好,当用户需要使用水的时候,取出水用完了以后,关闭水龙头。我们操作流是不是应该也是一个这样的流程?
为了确保程序的通用型,以便在无需了解数据源的情况下就可以读取数据,最好的办法是围绕抽象类来编写程序,所有的流操作都可以使用下面的步骤:
1. 建立流:根据源和具体的操作(是读取还是写入)选择流,然后建立流。通过流的建立,创建内存到数据源之间的数据通道,以传输数据。
2. 操作流:将数据读取到内存,或将内存中的数据写入数据源。
3. 关闭流:流操作结束后,释放与该流相关联的系统资源。
现在我们已将掌握Java I/O 库中各类流的分布规律和使用规律。下面我们开始深入学习Java I/O库的主要类。
3.1文件类
Java I/O包中的File 类提供了管理磁盘文件和目录的基本功能。我们可以将一个File 对象看作是一个文件或者目录的名称和位置的字符串。File 有4个构造方法
| 构造 | 释义 |
| 使用指定参数创建一个File对象。参数parent代表一个目录,参数child代表在parent中的子目录或者文件 | |
| public File(String pathname) | 创建一个与指定路径名相关联的File对象
|
| 使用指定参数创建一个File对象。参数parent代表一个目录(这里传递的是字符串),参数child代表在parent中的子目录或者文件 | |
| public File(URI uri) | 使用给定的java.net.URI对象创建一个File对象,URI是统一资源标识符,一个文件的URI:"file:///目录/文件名" 格式 |
因此File对象类似一个字符串,只代表一个文件或者目录的的路径名,所以即使指定的文件或者文件目录不存在,这些构造也能成功,例入:
|
虽然这个文件可能并不存在,但是我们得到另一个文件名为s的File对象,而File对象包含了很多有用的方法来判断该文件的信息。
| 方法 | 描述 |
| boolean canWriter() | 判断文件是否可写 |
| boolean canRead() | 判断文件是否可读 |
| boolean createNewFile() | 创建一个新的文件,该方法会抛出IO异常 |
| boolean delete() | 删除文件文件或者目录,删除目录需要保证该目录为空目录 |
| boolean exists() | 判断文件或者目录是否存在 |
| boolean isFile() | 判断是否是文件 |
| boolean isDirectory() | 判断是否为目录 |
| String [] list() | 返回包含目录中所有文件或者目录的名称 |
| File [] listFiles() | 返回该目录中File对象的数组 |
| boolean mkdirs() | 创建目录 |
为了演示File类 下面的代码清单中演示创建了File对象,并使用File对象的方法判断文件或者目录的信息
| package ch11; import java.io.File; public class FileDemo { public static void main(String[] args) { File file = new File(args[0]); if(!file.exists()){ System.out.println("文件不存在"); return ; } if(file.isFile()&&file.canRead()){ System.out.println("该文件可以操作"); }
if(file.isDirectory()){ System.out.println(file.getPath()+"是一个目录,它包含如下文件"); String [] fileNames = file.list(); for (String string : fileNames) { System.out.println(string); } } } }
|
通过上面的例子我们可以发现,file类对象可以操作我们用户电脑上的磁盘文件。在File中还提供了File.separator这个常量,用于返回操作系统文件分割符,为什么要提供这个?这是因为各个操作平台下的文件分割符是不一样的。Windows操作系统使用"\",而Unix和Linux使用"/"。我们程序要想实现在不同的系统上对文件进行操作则需要使用文件类提供的分割符来做文件目录的分割符。
虽然File类可以操作文件,但是File不是流,它不能操作文件的内容。需要操作文件内容需要使用下面学习的各种流。
4.1字节流
字节流用于处理而精致文件。所有字节流都继承与抽象类InputStream和OutputStream 两个父类。其中InputStream为读取字节流的父类,OutputStream为写入字节流的父类。下图中给出字节流的关系结构
在InputStream类中提供了方法,这些方法提供了管理流的信息,从流中读取数据,查明流中有多少数据、关闭流的等功能。所有的输入流都至少有这些方法可以使用。在掌握父类方法基础上去学习子类特殊的方法,这样就可以减少学习难度。
| 方法 | 描述 |
| int available() | 返回可以从该流中读取的字节数 |
| void close() | 关闭输入流,并释放资源 |
| int read() | 从输入流读取下一个字节,读取完毕返回-1 |
| int read(byte[] b) | 从输入流读取字节,并将都去的字节放入缓存数组 b ,读取完毕返回-1 |
| int read(byte [] b int sta,int end) | 从输入流指定位置sta开始读取数据到end个字节,并将读取的数据放在缓存区 b中读取完毕返回- |
图:InputStream常用方法
在OutputStream类中提供了方法,这些方法提供了向流中写入数据,将流中的内容刷入文件并关闭该改流的基本功能。同样,所有的输出流类都至少有这些方法可以使用。掌握了父类的方法,在去学习子类的特有方法,会更简单。
| 方法 | 描述 |
| void close() | 关闭输出流,并释放所有与该留相关的系统资源 |
| void flush() | 刷新该输出流,并强制写出所有的缓冲字符串 |
| void write(int b) | 将字节b写到该输出流中 |
| void write(byte [] bs) | 将制定的字节数组bs中的内容斜土该输出流 |
| void write(byte [] bs,int off,int len) | 将制定字节数组b中数据,从off 开始写入len长度字节到该输出流 |
图:OutputStream常用方法
下面我们讲对字节流中的低级流和高级流介绍。
4.1.1 低级字节流
字节流中的主要低级流,根据其输入和输出的设备和数据源,分为三类:
u 对二进制文件进行读写操作的FileInputStream和OutputStream类,其数据源为磁盘文件
u 对内存缓冲区中的字节数组进行读写操作的ByteArrayInputStream 和ByteArrayOutputStream类,其数据源是内存中的字节数组。
u 对线程管道经行读写操作的PipedInputStream和PipedOutputStream类,其数据源是线程通道。
下面,我们以常见的用的对文件经行读写操作的FileInputStram和FileOutputStream为例,演示低级流的操作。
| package oop7;
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;
public class CopyFile {
public static void main(String[] args) { // TODO Auto-generated method stub
if (args.length == 0) { System.out.println("参数没有~"); return; }
// 拷贝源 File inFile = new File(args[0]); if (!inFile.exists() || inFile.isDirectory()) { System.out.println("复制源不存在"); return; }
// 目标 File outFile = new File(args[1]); InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = new FileInputStream(inFile); outputStream = new FileOutputStream(outFile); byte [] bs = new byte[1024];//缓冲区 一般习惯给1024 int i=0; //循环的读取和写入文件 直到将源文件读完 while ((i = inputStream.read(bs)) != -1) { outputStream.write(bs,0,i); }
} catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { //关闭流 if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
}
} |
运行上面程序,在调用时传递参数,会讲数组中的第一元素的内容进行复制一次,复制到数组的第二个元素所有代表的文件。
这里需要注意下,对流的操作一定要关闭,释放相关的资源。这里使用了finally块来关闭流,即使上面的错误发生了,这里也能保证流被关闭掉。
4.1.2 高级字节流
高级字节流对低级字节流进行了封装,并有许多功能的扩展
字节缓冲流:BufferedInputStream和BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能,创建BufferedInputStream需要给定一个InputStream实列,实现BufferedInputStream时,实际上最后显示的InputStream实例,同样的在创建BufferedOutputStream流时,也需要给定一个OutputStream实例。
| package ch11;
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class CopyFileBuffer { public static void main(String[] args) { // 原始文件地址 String inFile = "C:" + File.separator + "input.txt"; // 目标文件地址 String outFile = "C:" + File.separator + "output.txt"; BufferedInputStream bufferedInputStream = null; BufferedOutputStream bufferedOutputStream = null; try { // 读取内容 bufferedInputStream = new BufferedInputStream(new FileInputStream(inFile)); // 写 bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outFile)); int c; while ((c = bufferedInputStream.read()) != -1) { bufferedOutputStream.write(c); } // 将缓冲区中的内容写入到流中 bufferedOutputStream.flush(); } catch (IOException exception) { exception.printStackTrace(); } finally { // 关闭流 try { if (bufferedInputStream != null) { bufferedInputStream.close(); } if (bufferedOutputStream != null) { bufferedOutputStream.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
|
为了确保缓冲区中的数据一定被别写出到目的地,建议最后执行flush() 将缓冲区中的数据全部写出到流中。
因为在缓冲流中可以提高I/O的操作效率,所以在是文本读取的操作内容中,我们应该尽可能的使用缓冲流去实现。
字节流,可以操作磁盘中的任意文件,并且不会发生错误。。
5.1 字符流
字符流用于处理文本文件,所有的字符流都继承与抽象类Reader和Writer两个父类,其中Reader为读取字符流的父类,Writer为写入字符流的父类。下面两图展示了字符流中输入和输出的层次关系示意图
输入字符流
输出字符流
Reader和InputStream相似,用于从流中读取数据。Reader以字符为单位操作流。下图中列举出了Reader常用方法
| 方法 | 描述 |
| int reader() | 从流中读取一个字符 ,并将其返回,读取到末尾返回-1 |
| int reader(char []bufer) | 从流中读取一个字符放到字符数组中,返回读出的字符数 |
| int reader(char [] bufer,int off,int len) | 讲流中中的指定位置的off开始读取数据的前len个字符,并将其读入字节性数据中,读取完毕,返回-1 |
| void close() | 关闭输入流,并释放所有与该流相关联的系统资源 |
Writer和OutputStream类似,用于向流中写入数据,只是Writer是以字符为单位写入数据。下图中列出了Writer常用的方法。
| 方法 | 描述 |
| void writer(int c) | 将参数c组成的字符写入流中 |
| void writer(char[] buffer) | 将字符数组buffer写入流中 |
| void writer(char [] buffer,int off,int len) | 讲字符数组buffer从off开始写入到len个字符 |
| void writer(String str) | 将str字符串写入流中 |
| void flush() | 刷新该输出流,并强制写出所有缓冲输出字节 |
| void close() | 关闭输入流 并释放所有与流相关联的系统资源 |
在处理字符流时,主要的问题是经行字符编码的转换。Java语言采用Unicode字符编码方式,对于每个字符,JVM为其分配2个字节的内存。但是,在文本文件中,字符有可能采用其它的类型转换为Unicode编码。例如UTF-8,GBK,GB2312等。Reader类可以将数据源中采用其它编码类型的字符转换为Unicode字符。然后在内存中为这些Unicoder字符分配空间;Writer类能够将内存中的Unicode字符转换为其它编码类型的字符,在写到数据目标地。默认情况下,Reader和Writer会在本地操作系统默认字符和Unicode编码之间转换。对于中文操作系统平台,默认的字符编码通常为GBK.
由于Reader和Writer采用了字符编码转换技术,所以他们能够正确的访问各种字符编码的文本文件。另一方面,在为字符分配内存时,JVM在内存中统一使用Unicode编码,所以Java程序在处理字符具有平台独立性。这就省去了开发人员在编写程序时手动进行字符转码的繁琐设置。
字符流类的用法和字节流的用法大值相同,因此下面我们同样可以讲字符流分为低级流和高级流,并做简单简绍。
5.1.1低级字符流
字符流中的低级流包括:
u 对内存数组操作的CharArrayReader和CharArrayWriter,与ByteArrayInputStream 和ByteArrayOutputStream类类似,其数据源为内存中的字符数组。
u 对内存中的String对象进行操作的StringReader和StringWriter类,其数据源为内存中String对象。
u 对线程管道经行读写操作的PipedReader和PipedWriter类,其数据源是线程通道。
u 对文件文件进行对象操作的FIleReader和ReaderWriter类,与FileInputStream和FileOutput类似,其数据源为硬盘文件,FileReader和FileWriter按照本地操作系统默认字符编码,读写文件
下面我们来看一个列子
| public static void main(String[] args) { try { Writer writer = new OutputStreamWriter(new FileOutputStream(new File("F:/work_spaces/CH11 "))); Scanner sc = new Scanner(System.in); writer.write("\r\n"+sc.next()); writer.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } |
上面的类中演示了将程序中的字符串写入文件中。我们在来看下面一个列子。
| public static void main(String[] args) { try { BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(new FileInputStream("F:/work_spaces/CH11"))); String me = null; while ((me = bufferedReader.readLine()) != null) { System.out.println(me); } bufferedReader.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } |
在上面的列子省略了部分内容。
5.1.2高级字符流
字符流中常用的高级流包括:
u 缓冲流:包括BufferedReader和BufferedWriter类,利用缓冲区提高读写数据的效率
u 转换流:用于字节数据到字符数据之间的转换,包括InputStreamReader和OutputStreamWriter。
u 打印输出流:包括PrintWriter类,允许将基本数据类型数据类型打印输出到字符流中,PrintWriter带有自动刷新(Flush)功能。
下面我们来看看列子
| public static void main(String[] args) {
try { Writer writer = new OutputStreamWriter(new FileOutputStream(new File("F:/work_spaces/CH11"))); Scanner sc = new Scanner(System.in); //手动换行 writer.write("\r\n"+sc.next());
//刷新 writer.flush(); //关闭 writer.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
} |
在上面的代码中我们看见的是使用了转换流的高级流,如果要使用BufferedWriter ,只需要使用 BufferedWriterbufferedWriter = new BufferedWriter(new OutputStreamWriter(newFileOutputStream(new File("F:/work_spaces/CH11"))));下面我们在看看使用转换流的读取方式
| //省略。。。 try { BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(new FileInputStream("F:/work_spaces/CH11")));
String me = null; while ((me = bufferedReader.readLine()) != null) { System.out.println(me); } bufferedReader.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } |
6.1 对象流
ObjectInputStream和ObjectOutputStream类分别是InputStream和OutputStream的子类。ObjectInputStream和ObjectOutputStream创建的对象称为对象输入流和对象输出流,对象输出流使用WriteObject(Object obj)方法将一个对象Obj写入输出流送往目的地,对象输入流则使用readObject()方法从源中读取一个对象到程序中。下面我着重讲解对象流
当我们使用对象流写入或者读取对象时,要保证对象是序列化的,这是为了保证能把对象写入到文件,并且从文件中正确读取到程序中。一个类如果实现了Serializable接口,那么这个类创建的对象就是序列化的对象,Serializable接口中的方法对程序是不可见的,因此实现该接口的类不需要实现额外的方法,当把一个序列化的对象写入到对象输出流的时候,JVM会自动的实现Serializable接口中的方法,按照一定格式的文本将对象写入到目的地。
但是我们应该明确,对象流写入到文件是以16进制保存的,因此使用普通的编辑器打开(word,记事本)会乱码,对象流本来就是适合网络之间的传输。
| package com.eduask.test; import java.io.Serializable; public class Animal implements Serializable { private static final long serialVersionUID = 1L; private Stringname; private Integerweight; private Stringcolor; private Stringtype; private Integerage; private Integerlifetime; public String getName() { returnname; } public void setName(String name) { this.name =name; } public Integer getWeight() { returnweight; } public void setWeight(Integer weight) { this.weight =weight; } public String getColor() { returncolor; } public void setColor(String color) { this.color =color; } public String getType() { returntype; } public void setType(String type) { this.type =type; } public Integer getAge() { returnage; } public void setAge(Integer age) { this.age =age; } public Integer getLifetime() { returnlifetime; } public void setLifetime(Integer lifetime) { this.lifetime =lifetime; } public Animal(Stringname, Integer weight, Stringcolor, String type, Integerage, Integer lifetime) { super(); this.name =name; this.weight =weight; this.color =color; this.type =type; this.age =age; this.lifetime =lifetime; } @Override public String toString() { return"Animal [name=" + name + ", weight=" + weight + ", color=" + color + ", type=" + type + ", age=" + age + ", lifetime=" + lifetime + "]"; } }
|
| package com.eduask.test;
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class TestObjectStream { public static void main(String[] args) { try { ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream(new File("d:/oos.dat"))); Animal a1 = new Animal("tiger", 120,"red", "cat", 12, 20); Animal a2 = new Animal("eagle", 10,"gold", "bird", 6, 10); oos.writeObject(a1); oos.writeObject(a2); oos.flush(); oos.close();
ObjectInputStream ois =new ObjectInputStream(new FileInputStream("d:/oos.dat")); Animal ra1 = (Animal)ois.readObject(); System.out.println(ra1.toString()); Animal ra2 = (Animal)ois.readObject(); System.out.println(ra2.toString()); } catch (Exceptione) { e.printStackTrace(); } } } |
课后作业

被折叠的 条评论
为什么被折叠?



