12.11 数据操作流
在IO包中,提供了两个与平台无关的数据操作流,分别为数据输出流(DataOutputStream)和数据输入流(DataInputStream)。通常数据输出流会按照一定的格式将数据输出,再通过数据输入流按照一定的格式将数据读入,这样可以方便地对数据进行处理。
例如,有下表所示的一组表示订单的数据
商品名称 | 商品价格 | 商品数量 |
衬衣 | 98.3 | 3 |
手套 | 30.3 | 2 |
围巾 | 50.5 | 1 |
如果要将以上数据保存到文件中,就可以使用数据输出流将内容保存到文件,然后再使用数据输入流从文件中读取进来。
12.11.1 DataOutputStream类
类DataOutputStream是OutputStream类的子类。此类的定义如下:
public class DataOutputStream extends FilterOutputStream implements DataOutput
此类继承自FilterOutputStream类(FilterOutputStream类是OutputStream类的子类),同时实现了DataOutput接口,在DataOutput接口定义了一系列的写入各种数据的方法。
- DataOutput是数据的输出接口,其中定义了各种数据的输出操作方法。例如在DataOutputStream类中的各种writeXxx()方法就是此接口定义的,但是在数据输出时一般都会直接使用DataOutputStream类,只有在对象序列化时才有可能直接操作到此接口。这点可以在Externalizable接口时可以看到!!
范例:将订单数据写入到文件order.txt中。
package org.forfan06.datademo;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class DataOutputStreamDemo01{
public static void main(String args[]){
DataOutputStream dos = null; //声明数据输出流对象
//Step 1: 指定操作文件的位置 (指定文件的保存路径)
File f = new File("E:" + File.separator + "order.txt");
//Step 2: 通过子类实例化对象,实例化数据输出流对象
dos = new DataOutputStream(new FileOutputStream(f));
//new DataOutputStream(OutputStream out);
//OutputStream out = new FileOutputStream(f);
String names[] = {"衬衣", "手套", "围巾"};
float prices[] = {98.3f, 30.3f, 50.5f};
int nums[] = {3, 2, 1};
//Step 3: 输出输入操作
for(int i = 0; i < names.length; i++){
dos.writeChars(names[i]);
dos.writeChar('\t');
dos.writeFloat(prices[i]);
dos.writeChar('\t');
dos.writeInt(nums[i]);
dos.writeChar('\n');
}
//Step 4: 关闭流
dos.close(); //关闭输出流
}
}
12.11.2 DataInputStream类
DataInputStream类是InputStream类的子类,专门负责读取使用DataOutputStream类输出的数据。此类的定义如下:
public class DataInputStream extends FilterInputStream implements DataInput
DataInputStream类继承自FilterInputStream类(FilterInputStream类是InputStream类的子类);同时实现了DataInput接口,在DataInput接口中定义了一些列读入各种数据的方法。
- DataInput接口是读取数据的操作接口,与DataOutput接口提供的各种writeXxx()方法对应,在此接口中定义了一系列的readXxx()方法,这些方法在DataInputStream类中都有实现。一般在操作时不会直接使用到此接口,而主要是用DataInputStream类完成读取功能,只有在对象序列化时才有可能直接利用此接口读取数据。这点可以在Externalizable接口时可以看到!!
范例:从order.txt中读取数据
package org.forfan06.datademo;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
public class DataInputStreamDemo01{
public static void main(String args[]) throws Exception{
DataInputStream dis = null;
//Step 1:
File f = new File("E:" + File.separator + "order.txt");
//Step 2:
dis = new DataInputStream(new FileInputStream(f));
//InputStream is = new FileInputStream(f);
//dis = new DataInputStream(is);
String name = null;
float price = 0.0f;
int num = 0;
char temp[] = null;
char c = 0;
int len = 0;
//Step 3:
try{
while(true){
temp = new char[200];
len = 0;
while((c = dis.readChar()) != '\t'){
temp[len] = c;
len++;
}
name = new String(temp, 0, len);
price = dis.readFloat();
dis.readChar(); //读出\t
num = dis.readInt();
dis.readChar();
System.out.printf("名称:%s; 价格:%5.2f; 数量:%d\n", name, price, num);
}
}catch(Exception e){ //如果读到底,则会出现异常
}
//Step 4:
dis.close();
}
}
12.12 合并流
合并流的主要功能是将两个文件的内容合并成为一个文件。
如果要实现合并流,则必须使用SequenceInputStream类。此类的常用方法有:
public SequenceInputStream(InputStream s1, InputStream s2) //使用两个输入流对象实例化本类对象
public int available() throws IOException //返回文件大小
范例:合并两个文件
package org.forfan06.sequencedemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
public class SequenceDemo01{
public static void main(String args[]) throws Exception{
InputStream is1 = null;
InputStream is2 = null; //输入流1,2
OutputStream os = null; //输出流
SequenceInputStream sis = null; //合并流
is1 = new FileInputStream("E:" + File.separator + "a.txt");
is2 = new FileInputStream("E:" + File.separator + "b.txt");
os = new FileOutputStream("E:" + File.separator + "ab.txt");
sis = new SequenceInputStream(is1, is2); //实例化合并流
int temp = 0;
while((temp = sis.read()) != -1){
os.write(temp);
}
sis.close();
is1.close();
is2.close();
os.close();
}
}
SequenceInputStream类在进行读取时, 实际上是从两个输入流中一起读取内容的。
12.13 压缩流
在Java中为了减少传输时的数据量,提供了专门的压缩流,可以将文件或文件夹压缩成ZIP、JAR、GZIP等文件形式。
12.13.1 ZIP压缩输入/输出简介
ZIP是一种很常见的压缩形式,在Java中要实现ZIP的压缩需要导入java.util.zip包。可以使用此包中的ZipFile、ZipOutputStream、ZipInputStream、ZipEntry几种类完成操作。
(1)ZIP压缩的支持类保存在java.util.zip包中。常用类有如下几种:
- Zip压缩输出流: ZipOutputStream
- Zip压缩输入流: ZipIntputStream
- Zip文件: ZipFile
- Zip实体: ZipEntry
(2)JAR压缩的支持类保存在java.util.jar包中,常用类有如下几种:
- JAR压缩输出流: JarOutputStream
- JAR压缩输入流: JarInputStream
- JAR文件:JARFile
- JAR实体: JAREntry
(3)GZIP是用于UNIX系统的文件压缩,在Linux中经常会使用到*.gz的文件,就是GZIP格式;GZIP压缩的支持类保存在java.util.zip包中,常用类有如下两个:
- GZIP压缩输出流:GZIPOutputStream
- GZIP压缩输入流: GZIPInputStream
在每一个压缩文件中都会存在多个字文件,那么每一个字文件在Java中就是用ZipEntry表示。
ZipEntry类的常用方法:
public ZipEntry(String name) //创建对象并指定要创建的ZipEntry名称
public boolean isDirectory() //判断此ZipEntry是否是目录
- 压缩的输入类/输出类定义在java.util.zip包中。 压缩的输入/输出流也属于InputStream类或OutputStream类得子类。但是却没有定义在java.io包中,而是以一种工具类得形式提供的,在操作时还需要使用java.io包的支持
12.13.2 ZipOutputStream类
如果要完成一个文件或文件夹的压缩,则要使用ZipOutputStream类。ZipOutputStream类是OutputStream类的子类,常用操作方法如下:
public ZipOutputStream(OutputStream out) //构造方法,创建新的ZIP输出流
public void putNextEntry(ZipEntry e) throws IOException //设置每一个ZipEntry对象
public void setComment(String comment) //设置ZIP文件的注释
(1)压缩文件
范例:在E盘中存在一个temp.txt文件,要将其压缩成temp.zip文件
package org.forfan06.zipdemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipOutputStreamDemo01{
public static void main(String args[]) throws Exception{
//Step 1:
File file = new File("E:" + File.separator + "temp.txt");
File zipFile = new File("E:" + F.separator + "temp.zip");
//Step 2:
InputStream input = new FileInputStream(file);
ZipOutputStream zipOut = null;
zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
zipOut.putNexEntry(new ZipEntry(file.getName)));
zipOut.setComment("@author forfan06");
//Step 3:
int temp = 0;
while((temp = input.read()) != -1){
zipOut.write(temp);
}
//Step 4:
input.close();
zipOut.close();
}
}
以上程序将temp.txt作为源文件,然后使用ZipOutputStream类将所有的压缩数据输出到temp.zip文件中,在压缩时同样采用了边读边写的方式完成:
while((temp = input.read()) != -1){
zipOut.write(temp);
}
程序运行后,会在E盘创建一个temp.zip的压缩文件。
(2)压缩文件夹
现在要压缩E盘中的文件夹Temp文件夹。
分析: 如果现在要进行压缩,则在压缩后的文件中应该存在一个Temp文件夹。在文件夹中应该存放着各个压缩文件。 所以在实现时就应该列出文件夹中的全部内容,并把每一个内容设置成ZipEntry对象,保存到压缩文件中,执行流程如下所示:
范例: 压缩一个文件夹
package org.forfan06.zipdemp;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipOutputStreamDemo02{
public static void main(String args[]) throws Exception{
//Step 1:
File file = new File("E:" + File.separator + "Temp");
File zipFile = new File("E:" + File.separator + "Temp.zip");
InputStream input = null;
ZipOutputStream zipOut = null;
//Step 2:
zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
zipOut.setComment("zip a folder!!!!!!!!!");
//Step 3:
if(file.isDirectory()){
File lists[] = file.listFiles(); //列出所有文件
for(int i = 0; i < lists.length; i++){
input = new FileInputStream(lists[i]); //设置文件输入流
//每一个被压缩的文件都用ZipEntry表示,需要为每一个压缩后的文件设置名称
zipOut.putNextEntry(new ZipEntry(file.getName() + File.separator + lists[i].getName())); //创建ZipEntry
int temp = 0;
while((temp = input.read()) != -1){
zipOut.write(temp);
}
input.close();
}
}
//Step 4:
zipOut.close();
}
}
程序首先判断给定的路径是否是文件夹,如果是文件夹,则将此文件夹中的内容使用listFiles()方法全部列出来,此方法返回File类的对象数组,然后将此File对象数组中的每一个文件进行压缩,每次压缩时都要设置一个新的ZipEntry对象。
12.13.3 ZipFile类
在Java中,每一个压缩文件都可以使用ZipFile类表示,还可以使用ZipFile类根据压缩后的文件找到每一个压缩文件中的ZipEntry并将其进行解压缩操作。
ZipFile类的常用方法如下:
ZipFile类实例化时,需要File指定的路径。下面介绍ZipFile类的基本使用。
范例:实例化ZipFile类对象
package org.forfan06.zipdemo;
import java.io.File;
import java.util.zip.ZipFile;
public class ZipFileDemo01{
public static void main(String args[]) throws Exception{
File file = new File("E:" + File.separator + "forfan06.zip"); //定位压缩文件
ZipFile zipFile = new ZipFile(file); //实例化ZipFile类的对象
System.out.println("压缩实体名称:" + zipFile.getName()); //得到压缩文件的名称
}
}
下面来利用ZipFile类进行文件的解压缩操作。
范例:解压缩文件
package org.forfan06.zipdemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/*
*E盘下有一个forfan06.zip文件。里面只包含一个forfan06.txt文件。
*/
public class ZipFileDemo02{
public static void main(String args[]) throws Exception{
//Step 1:
File file = new File("E:" + File.separator + "forfan06.zip");
//Step 2:
File outputFile = new File("E:" + File.separator + "forfan06_upzip.txt"); //定义解压缩的文件名称
ZipFile zipFile = new ZipFile(file); //实例化ZipFile对象
//Step 3:
ZipEntry entry = zipFile.getEntry("forfan06.txt"); //得到一个压缩实体
InputStream input = zipFile.getInputStream(entry); //取得ZipEntry输入流
OutputStream out = new FileOutputStream(outputFile); //实例化输出流对象
int temp = 0; //保存接收数据
while((temp = input.read()) != -1){ //读取数据
out.write(temp); //输出数据
}
//Step 4:
input.close();
out.close();
}
}
以上程序是将E盘中的forfan06.zip中的文件解压缩到forfan06_uzip.txt文件中。程序首先通过getEntry()方法根据名称取得一个压缩的ZipEntry;然后通过InputStream取得此ZipEntry的输入流。并通过循环的方式将全部内容通过输出流输出。
但是,以上程序只适合于压缩文件中只有一个ZipEntry的情况(并且还需要知道文件名。。。)!!!如果一个压缩文件中有多个文件夹或者多个ZipEntry就无法使用了。如果要操作更加复杂的压缩文件,就必须结合ZipInputStream类完成!!!!
12.13.4 ZipInputStream类
ZipInputStream类是InputStream类的一个子类。通过此类就可以方便地读取ZIP格式的压缩文件。此类的常用方法如下所示:
使用ZipInputStream类可以像ZipFile一样取得Zip压缩文件中的每一个ZipEntry!!!
范例:取得forfan06.zip中的一个ZipEntry
package org.forfan06.zipdemo;
import java.io.File;
import java.io.FileInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipInputStreamDemo01{
public static void main(String args[]) throws Exception{
File file = new File("E:" + File.separator + "forfan06.zip"); //定位压缩文件
ZipInputStream input = null; //定义压缩输入流
input = new ZipInputStream(new FileInputStream(file)); //实例化压缩输入流
ZipEntry entry = input.getNextEntry(); //取得压缩实体
System.out.println("压缩实体名称:" + entry.getName());
input.close();
}
}
通过ZipInputStream类中的getNextEntry()方法可以一次取得每一个ZipEntry,那么将ZipInputStream类与ZipFile类结合,就可以对压缩的文件夹进行解压缩操作。但是需要注意的是:在forfan06.zip文件本身是包含压缩的文件夹。所以在进行解压缩之前,应该先根据ZIP文件中的文件夹的名称在硬盘上创建好一个对应的文件夹,然后才可以把文件解压缩进行;而且在操作时对于每一个解压缩的文件都必须先创建(利用File类的createNewFile()方法来创建新文件)后再将内容输出!!!!
范例:解压缩包含多个ZipEntry的文件
package org.forfan06.zipdemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
public class ZipInputStreamDemo02{
public static void main(String args[]) throws Exception{
File file = new File("E:" + File.separator + "forfan06.zip");
File outFile = null; //定义输出的文件对象
ZipFile zipFile = new ZipFile(file);
ZipInputStream zipInput = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = null; //用于接收压缩文件中的每一个实体
InputStream input = null; //输入流,用于读取每一个ZipEntry
OutputStream out = null; //输出流,用于输出每一个实体内容
while((entry = zipInput.getNextEntry()) != null){
System.out.println("解压缩:" + entry.getName() + "文件!");
outFile = new File("E:" + File.separator + entry.getName());
if(!outFile.getParentFile().exists()){
outFile.getParentFile().mkdir();
}
if(!outFile.exists()){
outFile.createNewFile();
}
input = zipFile.getInputStream(entry);
out = new FileOutputStream(outFile);
int temp = 0;
while((temp = input.read()) != -1){
out.write(temp);
}
//应该在这里关闭输入/输出流吗?
input.close();
out.close();
}
}
}
解析:上面程序首先使用ZipInputStream类读取ZIP格式的压缩文件,然后通过getNextEntry()方法依次读取出其中的每一个ZipEntry对象的名称;并通过ZipFile类取得每一个ZipEntry的输入流对象!在进行文件输出前,判断其输出的文件夹和文件是否存在,如果不存在则创建!!!
12.14 回退流
在JavaIO中,所有的数据都是采用顺序的读取方式!!即对于一个输入流来说,都是采用从头到尾的顺序读取的。 那么现在,如果在输入流中有某些不需要的内容,要想处理掉这些不需要的内容,则只能通过程序将这些不需要的内容处理掉。
为了处理输入流中不需要的内容,在Java中提供了一种回退输入流(PushbackInputStream、PushbackReader),可以把读取进来的某些数据重新退回到输入流的缓冲区中。
在回退流中,对于不需要的数据可以使用unread()方法将内容重新送回到输入流的缓冲区中。 下面以PushbackInputStream类为例进行讲解。
上表中的3个unread()方法与InputStream(PushbackupInputStream类事InputStream类的一个子类!!)类中的3个read()方法相对应。所以回退完全是针对于输入流进行的操作。
范例:内存中有“www.csdn.net”字符串,只要输入的内容是“.”,则执行回退操作。也就是不读取“.”
package org.forfan06.pushbackdemo;
import java.io.ByteArrayInputStream;
import java.io.PushbackInputStream;
public class PushbackInputStreamDemo01{
public static void main(String args[]) throws Exception{
//Step 1:
String str = "www.csdn.net";
PushbackInputStream push = null;
ByteArrayInputStream byteInput = null;
//Step 2:
byteInput = new ByteArrayInputStream(str.getBytes());
push = new PushbackInputStream(byteInput);
//Step 3:
System.out.print("读取之后的数据为:");
int temp = 0;
while((temp = push.read()) != -1){
if(temp == '.'){
push.unread(temp);
temp = push.read();
System.out.print("{退回" + (char)temp + ")");
}else{
System.out.print((char) temp);
}
}
//Step 4:
push.close();
byteInput.close();
}
}
解析:
while((temp = push.read()) != -1){
if(temp == *.*){
push.unread(temp); //这里把 . 给 push back到缓冲区前面
temp = push.read(); //紧接着,这里有把上面push back回去的 . 又给读出来了!!!
}
//有没有都没有关系
}
这里没有死循环,当判断是 “.” 时 ,把这个 “.” push back 回去了。但是在这个if语句中紧接着又把这个 “.”通过 temp = push.read()给读出来了。。。。
然后这个if语句执行完了。跳到外面继续执行while, 此时temp = push.read()这里应该是紧接着 上面if中的那个 . 之后的字符了
==================copy from zhidao baidu===================
你要了解用回退流做什么。。。
回退就是为了下次读取的时候再读回来。。
比如<aaa chartset='utf8'> 解析输入流时 判断chartset='utf8'时 按照utf8解析。。 但是输入流读到utf8的时候前面的字符已经读过了。read()智能继续往下读。这时可以把之前读过的都回退。。
重新读所有的字符。。。。。
大概就是这么个用法吧。。
==================copy from zhidao baidu===================
12.15 字符编码
12.15.1 Java常见编码简介
在计算机里,任何的文字都是以制定的编码方式存在的。在Java程序的开发中,最常见的编码方式是:ISO8859-1、GBK/GB2312、unicode、UTF编码。
- ISO8859-1: 属于单字节编码,最多只能表示0~255 的字符范围。 主要在英文上应用。
- GBK/GB2312: 中文的国际编码,专门用来表示汉字。;是双字节编码。如果在此编码中出现中文,则使用IOS8859-1编码,GBK可以表示简体中文和繁体中文;而GBK2312 只能表示简体中文;GBK兼容GB2312
- unicode: Java中使用此编码方法,是最标准的一种编码,使用十六进制表示编码。 但是此编码不兼容IOS8859-1编码
- UTF: 由于unicode不支持ISO8859-1编码,而且容易占用更多的空间,而且对于英文字母也需要使用两个字节编码,这样使用unicode不便于传输和存储。因为产生了UTF编码。 UTF编码兼容了ISO8859-1编码,同时也可以用来表示所有的语言自负,不过UTF编码是不定长编码,每一个字符的长度为1~6个字节不等。 一般在中文网页使用此编码,可以节省空间
在程序中如果处理不好字符的编码,则就有可能出现乱码问题。如果本机的默认编码是GBK,但在程序中使用了ISO8859-1编码,则会出现字符的乱码!
如果要避免产生乱码,则程序的编码与本地的默认编码保持一致即可。而本地的默认编码,可以使用System类来获得!!!!
12.15.2 得到本机的编码显示
获取系统的默认编码。需要使用到System类!!!!
public static Properties getProperty()
范例:使用System类得到JVM的默认编码
package org.forfan06.charsetdemo;
public class CharSetDemo01{
public static void main(String args[]){
System.out.println("系统默认编码:" + System.getProperty("file.encoding"));
}
}
12.15.3 乱码产生
假如,本地的默认编码是GBK;通过ISO8859-1编码对文字进行编码转换。
如果要实现编码转换可以使用String类中的getBytes(String charset)方法,此方法可以设置指定的编码。其格式如下:
pubcli byte[] getBytes(String charset)
范例: 使用ISO8859-1编码
package org.forfan06.charsetdemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class CharSetDemo02{
public static void main(String args[]) throws Exception{
File f = new File("E:" + File.separator + "test.txt");
OutputStream out = new FileOutputStream(f);
byte[] b = "陌生人,你好!".getBytes("ISO8859-1");
out.write(b);
out.close();
}
}
乱码产生的原因之一:
输出内容的编码(例如:程序指定)与接收内容的编码(如本机环境默认)不一致!!!!!!