目录
数据流
一、内容回顾
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。流的本质是数据传输,Java根据数据传输特性将流抽象为各种类。在这一部分将简要介绍Java数据流的分类以及常见Java数据流的功能和用法 。
(一)Java数据流的分类
java.io包中定义了多个流类型(类或抽象类)来实现输入/输出功能。这些数据流可以按照不同的维度进行分类。
(1)按数据流向的不同可分为输入流和输出流。其中输入流是用来读取数据,输出流是用来写入数据。
(2) 按处理数据的单位划分,则可分为字符流和字节流。其中字节流主要用来处理各种字节、整数和其他简单的数据类型,支持8位的字节数据。而字符流则适于处理文本化的数据,支持16位的Unicode字符数据。
(3)按照流是否直接连接实际数据源,可分为实体流和装饰流。其中实体流直接实现将数据源,例如文件、网络、字节数组等转换为流对象,在实体流类中实现了流和数据源之间的转换,实体流类均可单独进行使用。而装饰流指不直接连接数据源,而是以其它流对象(实体流对象或装饰流对象)为基础建立的流,这类流实现了将实体流中的数据进行转换,增强流对象的读写能力。
如图1,2,3,4,分别给出了输入字节流,输出字节流,输入字符流以及输出字符流的类结构图。
图1 输入字节流类结构图
图2 输出字节流类结构图
图3 输入字符流类结构图
图4 输出字符流类结构图
从图1可以看到,InputStream是所有输入字节流的父类,它是一个抽象类。ByteArrayInputStream、StringBufferInputStream、FileInputStream是三种基本的实体流,它们分别从ByteArray、StringBuffer、和本地文件中读取数据。ObjectInputStream和所有FilterInputStream的子类都是装饰流 。
从图2可以看到,OutputStream是所有的输出字节流的父类,它是一个抽象类。ByteArrayOutputStream、FileOutputStream是两种基本的实体流,它们分别向ByteArray和本地文件中写入数据。而ObjectOutputStream和所有FilterOutputStream的子类都是装饰流。
从图3可以看到,Reader是所有输入字符流的父类,它是一个抽象类。CharArrayReader、StringReader是两种基本的实体流,它们分别从CharArray和String中读取数据。BufferedReader是一个装饰流,它和其子类负责装饰其它Reader对象。FilterReader是所有自定义具体装饰流的抽象父类。
从图4可以看到,Writer是所有的输出字符流的父类,它是一个抽象类。 CharArrayWriter、StringWriter是两种基本的实体流,它们分别向CharArray、String中写入数据。BufferedWriter则是一个提供缓冲功能的装饰流。
(二) Java数据流的功能与使用
(1)ByteArrayInputStream
ByteArrayInputStream将缓冲区中的Byte数组转换为一个InputStream。通过缓冲区中的Byte数组构造该对象。ByteArrayInputStream一般作为数据源,会使用其它装饰流包装它以提供额外的功能。
(2)StringBufferInputStream
StringBufferInputStream已过时。主要原因是它未能正确地将字符转换为字节,所以推荐使用StringReader替代它。
(3)FileInputStream
FileInputStream主要用于从文件系统中的某个文件中获取输入字节。通过一个代表文件路径的 String、File对象或者 FileDescriptor对象构造它。FileInputStream一般作为数据源,一般会使用其它装饰流包装它以提供额外的功能。
(4)PipedInputStream
通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。利用对应PipedOutputStream构造它。在多线程程序中作为数据源,一般会使用其它装饰流包装它以提供额外的功能。
(5)SequenceInputStream
SequenceInputStream将2个或者多个InputStream 对象转变为一个InputStream。使用两个InputStream 或者内部对象为InputStream 的Enumeration对象构造它。一般作为数据源,一般会使用其它装饰流包装它以提供额外的功能。
(6)DataInputStream
DataInputStream提供了Java基本数据类型的读取功能。利用一个InputStream构造它。
(7)BufferedInputStream
BufferedInputStreamv将字节读入缓存区,然后从缓存区中读取数据。可以避免频繁的IO操作。利用一个InputStream或者一个自定义的缓存区大小构造它。
(8)LineNumberInputStream
LineNumberInputStream可以跟踪输入流中的行号。可以调用其提供的getLineNumber( )和 setLineNumber(int)方法得到和设置行号。利用一个InputStream构造它。
(9)PushbackInputStream
PushbackInputStream提供向另一个输入流添加“推回 (push back)”或“取消读取 (unread)”一个字节的功能。利用一个InputStream构造它。一般很少用到这个流。
(10)ByteArrayOutputStream
ByteArrayOutputStream创建一个缓冲区,所有写入此流中的数据都被放入到此缓冲区中。无参或者使用一个初始化buffer的大小的参数构造它。通过toByteArray()方法toString()
可以得到流中的数据。一般使用其它装饰流包装它以提供额外的功能。
(11)FileOutputStream
FileOutputStream用于将字节数据写入文件。使用代表文件路径的String、File对象、 FileDescriptor对象或者代表写入的方式是否为追加的标记构造它。 一般使用其它装饰流包装它以提供额外的功能。
(12)PipedOutputStream
通常,数据由某个线程写入 PipedOutputStream 对象,并由其他线程从连接的 PipedInputStream 读取。利用PipedInputStream构造它。一般使用其它装饰流包装它以提供额外的功能。
(13)DataOutputStream
DataOutputStream提供将基本 Java 数据类型写入基础输出流中的功能。利用OutputStream构造它。
(14)PrintStream
PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。使用OutputStream和一个可选的表示缓存是否在每次换行时是否刷新的标记构造它。
(15)BufferedOutputStream
BufferedInputStream利用缓冲区将各个字节写入基础输出流中,可以避免频繁的IO操作。利用一个OutputStream或者和一个代表缓存区大小的可选参数构造它。
(16)InputStreamReader
InputStreamReader是字节流通向字符流的桥梁,它使用指定的字符集读取字节并将其解码为字符。构造它所使用的字符集可以由名称指定或显式给定,否则可能接受平台默认的字符集。为了提高效率,一般可考虑在 BufferedReader 内包装 InputStreamReader。
(17)OutputStreamWriter
OutputStreamWriter 是字符流通向字节流的桥梁,使用指定的字符集将要向其写入的字符编码为字节。构造它所使用的字符集可以由名称指定或显式给定,否则可能接受平台默认的字符集。为了提高效率,一般可考虑在 BufferedWriter 内包装OutputStreamWriter。
Reader、Writer的大部分子类流的功能和使用与InputStream、OutputStream的子类流的功能和使用比较相似,而且都有对应关系,在这里就不作一一介绍,具体内容可以查阅JAVA的API文档。
除此之外,java.io包中还包括File类和RandomAccessFile类这两个虽然不在Java流体系中但在Java的IO操作中经常用到的类。其中File类是对文件系统中文件和目录路径名的抽象表示形式,可以通过它获取文件或目录的各种元数据信息,包括文件名、文件长度、 当前文件的路径名、获得当前目录中的文件列表等方法。而RandomAccessFile支持对随机存取文件的读取和写入。
二、典型实例
例1 本例测试ByteArrayInputStream和ByteArrayOutputStream用法。
// ByteArrayStreamTest.java
import java.io.*;
public class ByteArrayStreamTest{
public static void main(String[] args){
try{
String str = "hello world!";
byte[] buffer1 = str.getBytes();
byte[] buffer2 = new byte[buffer1.length];
ByteArrayInputStream ais = new ByteArrayInputStream(buffer1);
ByteArrayOutputStream aos = new ByteArrayOutputStream();
ais.read(buffer2,0,12);
aos.write(buffer2);
byte[] buffer3 = aos.toByteArray();
for(int i = 0;i<buffer3.length;i++){
System.out.print((char)buffer3[i]);
}
FileOutputStream fs = new FileOutputStream("testByteArray.txt");
aos.writeTo(fs);
fs.close();
aos.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
程序执行结果如图5所示。
说明:
① 本例首先将字符串"hello world!"转换为字节数组buffer1,然后用其构造ByteArrayInputStream。接着ByteArrayInputStream对象ais将数据读入到字节数组buffer2,然后由ByteArrayOutputStream对象aos通过buffer2 写入。然后将aos转换为字节流buffer3 并将其遍历以字符形式输出。最后通过aos将数据写入到文件testByteArray.txt中,即文件内容为"hello world!"。
图5 例1执行结果
例2 本例测试DataInputStream和DataOutputStream用法。
// DataStreamTest.java
import java.io.*;
public class DataStreamTest {
public static void main(String[] args) {
try{
DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testDataStream.txt")));
DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("testDataStream.txt")));
dos.writeInt(5);
dos.writeBoolean(true);
dos.writeDouble(3.1415926);
String str="中国China";
dos.writeUTF(str);
dos.writeChars(str);
dos.close();
System.out.println(dis.readInt());
System.out.println(dis.readBoolean());
System.out.println(dis.readDouble());
System.out.println(dis.readUTF());
char [] c=new char[7];
for(int i=0;i<7;i++){
c[i]=dis.readChar();
}
System.out.println(new String(c,0,7));
}
catch(Exception e){
e.printStackTrace();
}
}
}
程序执行结果如图6所示。
图6 例2执行结果
说明:
①本例首先利用DataOutputStream的方法将整型,布尔,双精度,字符串类型数据分别写入到文件testDataStream.txt中,然后用DataInputStream相对应的方法将这些数据读出并显示出来。
例12-3 本例测试InputStreamReader和OutputStreamWriter用法。
// StreamReaderWriterTest.java
import java.io.*;
public class StreamReaderWriterTest {
public static void main(String[] args) {
try {
BufferedWriter bw1 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("testStreamReaderWriter1.txt")));
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("testStreamReaderWriter2.txt"),"UTF-8"));
bw1.write("中国");
bw2.write("中国");
bw1.close();
bw2.close();
BufferedReader br1 = new BufferedReader(new InputStreamReader(new FileInputStream("testStreamReaderWriter1.txt")));
BufferedReader br2 = new BufferedReader(new InputStreamReader(new FileInputStream("testStreamReaderWriter2.txt"),"UTF-8"));
System.out.println("String and GBK code of"+"testStreamReaderWriter1.txt");
String content;
while ( (content= br1.readLine()) != null) {
System.out.println(content);
byte[] bs = content.getBytes() ;
StringtoByte( bs);
}
System.out.println();
System.out.println("String and UTF-8 code of"+"testStreamReaderWriter2.txt");
while ( (content = br2.readLine()) != null) {
System.out.println(content);
byte[] bs = content.getBytes("utf-8") ;
StringtoByte( bs);
}
br1.close();
br2.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
public static void StringtoByte( byte[] b){
String temp;
for (int i = 0; i < b.length; i++) {
temp = Integer.toHexString(b[i]&0xff) ;
System.out.print(temp+" ");
}
}
}
程序执行结果如图7所示
图7 例3执行结果
说明:
①本例首先利用OutputStreamWriter将字符串“中国”以两种不同的编码方式GBK和UTF-8写入两个不同的文件。然后利用InputStreamWriter以与编码方式相对应的解码方式从两个文件将字符串读出,最后将读出的字符串对应的字节编码以16进制形式输出。
② toHexString(int i)是Integer的静态方法,作用是以十六进制的无符号整数形式返回一个整数参数的字符串表示形式。由于这个方法的形参是32位整型,而传入的实参为8位字节,因此需要以&0xff的方式屏蔽整型的高24位,保留低8位。
例4 本例测试SequenceInputStream用法。
// SequenceInputStreamTest.java
import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
public class SequenceInputStreamTest {
public static void main(String[] args) {
SequenceInputStream sis = null;
BufferedOutputStream bos = null;
try {
Vector<InputStream> vector = new Vector<InputStream>();
vector.addElement(new FileInputStream("testSequenceStream1.txt"));
vector.addElement(new FileInputStream("testSequenceStream2.txt"));
vector.addElement(new FileInputStream("testSequenceStream3.txt"));
Enumeration<InputStream> e = vector.elements();
sis = new SequenceInputStream(e);
byte[] buf = new byte[1024];
int len = 0;
System.out.println("three files contents are:");
while ((len = sis.read(buf)) != -1) {
String s = new String(buf,0,len);
System.out.println(s);
}
sis.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果如图8所示。
说明:
①本例首先生成一个由三个文件流组成的枚举对象(这三个文本文件中分别事先录入This is testSequenceStream1! This is testSequenceStream2! This is testSequenceStream2!),然后通过此枚举对象构造SequenceInputStream,从而实现将三个文件流串联的目的。最后将SequenceInputStream对象sis中的数据以字节形式读取到字节数组buf中并转换成字符串输出。
图8 例4执行结果
例5 本例测试ObjectInputStream和ObjectOutputStream的用法。
// TestObjectStream.java
import java.io.*;
import java.util.*;
class TestObject implements Serializable{
private String name;
private int age;
public TestObject(String sname, int sage){
name = sname;
age = sage;
}
public String toString(){
return name+"今年"+age;
}
}
public class TestObjectStream{
public static void main(String[] args) {
TestObject[] toSet = new TestObject[3];
toSet[0] = new TestObject("张",23);
toSet[1] = new TestObject("李",36);
toSet[2] = new TestObject("王",45);
try{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("testObjectStream.txt"));
out.writeObject(toSet);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("testObjectStream.txt"));
TestObject[] othertoSet = (TestObject[])in.readObject();
in.close();
for(int i = 0;i<othertoSet.length;i++)
System.out.println(othertoSet[i]);
}
catch (Exception e){
e.printStackTrace();
}
}
}
执行结果如图9所示。
说明:
①本例利用ObjectOutputStream将TestObject的对象数组toSet写入到文件testObjectStream.txt中,实现对象的持久存储。然后利用ObjectInputStream
将TestObject的对象数组从文件testObjectStream.txt中恢复并显示出来。
图9 例5执行结果
例6 本例测试File类的用法。
// TestFile.java
import java.io.*;
public class TestFile {
public static void main(String[] args) {
String strPath = "d:\\javadir";
File dir = new File(strPath);
File[] files = dir.listFiles();
if (files == null)
return;
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
System.out.println(files[i].getAbsolutePath().toLowerCase());
}
else {
String strFileName = files[i].getAbsolutePath().toLowerCase();
System.out.println(strFileName);
}
}
}
}
程序执行结果如图10所示。
说明:
① 本例对当前目录下的目录和文件进行遍历并显示遍历结果。需要说明的是,本例仅遍历到当前目录下的第一层目录结构,即如果当前目录下的目录还存在子目录和文件,则不能遍历显示。
图10 例6执行结果
三、实验设计
(一)实验一
(1)实验目的
让学生掌握体会DataOutputStream和DataInputStream的用法。
(2)实验要求
利用DataOutputStream的方法将字符串"中国China"以UTF,字符,字节三种形式写入到一个文本文件中,然后用DataInputStream相对应的方法将这个字符串读出并显示出来。
(3)实验效果展示
修改程序后执行的结果如图11所示。
图11 实验一效果展示
(4)实验指导
①对于以字节形式写入,请首先测试writeBytes(String s)方法,并对结果进行分析。然后尝试利用DataOutputStream从父类FilterOutputStream继承的write(byte[] b)方法将字符串以字节方式写入。
实验参考代码
//MyDataStream.java
import java.io.*;
public class MyDataStream{
public static void main(String[] args){
try{
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testDataStream.txt")));
DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream("testDataStream.txt")));
String str = "中国China";
dos.writeUTF(str);
dos.writeChars(str);
byte[] k = str.getBytes();
dos.write(k);
dos.close();
System.out.println(dis.readUTF());//UTF读取
char[] a = new char[7];
for(int i=0;i<7;i++){
a[i] = dis.readChar();
}
System.out.println(new String(a,0,7)); //按字符读取
int i = 0;
String line;
BufferedReader in = new BufferedReader(new FileReader("testDataStream.txt"));
while ((line = in.readLine()) != null) { //如果之前文件为空,则不执行输出
i++;
if(i==2){
System.out.println(line);
}//字节读取
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
(二)实验二
(1)实验目的
让学生掌握体验SequenceInputStream、OutputStreamWriter和InputStreamReader的用法。
(2)实验要求
利用字符流的形式分别向两个文本文件写入"This is file1! "和"This is file2! "。 然后将这两个文件流合并为一个SequenceInputStream,并从SequenceInputStream中以字符流的形式将两个文件中的内容读出。
(3)实验效果展示
运行结果为图12所示。
图12 实验二效果展示
(4)实验指导
①参考例3和4。
② 利用SequenceInputStream(InputStream s1,InputStream s2)构造方法。
参考实验代码
//MySequenceStream.java
import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
public class MySequenceStream{
public static void main(String[] args){
SequenceInputStream sis = null;
BufferedOutputStream bos = null;
try{
BufferedWriter bw1 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test1.txt")));
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test2.txt")));
bw1.write("This is file1!");
bw2.write("This is file2!");
bw1.close();
bw2.close();
Vector<InputStream> vector = new Vector<InputStream>();
vector.addElement(new FileInputStream("test1.txt"));
vector.addElement(new FileInputStream("test2.txt"));
Enumeration<InputStream> e = vector.elements();
sis = new SequenceInputStream(e);
byte[] buf = new byte[1024];
int len = 0;
while((len = sis.read(buf)) != -1){
String s = new String(buf,0,len);
System.out.println(s);
}
sis.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
(三)实验三
(1)实验目的
让学生掌握体验File类的用法。
(2)实验要求
对当前目录下的所有目录和文件进行遍历并显示遍历结果。
(3)实验效果展示
运行结果为图13所示。
图13 实验三效果展示
(4)实验指导
①参考例6。
② 考虑使用递归遍历。
import java.io.File;
import java.util.ArrayList;
public class MyFile {
private static ArrayList<String> filelist = new ArrayList<String>();
public static void main(String[] args) throws Exception {
String filePath = "D://Users//Mark//Desktop//Java";
getFiles(filePath);
}
//通过递归得到某一路径下所有的目录及其文件
static void getFiles(String filePath){
File root = new File(filePath);
File[] files = root.listFiles();
for(File file:files){
if(file.isDirectory()){
// 递归调用
getFiles(file.getAbsolutePath());
filelist.add(file.getAbsolutePath());
System.out.println(file.getAbsolutePath());
}
else{
System.out.println(file.getAbsolutePath());
}
}
}
}
(四)实验四
(1)实验目的
让学生掌握体验ObjectOutputStream和ObjectInputStream的用法。
(2)实验要求
Student.java定义了一个学生类,要求实现ScoreSort.java负责将若干学生的的信息写入到当前目录下的student.txt文件中。然后将student.txt文件中数据读取出来显示并按学生的成绩排序,最后将排序后的成绩重新写入student.txt文件中并再次显示。
// Student.java
import java.io.Serializable;
class Student implements Serializable{
//定义一个Student类,学号为id,姓名为name,成绩为score的对象
int id;
String name;
int score;
public void setId(int id){//设置学生的学号
this.id=id;
}
public int getId(){//得到学生的学号
return this.id;
}
public void setName(String name){//设置学生的姓名
this.name=name;
}
public String getName(){//得到学生的姓名
return name;
}
public void setScore(int score){//设置学生的成绩
this.score=score;
}
public int getScore(){//得到学生的成绩
return score;
}
public Student(int id,String name,int score) {
setId(id);
setName(name);
setScore(score);
}
}
(3)实验效果展示
运行结果为图14所示。
图14 实验四效果展示
(4)实验指导
①参考例5。
实验代码
//ScoreSort2.java
import java.awt.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
public class ScoreSort2 {
int number = 100;
Student student[];
File filename;
public ScoreSort2(){
System.err.println("输出文件student.txt的内容");
output();
student = new Student[number];
student = readFromFile();
sort(student);
System.err.println("输出文件student.txt的内容");
writeToFile(student);
output();
}
public void output() {
try{
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("student.txt"));
System.out.println("学号 姓名 成绩");//程序段3
Student[] student = (Student[])inputStream.readObject();
for(int i=0;i<student.length;i++)
System.out.println(student[i].getId()+" "+student[i].getName()+" "+student[i].getScore());
inputStream.close();
}
catch (IOException ex) {
System.err.println("打开文件失败");
}
catch (ClassNotFoundException ex) {
System.err.println("ClassNotFoundException");
}
}
public void sort(Student s[]){
for(int i=1;i<s.length;i++)
for(int j=0;j<s.length-1;j++){
if(s[j].getScore()>s[j+1].getScore()){
Student temp = s[j];
s[j] = s[j+1];
s[j+1] = temp;
}
}
}
public Student[] readFromFile() {
Student s[] = null;
try{
ObjectInputStream input = new ObjectInputStream(new FileInputStream("student.txt"));
s = (Student[])input.readObject();
student = s;
}
catch (IOException ex) {
System.err.println("ERROR");
}
catch (ClassNotFoundException ex) {
}
return s;
}
public void writeToFile(Student[] s) {
try{
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("student.txt"));//代码1;
// for(int i=0;i<s.length;i++) {
output.writeObject(s);//将s[i]学生的学号写入到文件中
//output.writeObject(s);//姓名
//output.writeObject(s);//成绩
//}
output.close();
}catch (IOException ex) {
JOptionPane.showMessageDialog(null,"读入2文件失败!");
}
}
public static void main(String[] args) {
ScoreSort scoreSort = new ScoreSort();
System.exit(0);
}
}
class NegativeException extends Exception{
NegativeException(){
}
public String toString(){
return "数字是小于或等于0";
}
}
//ScoreSort.java
import java.awt.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
public class ScoreSort {
Scanner scanner;
Student[] student;
int number;
File filename;
public ScoreSort() {
scanner = new Scanner(System.in);
System.out.print("输入学生个数:");
number = scanner.nextInt();
try{
if(number <= 0) throw new NegativeException();
input(number);
writeToFile(student);
}catch (NegativeException e) {
JOptionPane.showMessageDialog(null,"人数小于1!");
}
}
public void input(int n) {
student = new Student[n];
System.out.println("学号 姓名 成绩");
for(int i=0;i<student.length;i++){
int id = scanner.nextInt();
String name = scanner.next();
int score = scanner.nextInt();
student[i] = new Student(id,name, score);
}
}
public void writeToFile(Student[] s) {
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("student.txt"));
outputStream.writeObject(s);
outputStream.close();
}catch (IOException ex){
JOptionPane.showMessageDialog(null,"读入1文件失败!");
}
}
public static void main(String[] args) {
ScoreSort2 scoreSort = new ScoreSort2();
System.exit(0);
}
}