流是一组有序的数据序列。I/O流提供了一条通道程序,可以使用这条通道将源中的字节序列送到目的地。
Java中定义了许多类专门负责各种方式的输入或输出,这些类都被放在java.io包中。其中所有的输入流类都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)类的子类;所有的输出流类都是抽象类OutputStream(字节输出流)类或Writer类(字符输出流)的子类。
InputStream类是字节输入流的抽象类,是所有字节输入流的父类。类中的所有方法遇到错误时都会引发IOException异常。
下面是InputStream类中的一些方法:
方法 | 简要说明 |
---|---|
read() | 从输入流中读取数据的下一个字节。返回0-255范围内的int字节值。如果已经到达流末尾而没有可用的字节,返回-1. |
read(byte[] b) | 从输入流中读取一定长度的字节到b字节数组中,并以整数形式返回字节数。 |
mark(int readlimit) | 在输入流的当前位置设置一个标记,readlimit参数告知此输入流在标记位置失效前允许读取的字节数 |
reset() | 将输入指针返回到当前所做的标记处 |
skip(long n) | 跳过输入流上的n个字节并返回实际跳过的字节数 |
markSupported() | 如果当前流支持mark()/reset()操作则返回true |
close() | 关闭此输入流并释放与该流有关的所有系统资源 |
注意,并不是所有的IputStream类的子类都支持该类只能够定义的所有方法,如skip(),reset(),mark()等。
Java中的字符是Unicode编码,是双字节的。InputStream类是用于处理字节的,并不适合处理字符文本。Java为字符文本的输入专门提供了单独的类Reader。Reader类并不是InputStream类的替换者,知识在处理字符串时会简化编程。Reader类是字符输入流的抽象类,所有字符输入流类都是Reader的子类。Reader类中的方法与InputStream类类似,可以参见API文档。
OutputStream流是字节输出流的抽象类。OutputStream类中的所有方法返回类型均为void,在遇到错误时会引发IOException异常。下面对字节输出流中的方法进行简要介绍:
方法 | 简要说明 |
---|---|
write(int b) | 指定的字节写入此输出流 |
write(byte[] b) | 将字节数组中的字节写入此输出流 |
write(byte[] b ,int off , int len) | 将指定的byte数组中从off开始的len个字节写入此输出流 |
flush() | 彻底完成输出并情况缓存区 |
close() | 关闭输出流 |
Writer类是字符输出流的抽象类。Writer类中的方法与OutputStream类中的方法类似。
File类的对象是java.io包中唯一代表磁盘文件本身的对象。File类定义了一些与平台无关的方法来操作文件,可以通过调用File类中的方法来实现删除、创建、重命名文件等操作。File类的对象主要用于获取文件自身的信息。数据流可以将数据写入文件中,也可以从文件中获取数据,文件也是数据流最常用的数据媒体。
可以使用File类创建一个文件对象。通常会使用以下三种构造方法来创建文件对象:
File(String pathname);
:该构造方法通过给定路径名字符串转换为抽象路径名来创建一个新File实例。其中,pathname值路径名称(包含文件名),如:“d:/12.txt”
File(String parent , String child);
:该构造方法根据定义的父路径和子路径字符串创建一个新的File对象File(File f , String child);
:该构造方法根据定义的父路径对象和子路径字符串创建一个新的File对象在使用File构造函数创建File对象后,如果当前目录中不存在所想要创建的文件,File类对象会自动调用createNewFile()方法创建一个改名称的文件;如果存在该文件,可以使用delete()方法对其进行删除。
File类提供了很多获取文件信息的方法:
方法 | 返回值 | 说明 |
---|---|---|
getName() | String | 获取文件名称 |
canRead() | boolean | 判断文件是否为可读的 |
canWrite() | boolean | 判断文件是否可写 |
exits() | boolean | 判断文件是否存在 |
length() | long | 获取文件的长度(以字节为单位) |
getAbsolutePath() | String | 获取文件的绝对路径 |
getParent() | String | 获取文件的父路径 |
isFile() | boolean | 判断文件是否存在 |
isDirectory() | boolean | 判断文件是否为目录(文件夹) |
isHidden() | boolean | 判断文件是否为隐藏文件 |
lastModified() | long | 获取文件最后修改文件 |
在程序运行期间,大部分数据都在内存中操作,当程序结束或关闭时,这些数据将会消失。如果需要将数据永久保存,可以使用文件输入输出流与指定文件建立连接,将需要的数据保存到文件中。
FileInputStream类和FileOutputStream类都用于操作磁盘文件。FileInputStream类继承自InputStream类,可以实现文件基本的读取操作;FileOutputStream类继承自OutputStream类,提供了基本的文件写入能力。
FileInputStream类的基本构造函数为:
FileInputStream(String name);
:使用给定的文件名name创建一个文件输入流对象FileInputStream(File file)
:使用给定的File对象创建一个文件输入流对象FileOutputStream类的构造方法与FileInputStream类类似,需要注意的是,构造方法的参数可以指定不存在的文件名,但是不能是已经被其他程序打开的文件。
虽然Java在程序结束时会自动关闭所有打开的流,但是显示的关闭流是一个编程的好习惯。一个被打开的流有可能会用尽系统资源,这样如果没有将其关闭时,当试图打开另一个流时,可能会得不到需要的系统资源。
当使用FileInputStream类和FileOutputStream类与文件建立连接时,这两个类都只提供了对字节或字节数组的读取方法。由于汉字在文件中占用两个字节,因此如果使用字节流时,可能会出现乱码现象。因此使用继承自Reader类和Writer类的FileReader类和FileWriter类来进行与文件关系的建立。
下面使用实例来进行介绍:
import java.io.*
public FileTest{
public static void main(String []args){
File file = new File("Test.txt"); //创建文件
try{
FileWruter fw = new FileWriter(file); //创建字符文件输入流
fw.write("MAMA的心事不让你看见。"); //使用输入流类write方法写入文件
fw.close(); //关闭字符文件输入流
FileReader fr = new FileReader(file); //创建字符文件输出流
Byte[] b = new Byte[1024]; //创建字节数组
int length = fr.read(b); //使用输出流中的read方法将文件中的内容写入字节数组,并返回读取的字节长度
System.out.println(new String(b,0,length)); //创建String对象并打印
fr.close(); //关闭字符数组输出流
}catch(Exception e){
e.printStackTrance();
}
}
}
缓存是IO的一种性能优化。缓存流为IO流增加了内存缓存区。有了缓存区,使得在流上执行skip()、mark()和reset()方法都成为可能。
BufferedInputStream类可以滴所有InputStream类进行带缓冲区的包装达到性能的优化。BufferedInputStream类有两个构造方法:
BufferedInputStream(InputStream in);
:创建了一个带有32个字节缓冲区的缓冲流BufferedInputStream(InputStream in , int size);
:按照指定的size大小创建缓冲流BufferedOutputStream类输出信息和OutputStream类的输出方法完全一样,只不过BufferedOutputStream类中定义了flush()方法将缓冲区中的数据强制完全输出。BufferedOytputStream类也有两个构造方法:
BufferedOutputStream(InputStream in);
:创建了一个带有32个字节缓冲区的缓冲流BufferedOutputStream(InputStream in , int size);
:按照指定的size大小创建缓冲流flush()方法在缓冲区即使没有满的情况下,也将缓冲区的内容强制输出到目标文件中,习惯上称这个过程为刷新。该方法只对使用缓冲区的OutputStream类及其子类有效。不过当调用close()方法时,系统在关闭流之前也会将缓冲区中的信息刷新到目标文件中。
BufferedReader类和BufferedWriter类继承自Reader类和Writer类。这两个类同样的具有内部缓存机制,并可以以行为单位进行输入或输出。
BufferedReader类的常用方法如下:
方法 | 说明 |
---|---|
read() | 读取单个字符 |
readLine() | 以行为单位进行读取并返回为字符串。若无数据可读则返回null |
BufferedWriter类的常用方法
方法 | 说明 |
---|---|
write(String str , int off , int len) | 写入字符串的一部分 |
newLine() | 写入一个行分隔符(即为换行) |
flush() | 刷新该流的缓存 |
由上可知,当使用write()方法时,数据并没有被直接写入流,而是写入了缓冲区。如果想立即将数据写入文件中,需要使用flush()方法。
DataInputStream类和DataOutputStream类允许应用程序与机器无关的方法从底层输入流中读取基本Java数据类型——也就是说,当读取一个数据是,不必再关心这个数值应当是哪种字节。
DataInputStream类和DataOutputStream类的构造方法:
DataInputStream(InputStream in);
:使用指定的InputStream创建一个DataInputStream类对象;DataOutputStream(OutputStream out);
:创建一个新的数据输出流,并将数据送入基础字节输出流中。DataOutputStream类提供了以下三种写入数据的方法:
writeBytes(String s);
writeChar(String s);
writeUTF(String s);
由于Java中的字符是Unicode编码为双字节,writeBytes方法是将字符串中每个字符的低字节内容写入设备中;writeChar方法是将字符串中每个字符两个字节的内容都写入目标设备;writeUTF方法将字符串按照UTF编码后的字节长度写入目标设备,然后才是将每个字节的UTF编码写入设备。
DataInputStream类中只定义了readUTF()方法返回字符串。因为如果在一个连续的字节流中如果没有特殊的标记作为字符串的结尾且不知道字符串的长度时,就无法得到正确的字符串。DataOutputStream类中也只有writeUTF提供了这些功能。所以也只有writeUTF方法和readUTF方法能够准确的写入读取字符串。
下面使用实例进行介绍:
package com.mw05;
import java.io.*;
public class DataStreamTest {
public static void main(String[] args) {
File file = new File("DataFileTest.txt");
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
dataOutputStream.writeUTF("使用writeUTF方法写入数据。");
dataOutputStream.writeBytes("使用writeBytes方法写入数据。");
dataOutputStream.writeChars("使用writeChar方法写入数据。");
dataOutputStream.close();
fileOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
System.out.println(dataInputStream.readUTF());
dataInputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用writeUTF写入字符串后,打开创建的.txt文件,尽管无法看出所写内容,但是可以使用readUTF方法进行读取。但是如果使用其他两种方法写入字符串,想要读取出来就不容易了。
java内置的ZIPInputStream类和ZIPOutputStream类来实现文件的解压和压缩。如果想要从ZIP压缩文件中读取某个文件,需要找到该文件的目录进入点;同时想要将文件内容写入ZIP文件中,也要先写入对应该文件的目录进入点,并且将此文件内容得位置要进入点所指的位置,再进行文件内容写入。
ZIPEntry类产生的对象是用来表示一个ZIP压缩文件内的进入点。下面介绍利用这三个类实现ZIP数据压缩。
利用ZIPOutputStream类可以将文件压缩为.zip文件。ZIPOutputStream类的构造方法如下;
ZIPOutputStream(OutputStream out);
ZIPOutputStream类的常用方法如下:
方法 | 返回值 | 说明 |
---|---|---|
putNextEntry(ZipEntry entry) | void | 开始写一个新的ZipEntry,并将此流内的位置移至entry所致数据的开头 |
write(byte[] b , int off , int len) | void | 将字符数组写入当前ZIP条目数据 |
finish() | void | 完成写入ZIP输出流的内容,无需关闭他所配合的OutputStream流 |
setComment(String comment) | void | 可设置此ZIP文件的注释文字 |
下面通过实例来进行介绍;
package com.mw06;
import java.io.*;
import java.util.zip.*;
public class MyZip {
public void zip(String zipfileName, File inputFile) throws Exception{
//创建zip方法,用于主方法中调用
ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipfileName));
//创建压缩输出流
System.out.println("压缩中......");
zip(zipOutputStream, inputFile,"");
//调用zip重载方法
zipOutputStream.close();
//关闭压缩输出流
}
public void zip(ZipOutputStream out , File inputFile ,String base) throws Exception{
if (inputFile.isDirectory()) { //测试此抽象路径名表示的文件是否为一个目录
File[] flFiles = inputFile.listFiles(); //获取路径数组
if (base.length() != 0) { //base表示该文件的父路径
out.putNextEntry(new ZipEntry(base + "\\")); //写入此目录的entry
}
for (int i = 0; i < flFiles.length; i++) { //对该文件中的所有文件进行zip重载调用
zip(out, flFiles[i], flFiles[i].getPath());
}
}else { //当该文件不是文件夹时
out.putNextEntry(new ZipEntry(base)); //将输出流的起点标记到该文件的父路径处
FileInputStream inputStream = new FileInputStream(inputFile);
//创建文件输入流读取该文件的内容
int b;
System.out.println(base); //打印该文件的父路径
while ((b = inputStream.read()) != -1) { //当该输入流读取未到达文件末尾时
out.write(b); //将该字符写入输出流的文件中
}
inputStream.close();
System.out.println("压缩完成。");
}
}
public static void main(String[] args) {
MyZip zip = new MyZip();
try {
zip.zip("WORK.zip", new File("WORK"));
} catch (Exception e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
如上可知,每个ZIP文件中可能包含多个文件。使用ZIPOutputStream类对压缩文件进行输出前,必须先使用putNextEntry方法将该文件的进入位置entry进行写入,这样输出流才能定位到该文件的位置处进行压缩输出。输出结束后,在当前项目目录下就会产生我们所需要的“WORK.zip”文件。
ZipInputStream类可以读取ZIP压缩格式的文件,包括已压缩和未压缩的条目。ZipInputStream类的构造方法如下:ZipInputStream(InputStream in);
常用的ZipInputStream类方法如下:
方法 | 返回值 | 说明 |
---|---|---|
read(byte[] b , int off ,int len) | int | 读取目标b数组内off偏移量长度为len字节的数据 |
available() | int | 判断是否已经读完当前entry所指定的数据。已读完返回0,否则返回1 |
closeEntry() | void | 关闭当前ZIP条目并定位流读取下一个条目 |
skip(long n) | long | 跳过当前ZIP条目中指定的字节数 |
getNextEntry() | ZipEntry | 读取下一个ZipEntry条目,并将流内的位置转移到该entry所指的数据的开头 |
createZipEntry(String name) | ZipEntry | 使用指定的name参数新建一个ZipEntry对象 |
下面我们使用实例对解压文件进行描述:
package com.mw07;
import java.io.*;
import java.util.zip.*;
public class ZIPread {
public static void main(String[] args) {
File file = new File("WORK.zip");
try {
ZipFile zipFile = new ZipFile(file);
ZipInputStream zin = new ZipInputStream(new FileInputStream(file));
//创建压缩输入流读取压缩文件目录
ZipEntry entry = zin.getNextEntry();
//首先跳过file压缩文件的父目录,此时entry代表的是放置file文件的路径
while ((entry = zin.getNextEntry()) != null && !entry.isDirectory()) {
//当entry路径表示的不是文件夹且是解压进入位置存在时
File file2 = new File(entry.getName());
if (!file2.exists()) {
file2.getParentFile().mkdirs();
//创建文件父类文件夹路径
FileOutputStream out = new FileOutputStream(file2);
//将解压文件输出到file2文件下
InputStream in = zipFile.getInputStream(entry);
//创建输入流对象,并将entry表示的路径写到输入流上
int count = 0;
while ((count = in.read()) != -1) {
out.write(count);
}
out.close();
in.close();
}
zin.closeEntry(); //关闭当前entry
System.out.println(entry.getName() + "解压成功。");
}
zin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Java中的IO机制提供了一套简单的标准化API,以方便我们对不同的数据源进行读取或者写入。同时我们必须对字符流和字节流进行了解区分,对他们所扩展的子类也要熟练掌握。另外使用数据流来对磁盘文件的内容进行存取也是本节的重点,需要熟练掌握。