一、流的概念
1、定义:流是通过一定的传播路径从源传递到目的地字节序列。
java以流的形式处理所有的输入和输出。
2、流的分类:
输入流:在java中,可从中读出一系列字节的对象称为“输入流”。
输出流:能向其中写入一系列字节的对象称为“输出流”。
我们将向输出流写入数据信息的叫数据源;我们将从输入流读取数据信息的叫目的地。
数据源和目的地可以是(而且经常)文件,但它们也可能是一个网络连接(),
甚至一个内存块。所有数据最终都要保存为一系列字节。
java中以抽象类InputStream 和OutputStreaml来实现单字节的字符。
以抽象类Reader和Writer(它们的子类)来实现来专门处理采用Unicode(每个字符都使用了双字节)格式保存的信息。
3、流的抽象类分析
InputStream类
InputStream类提供了一个抽象方法:
- public abstract int read() throws IOException
方法说明:返回可读取一个字节,并将它返回;假如它遇到输入源的结尾,就会返回一个-1。
通常覆盖这个方法,以便提供更有用的功能。例如:在FileInputStream类中,这个方法会从一个文件中读入一个字节。System.in是InputStream的一个字类预先定义好的对象,允许我们从键盘读取信息。
InputStream类也提供了非抽象方法,用于读取一个字节数组,或者跳过一定数量的字节。这些方法会调用抽象的read方法,所以子类只需覆盖一个方法。
OutputStream类
OutputStream类定义了下述抽象方法:
- public abstract void write(int b) throws IOException
方法说明:它可将一个字节写到指定的输出位置。
相关知识联系:
无论read还是write方法都可能造成一个线程的延误,直到字节被实际读出或写入为止。换言之,假如字节不能马上读出或写入(通常是由于繁忙的网络连接),java就会暂停(挂起)包含了此次调用的那个线程。这样一来,其它线程就有机会在方法等待期间,做一些更有用的事情。
重点提示:
完成了对数据流的读取或写入操作,请记住用恰当的close方法将其关闭,这是由于流会占用操作系统有限的资源。特别指出的是,假如你没有关闭一个文件,最后一个字节包便可能永远都不会投递出去。也可用flush方法来人工刷新(清空)输出缓冲。
由于程序很少需要读写字节流,更多的是对数字、字串以及对象等等。JAVA为我们提供了多种流类,均是从基本的InputStream和OutputStream类衍生出来的。利用这些类,便可直接对常见格式的数据进行操作;而不必操作那些原始的字节流。
二、文件流
1、定义:FileInputStream和FileOutputStream(文件输入/输出流)是我们能对一个磁盘文件涉及的数据以流的形式流进行输入及输出处理。
2、构造一个文件流:
构造方式一:在构件器中指定文件名,或者完整的路径名。
- FileInputStream fin=new FileInputStream("employee.dat");
提示:由于对java.io中的所有类来说,它们对相对路径名进行解释时,都以当前的工作目录为准,所以一般都需要提前了解当前的工作目录是什么。为获知这一信息,请调整System.getProperty("user.dir").
构造方式二:也可以使用一个File对象构造文件流。
- File f=new File("employee");
- FileInputStream fin=new FileInputStream(f);
前提知识:File 类
1)、File类的功能:用于访问文件和目录对象。它使用主操作系统的文件命名惯例。通过File类的方法可删除、重命名文件。这些方法检查文件的读和写权限。通过File类的目录方法来创建、删除、重命名和列出目录。
2)、构造File类
- File(String pathname) //通过将给定的路径名字转换成一个抽象的路径名来创建File类对象;
- File(String parent,String child) //由父路径名和子路径名的字符串创建出File类的对象;
- File(File parent,String child) //由父File类的对象和子路径创建出File类的对象
演示代码:
- File file;
- file=new File("javaFile") //路径名;
- file=new File("//","javaFile") //父和子路径名;
- File dir=new File("//");
- file=new File(dir,"javaFile"); //File对象和路径名;
3)、File类的常用方法:参考API
4)、程序演示:
- //----------------------------* *----------------------------------------
- import java.io.*;
- import javax.swing.*;
- public class AA
- {
- public static void main(String args[])
- {
- try
- {
- File f=new File("c://mjp//","a.txt");
- boolean b=f.exists();
- System.out.println("getName() = "+f.getName());
- System.out.println("getParent() = "+f.getParent());
- System.out.println("exists() = "+String.valueOf(b));
- System.out.println("mkdir() = "+String.valueOf(f.mkdir()));
- System.out.println("List() = "+f.list());
- System.out.println("getPath() = "+f.getPath());
- }
- catch(Exception e){}
- }
- }
3、文件流代码演示:
- -------------------* 向一个文件中输入信息 *---------------------------
- import java.io.*;
- import java.lang.*;
- public class AA
- {
- public static void main(String args[])
- {
- try
- {
- FileInputStream fin=new FileInputStream("a.txt");
- byte[] b=new byte[100];
- fin.read(b);
- System.out.println("Read From file of a.txt is : "+b[0]);
- System.out.println("Read From file of a.txt is : "+new String(b));
- /*int a=fin.read();
- System.out.println("Read From file of a.txt is : "+ String.valueOf(a));*/
- }
- catch(Exception e)
- {}
- }
- }
- -------------------* 向一个文件输入信息,然后从中读出*-------------------
- import java.io.*;
- import java.lang.*;
- public class AA
- {
- public static void main(String args[])
- {
- try
- {
- //输入信息部分
- FileOutputStream fin1=new FileOutputStream("a.txt");
- byte[] b=new byte[10];
- System.out.println("please input message:");
- System.in.read(b);
- fin1.write(b);
- //输出信息部分
- FileInputStream fin2=new FileInputStream("a.txt");
- fin2.read(b);
- for(int i=0;i<b.length&&b[i]!=0;i++)
- System.out.println(""+b[i]);
- System.out.println("Read From file of a.txt is : "+new String(b));
- fin1.close();
- fin2.close();
- }
- catch(Exception e)
- {}
- }
- }
三、过滤流
抽象的InputStream和OutputStream类允许我们对字串及数字进行读写。为达到这个目的,还需要功能更多的子类。例如:DateInputStream和DataOutputStream允许我们对所有基本的java类型进行读写。
文件流类与抽象的InputStream和OutputStream类相似,这些类也只支持字节级的读写操作。换言之,只能从fin对象中读取字符和字节数组。byte b=fin.read(),他们没有提供专门的数值类型,所以DataInputStream没有办法从一个文件中获取数字。
解决方案:java给流职责分工,某些流(FileInputStream)负责从文件或另一些更特殊的地方读入字节数据。而另一些流
(DataInputStream、PrintWriter)负责将字节“组装”成更有用的数据类型。必须综合运用这两种流,将其合并成所谓的"过滤流(FilteredStreams)",方法是将一个现成的流传递给另一个流的构建器。
解决方案举例:从一个文件中读取数字
步骤:
1)创建一个FileInputStream;
2)将其传递给一个DataInputStream的构造函数;
代码:
1)
- FileInputStream fin=new FileInputStream("a.txt");
2)
- DataInputStream din=new DataInputStream(fin);
- double s=din.readDouble();
演示程序:
- ---------------------------------* 从一个文件中读取数字 *-------------------------------------
- import java.io.*;
- import java.lang.*;
- public class B
- {
- public static void main(String args[])
- {
- try
- {
- FileOutputStream fin1=new FileOutputStream("a.txt");
- DataOutputStream din1=new DataOutputStream(fin1);
- din1.writeDouble(102);
- FileInputStream fin2=new FileInputStream("a.txt");
- DataInputStream din2=new DataInputStream(fin2);
- Double d= new Double(din2.readDouble());
- System.out.println("read message is : "+d.toString());
- din2.close();
- fin2.close();
- din1.close();
- fin1.close();
- }
- catch(Exception e)
- {}
- }
- }
补充知识:
默认情况下,流不会进行缓冲。即每读一次,都会要求操作系统提供一个字节。通过BufferedInputStream和
BufferedOutputStream对流构建器进行过滤分层,实现缓冲。
1、构造函数:
- BufferedInputStream(InputStream in)
- BufferedInputStream(InputStream in, int size) //size:缓冲区的大小
2、代码演示:
- BufferedInputStream bis=new BufferedInputStream(System.in);
- BufferedInputStream bis=new BufferedInputStream(System.in ,100);
3、程序举例:
- //-----------*对a.txt文件进行缓冲以及数据输入操作*---------------
- import java.io.*;
- import java.util.*;
- import java.lang.*;
- public class BB
- {
- public static void main(String args[])
- {
- try
- {
- DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("a.txt")));
- byte[] c=new byte[10];
- while(dis.read(c)!=-1)
- {
- for(int i=0;i<10;i++)
- System.out.println("the message is "+String.valueOf(c[i]));}
- }catch(Exception e){}
- }
- }
四、随机存取文件
上述演示中,对文件的读写,总是从头开始,当向文件中存取信息,会产生原文信息被覆盖的情况。同时希望可从文件的任意位置读取。
RandomAccessFile(随机存取文件)是一种特殊的流类,可用它查找或写入文件的任何地方的数据。它同时实现了DataInput和DataOutput两个接口。磁盘文件采用的是随机存取方式,但来自一个网络的数据流却不是这样。打开一个随机文件后,要么只对其进行读操作。要么需要同时进行读写。
1、构造一个随机存取文件类
- RandomAccessFile(String name,String mode) //name:系统专用文件名
- mode:"r"代表只读;"rw"代表可读写
- RandomAccessFile(File file ,String mode) //file:封装了特殊系统专用文件名的一个File对象
- mode:"r"代表只读;"rw"代表可读写
代码演示:
1)
- RandomAccessFile raf=new RandomAccessFile("a.txt","rw");
2)
- File fin=new File("a.txt");
- RandomAccessFile raf=new RandomAccessFile(fin,"rw");
2、常用方法:
- long getFilePointer() //返回当前的文件指针位置;
- void seek(long pos) //将文件指针设为自文件开头,第pos个字节的位置;
- long length() //返回文件长度,以字节为单位;
3、演示程序:
- //---------* 向一个a.txt文件中追加信息,然后读取文件 *----------------
- import java.io.*;
- public class C
- {
- public static void main(String args[])
- {
- try
- {
- RandomAccessFile raf=new RandomAccessFile("a.txt","rw");
- raf.seek(raf.length()+1);
- System.out.println("the first pointer is "+raf.getFilePointer());
- raf.writeBytes("Hello jiang wei !");
- System.out.println("the second pointer is "+raf.getFilePointer());
- raf.seek(0);
- byte[] b=new byte[(int)raf.length()];
- raf.read(b);
- String s=new String(b);
- System.out.println("the input is " +s);
- System.out.println("the second pointer is "+raf.getFilePointer());
- raf.close();
- }
- catch(Exception e)
- {}
- }
- }
补充知识:
********* 文本流*********
以上讨论的都是二进制输入和输出。尽管二进制I/O的速度非常快,效率也很高。但人眼无法识别。采用文本格式可以解决此问题。例如:1234用二进制保存,他会作为一系列那变起意的字节写入:00 00 04 D2(用十六进制),采用文本格式,保存的就是一个简单的字串“1234”由于java采用了Unicode字符,但java目前运行的大多数环境中,他们使用的都是自己的一套字符编码。可能是单字节、双字节或者是可变字节方案。比如在Windows中,字串需要用ASCII格式写入,亦即31 32 33 34 ,中间没有附加的0值字节。如果将Unicode编码写进一个文本文件,那么使用主机环境的各种工具,通常很难凭人眼辨别出结果文件的内容。为解决此问题,就像早先指出的那样,java现在提供了一套过滤流,可用来弥补Unicode编码文字与本机操作系统采用的字符编码间的裂缝。所有这些类都从抽象类Reader和Writer中衍生出来,而且名字与以前二进制数据采用的名字相同。
一具体流类分析
1、InputStreamReader:可将采用特殊字符编码方案的、包含了字节的一个输入流转换成一个Reader,它产生的将是Unicode字符。
1) 构造函数:
- InputStreamReader(InputStream in) //采用主机系统默认的字符编码方案。
- nputStreamReader(InputStream in, String enc)
- throws UnsupportedEncodingException //指定一种不同的编码方案。
2)代码实现:
- InputStreamReader in=new InputStreamReader(System.in);
- InputStreamReader in=new InputStreamReader(new FileInputStream("a.txt"),"GB2312");
2、OutputStreamWriter:可将一个Unicode字符流转换成采用特殊字符编码方案的字节流。
1)构造函数:
- OutputStreamWriter(OutputStream out) //采用主机系统默认的字符编码方案。
- OutputStreamWriter(OutputStream out, String enc)
- throws UnsupportedEncodingException//指定一种不同的编码方案。
2)代码实现:
- OutputStreamWriter(new FileOutputStream("a.txt"));
- OutputStreamWriter(new FileOutputStream("a.txt"),"GB2312");
3、演示程序:
- //-------------*令一个reader从控制台读入按键,并将其自动转换成Unicode*---------------------------
- import java.io.*;
- public class D
- {
- public static void main(String args[])
- {
- try{
- char[] c=new char[10];
- System.out.println("please input message : ");
- InputStreamReader isr=new InputStreamReader(System.in);
- isr.read(c,0,10 );
- System.out.println("str is "+String.valueOf(c));
- isr.close();
- }
- catch(IOException e){}
- }
- }
- //--------------------*文件信息“0123456789”输入 ,然后输出*-----------------------------------------------
- import java.io.*;
- public class E
- {
- public static void main(String args[])
- {
- try{
- char[] c=new char[100];
- OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("a.txt"));
- osw.write("0123456789",0,10);
- osw.close();
- InputStreamReader isr=new InputStreamReader(new FileInputStream("a.txt"));
- isr.read(c,0,100);
- for(int i=0;i<10;i++)
- {
- System.out.print("the char["+i+"]");
- System.out.println(c[i]);
- }
- System.out.print(c);
- System.out.println("the a.txt is : "+String.valueOf(c));
- isr.close();
- }
- catch(IOException e){}
- }
- }
注意:由于经常需要将一个Reader或Writer同文件联系在一起,所以java专门提供了两个类:FileReader和FileWriter。
构造函数:
- FileWriter(File file)
- FileWriter(String fileName)
- FileWriter(String fileName, boolean append) //append :是否可追加信息
通常
- FileWriter out=new FileWriter("a.txt");
等价于
- OutputStreamWriter out=new OutputStreamWriter(new FileOutputStream("a.txt"));
二、文本输出
进行文本输出时,通常需要使用PrintWriter,它可通过文本格式打印(显示)字串及数字。提供了有用的输出方法,但却没有定义目的地。因此,一个PrintWriter必须同一个目标writer合并到一起。
构造函数:
- PrintWriter(Writer out) //新建一个PrintWriter,不自动进行行清空(刷新);
- PrintWriter(Writer out,boolean autoFlush) //autoFlush为true,则Println()方法会将输出缓冲清空;
- PrintWriter(OutputStream out) //不自动进行行清空(刷新),构造函数自动增加一个OutputStreamWriter,
以便将Unicode字符转换成流内的字节;
- PrintWriter(OutputStream out,boolean autoFlush) //autoFlush为true,则Println()方法会将输出缓冲清空;
代码演示:
- PrintWriter out=new PrintWriter(new FileWriter("a.txt"));
- PrintWriter out=new PrintWriter(new FileOutputStream("a.txt"));
为了向一个PrintWriter进行写操作,需要使用随System.out使用的相同的Print及Println方法。可用这些方法显示数字(包括 int,short ,long ,float和double等等)、字符、布尔值、字串以及对象。据题参考API(println()可将目标系统正确的换行字符添加到当前行。通过调用System.getProperty("line.separator")获取;如果将writer设为"自动清空模式",那么一旦调用println(),缓冲内的所有字符都会发生至它们的目的地,缺省时“自动清空”为false)
演示程序
- //-----------------------------------------*向a.txt中写文本信息*-----------------------------------
- import java.io.*;
- public class F
- {
- public static void main(String args[])
- {
- try{
- PrintWriter pw=new PrintWriter(new FileWriter("a.txt"));
- pw.print("Hello World");
- pw.close();
- /*InputStreamReader pr=new InputStreamReader(new FileInputStream("a.txt"));
- char[] c=new char[10];
- pr.read(c,0,10);
- System.out.println("message is"+String.valueOf(c));
- pr.close();*/
- }catch(IOException e){}
- }
- }
小结:
想以二进制格式写入数据,使用DataOutputStream;
想用文本格式输出 ,使用PrintWriter;
三、读文本输入
在java中,唯一用来处理文字输入的是BufferedReader方法。该方法又含了另一个方法,名为readLine(),可用它读取整行文本。
我们需要将一个BufferReader同一个输入源合并起来。
1、构造函数:
- BufferedReader(Reader in)
- BufferedReader(Reader in, int sz) //sz:输入缓冲的大小
2、代码演示:
- BufferedReader in=new BufferedReader(new FileReader("a.txt"));//FileReader将字节转换成Unicode字符。
对于其他的输入源,需要使用InputStreamReader ,它和PrintWriter(自动增加一个OutputStreamWriter,以便将Unicode字符转换成流内的字节)不同,它不自动提供转换方法弥补字节与Unicode字符间的裂缝;
- BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
3、演示程序:
- //------------------*从a.txt中读出文本信息*-----------------------------------------------
- import java.io.*;
- public class F
- {
- public static void main(String args[])
- {
- try{
- BufferedReader br=new BufferedReader(new FileReader("a.txt"));
- String s;
- while((s=br.readLine())!=null)
- {
- System.out.println("the message is :"+s);
- }
- }catch(Exception e){}
- }
- }
- 参考程序
- //---------------*从a.txt中读单个数字 *------------------------------------------------------
- import java.io.*;
- import java.util.*;
- import java.lang.*;
- public class G
- {
- public static void main(String args[])
- {
- try{
- BufferedReader br=new BufferedReader(new FileReader("a.txt"));
- String s=br.readLine();
- String name;
- while(s!=null)
- {
- StringTokenizer st=new StringTokenizer(s,"/ :");
- while(st.countTokens()!=0)
- {
- name=st.nextToken();
- System.out.println("name is : " +Integer.valueOf(name));}
- System.out.println("the message is :"+s);
- s=br.readLine();
- }
- }catch(Exception e){}
- }
- }
四、对象流
如果需要保存相同类型的数据,使用长度固定的记录格式无疑是个很好的选择,
在面向对象的程序中创建的对象极少出现类型相同的情况。 例如:一个Employee类型的数组,
实际可能包含了Employee的各个子类。 如果想保存包含了类对象信息的文件,首先必须保存每个对象类型,
然后用来定义对象当前状态的数据。需要将这种信息从一个文件读出的时候,必须:
1)读取对象类型
2)创建那种类型的一个空白对象
3)在其中填充我们保存在文件中的数据
java采用一种对象序列化的机制,实现上述步骤,具体过程
保存到磁盘的所有对象都获得一个序列号(1,2,3等等)
将一个对象存盘时,调查是否以保存了与之相同的对象。
如果以前保存过,只需写入“与以前保存的对象有相同的序列号x”标记;否则,保存它的所有数据
要读回数据时,将上述过程简单的逆转即可。对于载入的每个对象,都要注意它的序列号,
并记住将它存放在内存的什么位置。如果遇到“与以前保存的对象有相同的序列号x”标记,
就根据序列号x,检查对象放在什么位置,并设置对象引用,令其指向那个内存地址。
一、对象流类分析
1、ObjectOutputStream
构造函数:
- ObjectOutputStream()
- ObjectOutputStream(OutputStream out) //创建一个ObjectOutputStream,以便将对象写入指定的输出流。
常用方法:
- void writeObject(Object obj) //将指定对象写入对象输出流。对象的类、类的签名以及为标记得临时
的任何字段的值都会写入,同时写入的还包括它的所有超类的非静态字段。
2、ObjectInputStream
构造函数:
- ObjectInputStream()
- ObjectInputStream(InputStream in) //创建一个ObjectInputStream,以便从指定的InputStream 中读回对象信息。
常用方法: Object readObject() //从 ObjectInputStream中读取一个对象。他会同时读回对象的类;
类的签名以及为标记得临时的任何字段的值都会写入,同时写入的还包括它的所有超类的非静态字段。它会撤销序列化,
以便恢复多个对象引用。
注意:1)、用writeObject和readObject方法只能读写“对象”,不能读写数字。如果想读写数字,
需要使用象writeInt/readInt等方法。java中,字串和数组均是对象,能使用对象流
2)、对象流中保存和恢复的任何一个类,必须是实现了Serializable(可序列化)接口的类。
3、代码演示:
- ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("a.txt"));
- out.writerObject(new JFrame("the first Frame "));
- out.writerObject(new JLable(" Hello World !"));
- ObjectInputStream in=new ObjectInputStream(new FileInputStream("a.txt"));
- Object objects=in.readObject();//注意:在读回对象时,必须对已保存对象的数量、
它们的顺序以及它们的类型做到心中有数。对readObject()的每一次调用都会读入类型为Object的另一个对象。
因此需要将其造型为恰当的类型。
4、程序演示:
- //--------------------------------* 将JFrame对象写入a.txt,然后读出*------------------------------
- import java.io.*;
- import javax.swing.*;
- public class H
- {
- public static void main(String args[])
- {
- try{
- ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("a.txt"));
- out.writeObject(new JFrame("the First JFrame"));
- out.close();
- ObjectInputStream in=new ObjectInputStream(new FileInputStream("a.txt"));
- JFrame f=(JFrame)in.readObject();
- System.out.println("The class is : "+f.getClass().getName());
- }catch(Exception e){}
- }
- }
小结:
二进制数据的读写
DataInputStream和DataOutputStream
可以完成对所有基本Java类型的读写;
FileInputStream和FileOutputStream
对一个磁盘文件涉及的数据流进行输入输出处理,是字节级的读写操作;
Java的流不具备预读和缓冲功能,需要额外处理
BufferInputStram对流进行缓冲处理;
PushbackInputStream对流进行预读处理;
写文本输出
二进制写入数据:DataOutputStream;
文本格式写入数据:PrintWriter;
读文本输入:
二进制读入数据:DataInputStream;
文本格式读入数据:BufferReader;