- Java中流的概念
流是一组有序的数据序列,根据操作的类型,可分为输入流和输出流两种。I/O(Input/Output,输入/输出)流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地。虽然I/O流通常与磁盘文件存取有关,但是程序的源和目的地也可以是键盘、鼠标、内存或显示器窗口等。
Java语言定义了许多类专门负责各种方式的输入/输出,这些类都被放在java.io包中。其中,所有输入流类都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类;而所有输出流都是抽象类OutputStream(字节输出流)或抽象类Writer(字符输出流)的子类。
输入流
InputStream类是字节输入流的抽象类,是所有字节输入流的父类。
该类中所有方法遇到错误时都会引发IOException异常。下面是对该类中的一些方法的简要说明。
read()方法:从输入流中读取数据的下一个字节。返回0~255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回值为-1。
read(byte[]b):从输入流中读入一定长度的字节,并以整数的形式返回字节数。
mark(int readlimit)方法:在输入流的当前位置放置一个标记,readlimit参数告知此输入流在标记位置失效之前允许读取的字节数。
reset()方法:将输入指针返回到当前所做的标记处。
skip(long n)方法:跳过输入流上的n个字节并返回实际跳过的字节数。
markSupported()方法:如果当前流支持mark()/reset()操作就返回true。
close()方法:关闭此输入流并释放与该流关联的所有系统资源。
Java中的字符是Unicode编码,是双字节的。InputStream是用来处理字节的,并不适合处理字符文本。Java为字符文本的输入专门提供了一套单独的类Reader,但Reader类并不是InputStream类的替换者,只是在处理字符串时简化了编程。Reader类是字符输入流的抽象类,所有字符输入流的实现都是它的子类。Reader类的具体层次结构如图
输出流
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类的对象主要用来获取文件本身的一些信息,如文件所在的目录、文件的长度、文件读写权限等。数据流可以将数据写入到文件中,文件也是数据流最常用的数据媒体。
2.1 文件的创建与删除
可以使用File类创建一个文件对象。通常使用以下3种构造方法来创建文件对象。
(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 f,String child)
该构造方法根据parent抽象路径名和child路径名字符串创建一个新File实例。
语法如下:
new File(File f,String child)
f:父路径对象。例如,D:/doc/。
child:子路径字符串。例如,letter.txt。
下面演示在D盘根目录创建名为test.txt的文件
package cn.kinggm.io;
import java.io.File;
public class CreateFile {
public static void main(String[] args) {
try {
File file=new File("D:/test.txt");
file.createNewFile();
System.out.println("文件创建成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 获取文件信息
File类提供了很多方法用于获取一些文件信息,其中常用的方法如图所示。
2.3 文件输入/输出流
程序运行期间,大部分数据都在内存中进行操作,当程序结束或关闭时,这些数据将消失。如果需要将数据永久保存,可使用文件输入/输出流与指定的文件建立连接,将需要的数据永久保存到文件中。下面介绍文件输入/输出流。
FileInputStream 类与FileOutputStream类都用来操作磁盘文件。如果用户的文件读取需求比较简单,则可以使用FilelnputStream类,该类继承自InputStream类。FileOutputStream 类与FilelnputStream类对应,提供了基本的文件写入能力。FileOutputStream类是OutputStream类的子类。
FilelnputStream类常用的构造方法如下:
- FilelnputStream(String name)。
- FilelnputStream(File file)。
第一个构造方法使用给定的文件名name创建一个FilelnputStream对象,第二个构造方法使用File对象创建FileInputStream对象。第一个构造方法比较简单,但第二个构造方法允许在把文件连接输入流之前对文件作进一步分析。
FileOutputStream类有与FilelnputStream类相同的参数构造方法,创建一个FileOutputStream对象时,可以指定不存在的文件名,但***此文件不能是一个已被其他程序打开的文件***。下面的实例就是使用FileInputStream 与FileOutputStream 类实现文件的读取与写入功能。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class CreateFile {
public static void main(String[] args) {
File file=new File("D:/test.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
FileOutputStream fileOutputStream=new FileOutputStream(file);
byte by[]="把字符写入文件!!!".getBytes();
fileOutputStream.write(by);
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
FileInputStream fileInputStream=new FileInputStream(file);
byte b[]=new byte[1024];
int len=fileInputStream.read(b);
String date=new String(b,0,len); //把字节数组转换成字符串
System.out.println(date);
fileInputStream.close();
}catch (Exception e1){
e1.printStackTrace();
}
}
}
注意
虽然Java在程序结束时自动关闭所有打开的流,但是当使用完流后,***显式地关闭所有打开的流***仍是一个好习惯。一个被打开的流有可能会用尽系统资源,这取决于平台和实现。如果没有将打开的流关闭,当另一个程序试图打开另一个流时,可能会得不到需要的资源。
2.4. FileReader 和FileWriter类
使用FileOutputStream类向文件中写入数据与使用FilelnputStream类从文件中将内容读出来,都存在一点不足,即这两个类都只提供了对字节或字节数组的读取方法。由于汉字在文件中占用两个字节,如果使用字节流,读取不好可能会出现乱码现象,此时采用字符流Reader 或Writer类即可避免这种现象。
FileReader 和FileWriter 字符流对应了FileInputStream和FileOutputStream类。FileReader 流顺序地读取文件,只要不关闭流,每次调用read()方法就顺序地读取源中其余的内容,直到源的末尾或流被关闭。
使用FileWriter和FileReader类向文件中写入内容
import javax.annotation.processing.Filer;
import java.io.*;
public class CreateFile {
public static void main(String[] args) {
File file=new File("D:/test.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
FileWriter fileWriter=new FileWriter(file);
fileWriter.write("使用FileWriter向文件写入内容");
fileWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
FileReader fileReader=new FileReader(file);
char a[]=new char[1024];
int len=fileReader.read(a);
String s=new String(a,0,len);
System.out.println(s);
}catch (Exception e1){
e1.printStackTrace();
}
}
}
3.带缓存的输入/输出流
缓存是I/O的一种性能优化。缓存流为I/O流增加了内存缓存区。有了缓存区,使得在流上执行skip()、mark()和reset()方法都成为可
能。
3.1 BufferedlnputStream与BufferedOutputStream类
BufferedInputStream 类可以对所有InputStream类进行带缓存区的包装以达到性能的优化。
BufferedInputStream类有两个构造方法:
- BufferedInputStream(InputStream in)。
- BufferedInputStream(InputStream in,int size)。
第一种形式的构造方法创建了一个带有32个字节的缓存流;
第二种形式的构造方法按指定的大小来创建缓存区。一个最优的缓存区的大小,取决于它所在的操作系统、可用的内存空间以及机器配置。
从构造方法可以看出,BufferedlnputStream对象位于InputStream类对象之前。
下图描述了字节数据读取文件的过程。
使用BufferedOutputStream 输出信息和用OutputStream输出信息完全一样,只不过BufferedOutputStream有一个flush()方法用来将缓存区的数据强制输出完。BufferedOutputStream类也有两个构造方法:
1.BufferedOutputStream(OutputStream in)。
2.BufferedOutputStream(OutputStream in,int size)。
第一种构造方法创建一个有32个字节的缓存区,第二种构造方法以指定的大小来创建缓存区。
注意
flush()方法就是用于即使在缓存区没有满的情况下,也将缓存区的内容强制写入到外设,习惯上称这个过程为刷新。flush()方法只对使用缓存区的OutputStream类的子类有效。当调用close()方法时,系统在关闭流之前,也会将缓存区中的信息刷新到磁盘文件中。
3.2 BufferedReader与BufferedWriter类
BufferedReader 类与BufferedWriter类分别继承 Reader 类与Writer类。这两个类同样具有内部缓存机制,并可以以行为单位进行输入/输出。
BufferedReader类常用的方法如下。
read()方法:读取单个字符。
readLine()方法:读取一个文本行,并将其返回为字符串。若无数据可读,则返回null。
BufferedWriter类中的方法都返回void。常用的方法如下。
write(Strings,int off,int len)方法:写入字符串的某一部分。
flush()方法:刷新该流的缓存。
newLine()方法:写入一个行分隔符。
在使用BufferedWriter类的Write()方法时,数据并没有立刻被写入输出流,而是首先进入缓存区中。
如果想立刻将缓存区中的数据写入输出流,一定要调用flush()方法。
下面使用BufferedWriter和BufferedReader类向文件中写入和读取数据
import javax.annotation.processing.Filer;
import java.io.*;
public class CreateFile {
public static void main(String[] args) {
File file=new File("D:/test.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
FileWriter fileWriter=new FileWriter(file);
BufferedWriter bufferedWriter=new BufferedWriter(fileWriter);
String s[]={"第一行数据","第二行数据","第三行数据"};
for (int i = 0; i <s.length ; i++) {
bufferedWriter.write(s[i]);
bufferedWriter.newLine();
}
bufferedWriter.close(); //先关闭BufferedWriter流
fileWriter.close(); //再关闭FileWriter流
} catch (Exception e) {
e.printStackTrace();
}
try {
FileReader fileReader=new FileReader(file);
BufferedReader bufferedReader=new BufferedReader(fileReader);
String s=null;
while ((s=bufferedReader.readLine())!=null){
System.out.println(s);
}
bufferedReader.close();
fileReader.close();
}catch (Exception e1){
e1.printStackTrace();
}
}
}
4.数据输入/输出流
数据输入/输出流(DatalnputStream类与DataOutputStream类)允许应用程序以与机器无关的方式从底层输入流中读取基本Java数据类型。也就是说,当读取一个数据时,不必再关心这个数值应当是哪种字节。
DatalnputStream 类与DataOutputStream类的构造方法如下。
1.DatalnputStream(InputStream in):使用指定的基础InputStream创建一个DatalnputStream。
2.DataOutputStream(OutputStream out):创建一个新的数据输出流,将数据写入指定基础输出流。
DataOutputStream类提供了如下3种写入字符串的方法。
writeBytes(Strings)
writeChars(String s)
writeUTF(String s)
由于Java中的字符是Unicode编码,是双字节的,writeBytes只是将字符串中的每一个字符的低字节内容写入目标设备中;而writeChars将字符串中的每一个字符的两个字节的内容都写到目标设备中;writeUTF将字符串按照UTF编码后的字节长度写入目标设备,然后才是每一个字节的UTF编码。
DatalnputStream 类只提供了一个readUTF()方法返回字符串。这是因为要在一个连续的字节流读取一个字符串,如果没有特殊的标记作为一个字符串的结尾,并且不知道这个字符串的长度,就无法知道读取到什么位置才是这个字符串的结束。DataOutputStream类中只有writeUTF()方法向目标设备中写入字符串的长度,所以也能准确地读回写入字符串。
import javax.annotation.processing.Filer;
import java.io.*;
public class CreateFile {
private String[] args;
public static void main(String[] args) {
File file = new File("C:/Users/king/Desktop/test.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
FileOutputStream fileOutputStream = new FileOutputStream(file);
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
dataOutputStream.writeUTF("嘿嘿嘿嘿嘿嘿嘿");
fileOutputStream.close();
dataOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
System.out.println(dataInputStream.readUTF());
fileInputStream.close();
dataInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.ZIP格式压缩输入/输出流
关于ZIP压缩的I/O实现,在Java的内置类中提供了非常好用的相关类,使用java.util.zip包中的ZipOutputStream与ZipInputStream类来实现文件的压缩/解压缩。如要从ZIP压缩管理文件内读取某个文件,要先找到对应该文件的“目录进入点”(从它可知该文件在ZIP文件内的位置),才能读取这个文件的内容。如果要将文件内容写入ZIP文件内,必须先写入对应于该文件的“目录进入点”,并且把要写入文件内容的位置移到此进入点所指的位置,然后再写入文件内容。
Java实现了I/O数据流与网络数据流的单一接口,因此数据的压缩、网络传输和解压缩的实现比较容易。ZipEntry类产生的对象,是用来代表一个ZIP压缩文件内的进入点(entry)。ZipInputStream类用来读取ZIP压缩格式的文件,所支持的包括已压缩及未压缩的进入点(entry)。ZipOutputStream类用来写出ZIP压缩格式的文件,而且所支持的包括已压缩及未压缩的进入点(entry)。下面介绍利用ZipEntry、ZipInputStream和ZipOutputStream3个Java类实现ZIP数据压缩的方法。
5.1压缩文件
利用ZipOutputStream类对象,可将文件压缩为.zip文件。ZipOutputStream类的构造方法如下:
ZipOutputStream(OutputStream out);
ZipOutputStream类的常用方法如图所示。
下面代码演示如何压缩文件:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Zip {
public static void main(String[] args) {
Zip z=new Zip();
try {
z.zip("D:/a.zip",new File("D:/test")); // 将D盘根目录下 test文件夹压缩为 a.zip文件 保存在D盘根目录
System.out.println("压缩完成");
} catch (Exception e) {
e.printStackTrace();
}
}
public void zip(String zipFileName,File inputFile)throws Exception{
ZipOutputStream out=new ZipOutputStream(new FileOutputStream(zipFileName));
zip(out,inputFile,"");
System.out.println("压缩中...");
out.close();
}
private void zip(ZipOutputStream out, File f, String s)throws Exception {
if(f.isDirectory()){
File fl[]=f.listFiles();
if(s.length()!=0){
out.putNextEntry(new ZipEntry(s+"/"));
}
for (int i = 0; i <fl.length ; i++) {
zip(out,fl[i],s+fl[i]);
}
}else {
out.putNextEntry(new ZipEntry(s));
FileInputStream in=new FileInputStream(f);
int b;
System.out.println(s);
while ((b=in.read())!=-1){
out.write(b);
}
in.close();
}
}
}
5.2 解压缩.zip文件
ZipInputStream类可读取ZIP压缩格式的文件,包括已压缩和未压缩的条目(entry)。
ZipInputStream类的构造方法如下:
ZiplnputStream(InputStream in)
ZipInputStream类的常用方法如图所示。
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* 实现文件[夹]解压
* @author ljheee
*
*/
public class UnZip{
/**
* 解压到指定目录
* @param zipPath
* @param descDir
*/
public static void unZipFiles(String zipPath, String descDir) throws IOException {
unZipFiles(new File(zipPath), descDir);
}
/**
* 解压文件到指定目录
* 解压后的文件名,和之前一致
* @param zipFile 待解压的zip文件
* @param descDir 指定目录
*/
@SuppressWarnings("rawtypes")
public static void unZipFiles(File zipFile, String descDir) throws IOException {
ZipFile zip = new ZipFile(zipFile,Charset.forName("GBK"));//解决中文文件夹乱码
String name = zip.getName().substring(zip.getName().lastIndexOf('\\')+1, zip.getName().lastIndexOf('.'));
File pathFile = new File(descDir+name);
if (!pathFile.exists()) {
pathFile.mkdirs();
}
for (Enumeration<? extends ZipEntry> entries = zip.entries(); entries.hasMoreElements();) {
ZipEntry entry = (ZipEntry) entries.nextElement();
String zipEntryName = entry.getName();
InputStream in = zip.getInputStream(entry);
String outPath = (descDir + name +"/"+ zipEntryName).replaceAll("\\*", "/");
// 判断路径是否存在,不存在则创建文件路径
File file = new File(outPath.substring(0, outPath.lastIndexOf('/')));
if (!file.exists()) {
file.mkdirs();
}
// 判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
if (new File(outPath).isDirectory()) {
continue;
}
// 输出文件路径信息
// System.out.println(outPath);
FileOutputStream out = new FileOutputStream(outPath);
byte[] buf1 = new byte[1024];
int len;
while ((len = in.read(buf1)) > 0) {
out.write(buf1, 0, len);
}
in.close();
out.close();
}
System.out.println("******************解压完毕********************");
return;
}
//测试
public static void main(String[] args) {
try {
unZipFiles(new File("C:/Users/king/Desktop/hello.zip"), "C:/Users/king/Desktop/"); //第一个路径为待解压文件 第二个为解压后的文件存放路径
} catch (IOException e) {
e.printStackTrace();
}
}
}