Java中的I/O流——这样够详细吗?
Java中的I/O流
一、概述
流是一组有序的数组序列,根据操作的类型,可分为输入和输出流两种。I/O(Input/Output,输入/输出)流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地。虽然I/O流通常与磁盘文件的存取有关,但是程序的源和目的地也可以是键盘、鼠标、内存或者显示器等。
Java由数据流处理输入/输出模式,程序从指向源的输入流中读取源中的数据。源可以是文件、网络、压缩包或者其他数据源。
输出流的指向是数据要到达的目的地,程序通过向输出流中写入数据吧信息传递到目的地。输出流的目标可以是文件、网络、压缩包或者控制台和其他数据输出目标。
二、输入/输出流
Java语言定义了许多专门负责各种方式的输入/输出,这些类都被放在java.io包中。其中,所有输入流都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类;而所有输出流都是抽象类OutputStrame(字节输出流)或者抽象类Write(字符输出流)的子类。
2.1输入流
InputStream类是字节输入流的抽象类,是所有字节输入流的父类。
InputStream类的具体层次结构可以参考一下下面的图片:
该类中所有方法遇到错误时都会引发IOException异常。以下内容是对该类中的一些方法进行简要说明。
-
read()方法:从输入流中读取数据的下一字节。返回0-255范围内的int字节值。如果因为已经到达末尾而没有可用的字节,则返回值为-1.
-
read(byte[]b):从输入流中读取一定长度的字节,并以整数的形式返回字节数
-
mark(int readlimt)方法:在输入流的当前位置放置一个标记,readlimit参数告知此输入流在标记位置失效之前允许读取的字节数。
-
reset()方法:将输入指针返回到当前所做的标记处
-
skip(long n)方法:跳过输入流上的n个字节并返回实际跳过的字节数
·markSupported()方法:关闭此输入流并释放与该流关联的所有系统资源。
注意:
并不是所有的InputStream类的子类都支持InputStream中定义的所有方法,如skip()、mark()、reset()等方法只对某些子类有用。
Java中的字符是Unicode编码,是双字节的。InputStream是用来处理字节的,并不适合处理字符文本。Java为字符文本的输入专门提供了一套单独的类Reader类并不是InputStream类的替换着,只是在处理自负串时简化了编程。Reader类是字符输入流的抽象类,所有字符输入流的实现都是他的子类。Reader类的具体层次结构如下图所示:
2.2输出流
OutputStream类是字节输出流的抽象类,此抽象类是表示输出字节流的超累。
OutputStream类的具体层次如下图所示:
OutputStream类中的所有方法均返回void,在遇到错误时会引发IOException异常。
OutputStream类中的方法:
-
write(int b)方法:将制定的字节写入此输出流。
-
write(byte[]b)方法:将b个字节从指定的byte数组写入此输出流。
-
write(byte[]b,int off,int len)方法:将指定byte数组中从偏移量off开始 的len个字节写入此输出流。
-
flush()方法:彻底完成输出并清空缓存区。
-
close()方法:关闭输出流。
Writer类是字符输出流的抽象类,所有字符输出类的实现都是它的子类。
Writer类的层次结构如下图:
三、File类
File类是java.io包中唯一代表磁盘文件本身的对象。File类定义了一些与平台无关的方法来操作文件,可以通过调用File类中的方法,实现创建、删除、重命名文件等操作。File类的对象主要用来获取文件本身的一些信息,如文件所在的目录、文件的长度、文件读写权限等。数据流可以将数据写入到文件中,文件也是数据流最常用的数据媒体。
3.1文件的创建与删除
可以使用File类创建一个文件对象。通常使用以下三种方法来创建文件对象。
(1)File(String pathname)
该构造方法通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例。
语法:
new File(String pathname)
其中,pathname指路径名称(包含文件名)。例如:
File file = new File(“d:/1.txt”);
(2)File(String parent,String child)
该构造方法根据定义的父路径和子路径字符串(包含文件名)创建一个新的File对象。
语法:
new File(String parent,String child)
- parent:父路径字符串。例如:D:或者D:/doc
- child:子路径字符串。例如:letter.txt
(3)File(File d,String child)
该构造方法根据parent抽象路径名和child路径名字符串创建一个新File实例。
语法:
new File(File f,String child)
- f:父路径对象。例如,D:/doc
- child:字路径字符串。例如,letter.txt
注意:
Windows平台下包含盘符的路径名前缀有驱动器号和一个“:”组成。如果路径名是绝对路径,还可能后跟“\”。
当使用File类创建一个文件对象后,例如:
File file = new File(“word.txt”);
如果当前目录中不存在名称为word的文件,File类对象可通过createNewFile()方法创建一个名称为word.txt的文件;如果村在的word.txt文件,可以通过文件独享的delete()方法将其删除。
举个栗子(现在目录下并没有word.txt文件,运行后创建成功):
import java.io.File;
import java.io.IOException;
public class FileTest { //创建类FileTest
public static void main(String[] args) { //主方法
File file = new File("word.txt"); //创建文件对象
if (file.exists()) { //如果该文件存在
file.delete(); //将文件删除
System.out.println("文件已经删除");
} else { //如果该文件不存在
try { //try语句捕捉可能出现的异常
file.createNewFile(); //创建该文件
System.out.println("文件已创建");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
3.2获取文件信息
File类提供了很多方法用于获取一些文件本身信息,常用的方法如下表所示:
方法 | 返回值 | 说明 |
---|---|---|
getName() | String | 获取文件的名称 |
canRead() | boolean | 判断文件是否为可读的 |
canWrite() | boolean | 判断文件是否可被写入 |
exits() | boolean | 判断文件是否存在 |
length() | long | 获取文件长度(以字节为单位) |
getAbsolutePath() | String | 获取文件的绝对路径 |
getParent() | String | 判断文件的父路径 |
isFile() | boolean | 判断文件是否存在 |
idDirectory() | boolean | 判断文件是否为隐藏文件 |
isHidden() | boolean | 判断文件是否存在 |
lastModified() | lomg | 获取文件最后修改的时间 |
举个栗子:
获取当前文件夹下的word.txt文件的文件名、文件长度并判断该文件是否为隐藏文件。
import java.io.File;
public class FileTest2 { //创建类
public static void main(String[] args) { //主方法
File file = new File("word.txt"); //创建文件对象
if (file.exists()) { //如果文件存在
String name = file.getName(); //获取文件名称
long length = file.length(); //获取文件长度
boolean hidden = file.isHidden(); //判断文件是否为隐藏文件
//输出信息
System.out.println("文件名称:" + name);
System.out.println("文件长度:" + length);
System.out.println("是否为隐藏文件?" + hidden);
} else { //如果文件不存在
//输出信息
System.out.println("该文件不存在!");
}
}
}
运行结果:
四、文件输入/输出流
程序运行期间,大部分数据都在内存中进行操作,当程序结束或者关闭时,这些数据将会消失。如果需要将数据永久保存,可适应文件输入/输出流与指定的文件建立连接,将需要的数据永久保存到文件中。
4.1 FileInputStream与FileOutputStream类
FileInputStream类与FileOutputStream类都用来操作磁盘文件。如果用户的文件读取需求比较简单,则可以使用FileInputStream类,该类继承自InputStream类。FileOutputStream类与FileInputStream类对应,提供了基本的文件写入能力。FileOutputStream类是OutputStream类的子类。
FileInputStream类常用的构造方法如下:
-
FileInputStream(String name)
-
FileInputStream(File file)
第一个构造方法使用给定的文件名name创建一个FileInputStream对象,第二个构造方法使用File对象创建FileInputStream对象。第一个构造方法比较简单,但第二个构造方法允许吧文件传输连接输入流之前对文件作进一步分析。
FileOutputStream类有与FileInputStream类相同的采纳数构造方法,创建一个FileOutputStream对象时,可以指定不存在的文件名,但是此文件不能是一个已被其他程序打开的文件。
举个栗子:
使用FileInputStream与FileInputStream类实现文件的读取与写入功能。
import java.io.*;
public class FileTest3 { //创建类
public static void main(String[] args) throws IOException { //主方法
File file = new File("word.txt"); //文件对象
try {
//创建FileOutputStream对象
FileOutputStream out = new FileOutputStream(file);
//创建byte型数组
byte buy[] = "我是一个程序员,我特别的快乐。".getBytes();
out.write(buy);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
//创建FileInputStream类对象
FileInputStream in = new FileInputStream(file);
//创建byte型的数组
byte byt[] = new byte[1024];
//从文件中读取信息
int len = in.read(byt);
//将文件中的信息输出
System.out.println("文件中的信息是:" + new String(byt,0,len));
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
注意:
虽然Java在程序结束时候回自动关闭所有打开的流,但是当使用完流后,显式的关闭所有打开的流仍然是一个好习惯。一个被打开的流有可能会用尽系统资源,这取决于平台和实现,如果没有将打开的流关闭,当另一个程序试图打开另一个流时,可能会得不到需要的资源。
4.2FileReader和FileWriter类
使用FileOutputStream类向文件中写入数据与使用FileInputStream类从文件中将内容读出来,都存在一点不足,即这两个类都只提供了对字节或者字节数组的读取方法。由于汉字在文件中占用两个字节,如果使用字节流,读取不好可能会出现乱码的情况,此时采用符流Reader或Writer类即可避免这种现象。
FileReader和FileWriter字符流对应了FileInputStream和FileOutputStream类。FileReader流顺序地读取文件,只要不关闭流,每次调用read()方法就顺序的读取源中其余的内容,直到源的末尾或者流被关闭。
举个栗子:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
public class FileTest4 extends JFrame { //创建类,继承JFrame
private static final long serialVersionUID = 1L;
private JPanel jContentPane = null; //创建面板对象
private JTextArea jTextArea = null; //创建文本域对象
private JPanel controlPanel = null; //创建面板对象
private JButton openButton = null; //创建按钮对象
private JButton closeButton = null; //创建按钮对象
//省略窗体布局代码
private JButton getOpenButton(){
openButton = new JButton();
openButton.setText("写入文件"); //修改按钮的提示信息
openButton.addActionListener(new ActionListener() {
//按钮单击事件
@Override
public void actionPerformed(ActionEvent e) {
//创建文件对象
File file = new File("word.txt");
//创建FileWrite对象
try {
FileWriter out = new FileWriter(file);
//获取文本域中的文本
String s = jTextArea.getText();
out.write(s); //将信息写入磁盘文件
out.close(); //关闭流
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
return openButton;
}
private JButton getCloseButton(){
if (closeButton == null){
closeButton = new JButton();
closeButton.setText("读取文件"); //修改按钮的提示信息
closeButton.addActionListener(new ActionListener() {
//按钮单击事件
@Override
public void actionPerformed(ActionEvent e) {
File file = new File("word.txt"); //创建文件对象
//创建FileReader对象
try {
FileReader in = new FileReader(file);
char byt[] = new char[1024]; //创建char类型数组
int len = in.read(byt); //将字节读入数组
//设置文本域的显示信息
jTextArea.setText(new String(byt,0,len));
in.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
}
return closeButton;
}
public FileTest4(){
super();
initialize();
}
private void initialize(){
this.setSize(300,200);
this.setContentPane(getContentPane());
this.setTitle("JFrame");
}
private JPanel getjContentPane(){
if (jContentPane == null) {
jContentPane = new JPanel();
jContentPane.setLayout(new BorderLayout());
jContentPane.add(getJTextArea(), BorderLayout.CENTER);
jContentPane.add(getContentPane(),BorderLayout.SOUTH);
}
return jContentPane;
}
private Component getJTextArea() {
return null;
}
public static void main(String[] args) { //主方法
FileTest4 thisClass = new FileTest4(); //创建类对象
thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
thisClass.setVisible(true); //设置该窗体为显示状态
}
}
五、带缓存的输入/输出流
缓存是I/O的一种性能优化,缓存刘为I/O流增加了内存缓存区。有了缓存区,使得在流上执行skip()、mark()和reset()方法都称为可能。
–
5.1 BufferedInputStream和BufferOutputStream类
BufferedInputStream类可以对所有InputStream类进行带混存取的包装以达到性能的优化。
BufferedInputStream类有两个构造方法:
-
BufferedInputStream(InputStream in)
-
BufferedInputStream(InputStream in,int size)
第一种形式的沟早方法创建了一个带有32个字节的缓存流;第二种形式的构造方法按指定的大小来创建缓存区。一个最优的缓存区的大小,取决于它所在的操作系统、可用的内存空间以及机器配置。从构造方法中可以看出,BufferedInputStream对象位于InputStream类对象之前。
字节数据读取文件的过程:
使用BufferedOutputStream输出信息和用OutputStream输出信息完全一样,只不过BufferedOutputStream有一个flush()方法用来将缓存区的数据强制输出完。
BufferedOutputStream类也有两个构造方法:
-
BufferedOutputStream(OutputStream in)
-
BufferedOutputStream(OutputStream in,int size)
第一种构造方法创建一个有32个字节的缓存区,第二个构造方法以指定的大小来创建缓存区。
注意:
flush()方法就是用于即使在缓存区没有满的情况下,也将混存取的内容强制写入到外设,习惯上称这个过程为刷新。flush()方法只对使用缓存区的OutputStream类的子类有效。当调用close()方法时,系统在关闭流之前,也会将缓存区中的信息刷新到磁盘文件中。
5.2 BufferedReader与BufferedWriter类
BufferedReader类与BufferedWriter分类分别继承Reader类与Writer类。这两个类同样具有内部缓存机制,并且可以以行为单位输入/输出。
根据BufferedReader类的特点,总结了字符数据读取文件的过程。
BufferedReader类常用的方法如下:
-
read()方法:读取单个字符
-
readLine()方法:读取一个文本行,并且将其返回为字符串。若无数据可读,则返回null。
BufferedWrite类中的方法都返回void,常用方法如下:
-
write(String s,int off,int len)方法:写入字符串的某一部分。
-
flush()方法:刷新该流程的缓存。
-
newLine()方法:写入一个行分隔符。
在使用BufferedWrite类的Write方法时,数据并没有立刻被写入输出流,而是首先进入缓存区中。如果想立刻将缓存区的数据写入输出流,一定要调用flush()方法。
举个栗子:
import java.io.*;
public class StudentFile { //创建类
public static void main(String[] args) throws IOException { //主方法
//定义字符串数组
String content[] = {"好久不见","最近好吗","常联系"};
File file = new File("word.txt"); //创建文件对象
try {
FileWriter fw = new FileWriter(file);
//创建BufferedWrite类对象
BufferedWriter bufw = new BufferedWriter(fw);
for (int k = 0;k < content.length; k++) {
bufw.write(content[k]); //将字符串数组中的元素写入到磁盘文件中
bufw.newLine(); //将数组中的的那个元素以单行的形式写入文件
}
bufw.close();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileReader fr = new FileReader(file); //创建FileReader类对象
//创建BufferedReader类对象
BufferedReader bufr = new BufferedReader(fr);
String s = null; //创建字符串对象
int i = 0; //声明int类型的变量
//如果文件的文本行数不为null,则进入循环
while ((s = bufr.readLine()) != null) {
i++; //变量自增运算
System.out.println("第" + i + "行:" + s); //输出文件数据
}
bufr.close(); //将FileReader流关闭
fr.close(); //将FileReader流关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
六、数据输入/输出流
数据输入/输出流(DataInputStream类与DataOutputStream类)允许应用程序以与机器无关的方式从底层输入流中读取基本Java数据类型。也就是说,当读取一个数据时,补补再关心这个数值应当是哪种字节。
DataInputStream类与DataOutputStream类的构造方法如下:
-
DataInputStream(InputStream
in):使用指定的基础InputStream创建一个DataInputStream。 -
DataOutputStream(OutputStream out):创建一个新的数据输出流,将数据写入指定基础输出流。
DataOutoutStream类提供了如下三种写入字符串的方法:
-
writeBytes(String s)
-
waiteChars(String s)
-
waiteUTF(String s)
由于Java中的字符是Uincode编码,是双字节的,writeBytes只是将字符串中的美一个字符的低字节内容写入到目标设备中;而writeChars将字符串中的每一个字符的两个字节内容都写到目标设备中;writeUTF将字符串按照UTF编码后的字节长度写入目标设备,然后才是每一个字节的UTF编码
DataInputStream类只提供了一个readUTF()方法返回字符串。这是因为要在一个连续的字节流读取一个字符串,如果没有特殊的标记作为一个字符串的结尾,并且不知道这个字符串的长度,就无法知道读取到什么位置才是这个字符串的结束。DataOutputStream类中只有writeUTF方法想目标设备中写入字符串的长度,所以也能准确地读回写入字符串。
举个栗子:
import java.io.*;
public class Example01 { //创建类
public static void main(String[] args) { //主方法
try {
//创建FileOutputStream对象
FileOutputStream fs = new FileOutputStream("word.txt");
//创建DataOutputStream对象
DataOutputStream ds = new DataOutputStream(fs);
ds.writeUTF("使用writeUTF()方法写入数据"); //写入磁盘文件数据
ds.writeChars("使用writeChars()方法写入数据");
ds.writeBytes("使用writeByte()方法写入数据");
ds.close(); //关闭流
//创建FileInputStream对象
FileInputStream fis = new FileInputStream("word.txt");
//创建DataInputStream对象
DataInputStream dis = new DataInputStream(fis);
System.out.print(dis.readUTF()); //输出文件数据
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
七、 ZIP压缩输入/输出流
ZIP压缩管理文件(ZIP archive)是一种十分典型的文件压缩形式,使用它可以节省存储空间。关于ZIP压缩的I/O实现,在Java的内置类中提供了非常好用的相关的类,所以其实现方式非常简单。使用java.util.zip包中的ZipOutputStream与ZipInputStream类来实现文件的压缩和解压缩。如果要从ZIP压缩管理文件内读取某个文件,要先找到对应该文件的“目录进入点”(从它可知该文件在ZIP文件内的位置),才能读取这个文件的内容。如果要将文件内容西融入ZIP文件内,必须要先写入对应该文件的“目录进入点”,并且要把写入文件内容的位置移到此进入点所指的位置,然后再写入文件内容。
Java实现了I/O数据流与网络数据流的单一接口,因此数据的压缩、网络传输和解压缩的实现比较容易。ZipEntry类产生的对象,是用来代表一个ZIP压缩文件内的进入点(entry)。ZipInputStream类用来读取ZIP压缩格式的文件,所支持的包括已压缩及未压缩的进入点(entry)。ZipOutputStream类用来写出ZIP压缩格式的文件,而且所支持的包括已压缩及未压缩的进入点(entry)。
7.1 压缩文件
利用ZipOutputStream类对象,可将文件压缩为.zip文件。
该类的构造方法:
ZipOutputStream(OutputStream out)
该类常用的方法:
方法 | 返回值 | 说明 |
---|---|---|
putNextEntry(ZipEntry e) | void | 开始写一个新的ZipEntry,并将流内的位置移至此entry所指数据的开头 |
Write(byte[]b,int off,int len) | void | 将字节数据写入当前ZIP条目数据 |
Finish() | void | 完成写入ZIP输出流的内容,无须关闭它所配合的OutputStream |
setComment(String comment) | void | 可设置此ZIP文件的注释文字 |
举个栗子:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class MyZip { //创建类
private void zip(String zipFileName, File inputFile)throws Exception{
//创建ZipOutputStream类对象
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
zip(out,inputFile,""); //调用方法
System.out.println("压缩中..."); //输出信息
out.close(); //将流关闭
}
private void zip(ZipOutputStream out, File f, String base)throws Exception { //方法重载
if (f.isDirectory()){ //测试此抽象路径名表示额文件是否为一个目录
File[] fl = f.listFiles(); //获取路径数组
if (base.length() != 0){
out.putNextEntry(new ZipEntry(base+ "/")); //写入此目录的entry
}
for (int i = 0; i < fl.length; i++) {
zip(out,fl[i],base + fl[i]);
}
}else{
out.putNextEntry(new ZipEntry(base));
//创建FileInputStream对象
FileInputStream in = new FileInputStream(f);
int b;
System.out.println(base);
while((b = in.read()) != 1){
out.write(b);
}
in.close();
}
}
public static void main(String[] temp){ //主方法
MyZip book = new MyZip(); //创建对象
try {
//调用方法,参数为压缩后的文件与要压缩的文件
book.zip("D:hello.zip",new File("D:/hello"));
System.out.println("压缩完成"); //输出
} catch (Exception ex) {
}
}
}
运行结果:
7.2 解压缩ZIP文件
ZipInputStream类可读取ZIP压缩格式的文件,包括已压缩和未压缩的entry。
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对象 |
举个栗子:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class Decompression { //创建类
public static void main(String[] args) throws IOException { //主方法
File file = new File("hello.zip"); //当前压缩文件
ZipInputStream zin; //创建ZipInputStream对象
try { //try语句捕获可能发生的异常
ZipFile zipFile = new ZipFile(file); //创建压缩文件对象
zin = new ZipInputStream(new FileInputStream(file)); //实例化对象,指明要进行的解压文件
ZipEntry entry = zin.getNextEntry(); //跳过根目录,获取下一个ZipEntry
while(((entry = zin.getNextEntry())!= null) && !entry.isDirectory()){ //跳过根目录,获取下一个ZipEntry
File tmp = new File("C:\\" + entry.getName()); //解压出的文件路径
if (!tmp.exists()){ //如果该文件不存在
tmp.getParentFile().mkdirs(); //将文件目录中的文件放入输出流
OutputStream os = new FileOutputStream(tmp); //将文件目录中的文件放入输出流
//用输入流读取压缩文件中制定目录中的文件
InputStream in = zipFile.getInputStream(entry);
int count = 0; //创建临时变量
while((count = in.read()) != -1){ //如果输入流可以读取到数值
os.write(count); //输出流写入
}
os.close(); //关闭输出流
in.close(); //关闭输入流
}
zin.closeEntry(); //关闭当前entry
System.out.println(entry.getName() + "解压成功");
}
zin.close(); //关闭流
} catch (Exception e) {
e.printStackTrace();
}
}
}
希望有所帮助!!!