黑马程序员_IO中其他流对象

来源:互联网 发布:《算法》读书笔记 编辑:程序博客网 时间:2024/05/17 04:05

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------- 

IO流中其他对象

 

PrintWriterPrintStream

PrintWriterPrintStream

打印流,为其他输出流添加了功能,是它们能够方便的打印各种数据值的表现形式。该流提供了答应方法,可以将各种数据类型的数据都原样打印。

字节打印流PrintStream,构造函数可以接受的类型:

1File对象     File 

2、字符串路径   String

3、字节输出流   OutputStream

字符打印流PtineWrite,构造函数可以接受的类型是:

1File对象     File 

2、字符串路径   String

3、字节输出流   OutputStream

4、字符输出流   Writer

在开发应用中,字符打印流比较常用。注意,这个应用时用来输出的,别记错了。

以字符打印流来说,它是Writer的子类,专门用于打印各种数值,它有方法write,但是也自己一个很强的方法println,就是打印。不如当我从键盘读取数据,打印向控制台的时候,就可以使用:

       BufferedReader bufr =

              new BufferedReader(new InputStreamReader(System.in));

       //建立的打印流对象

       PrintWriter pw = new PrintWriter(System.out);

       String line =null;

       while ((line=bufr.readLine())!=null){

           if("over".equals(line))

              break;

           pw.println(line.toUpperCase());

           //注意刷新

           pw.flush();

看上述程序,如果我们在输出的时候,用write也是可以的,但是打印出来的数据就会变样,没有换行,但是这里需要用到换行,PrintWriter里是没有的。所以我们使用它自己的输出方法println,还自带换行。但是注意要刷新流。而打印流有一个构造方法,可以解决自动刷新功能,就是在构造函数的参数中,加入boolean变量,当为true是,就会自动刷新,否则不会,但只对方法中的printlnprintfformat有效。如下:

PrintWriter pw = new PrintWriter(System.outtrue)

这样使用println的时候,就不用在使用刷新方法了。还有一点要注意,这个构造方法只能用在目的地是流的构造方法中,如果目的地是一个文件,就不可以使用了。

但是我们可以把文件编程流对象,这样既可以把数据打印到文件里,又可以自动刷新。

PrintWriter pw = new PrintWriter(new FileWriterdemo.txttrue)

 

 

序列流SequenceInputStream

序列流,就表示其他输入流的串联,它从输入流的有序集合开始,从第一个输入流开始,知道达到文件末尾,然后从第二个输入流读取,以此类推,知道叨叨最后一个输入流的文件。简单来说,就是把多个小的输入流变成一个大的输入流。它有两个构造函数:

         SequenceInputStream(InputStreams1 , InputStream s2);

这是接收两个参数的序列流,建立对象的时候,把两个输入流的对象传入即可,然后直接使用序列流就好,序列流是InputSTream的子类,所以有读的方法,也可修饰。

SequenceInputStream(Enumeration<? extends InputStream> e);

当我们要多个流进行序列读取的时候,就要用到这个。Enumeration,用Vector集合获取。程序如下:   

Vector<FileInputStream> v = newVector<FileInputStream>();

       v.add(new FileInputStream("c:\\1.txt"));

       v.add(new FileInputStream("c:\\2.txt"));

       v.add(new FileInputStream("c:\\3.txt"));

       //获取Enumeration,用elements方法

       Enumeration<FileInputStream> e = v.elements();

       //建立序列流对象

       SequenceInputStreamsis =new SequenceInputStream(e);

       FileOutputStream fos = new FileOutputStream("c:\\4.txt");

       byte[] buf =newbyte[1024];

       int num =0;

       while((num=sis.read(buf))!=-1){

           fos.write(buf, 0, num); 

       }

       fos.close();

       sis.close();

 

序列流也是InputStream的子类,所以用Inputstream的方法即可。需要注意的是,多个文件时,Enumeration的获取方法要记住。

 

 

有一个联系比较有用,就是文件分割。当我们要操作的文件过大的时候,但是内存不允许我们建立过大的中间变量(比如读取的时候的数组),我们就要把文件分割成多部分进行分别的传输。说一下思路:

1、读取流关联原文件

2、循环中建立输出流对象,使用read(数组)的方法,数组大小为几兆即可。就是说,每一次循环都要建立一个输出流,把每次的数据装入一个新的文件,这里要注意的是,要定义一个计数器,一遍于区分分割的文件。比如,1.txt2.txt等等。

3、记得关流。

当然,既然有分割就有合并,合并方法就是上面序列流的方法了。

 

 

ObjectInputStreamObjectOutputStream

这个流是可以直接操作对象的流。一般我们在操作对象的时候,都是将对象加载到堆内存中,一旦使用完了就会被回收,而这个流可以直接把对象存到硬盘上,这个叫做对象的持久化,也叫做对象的序列化。当然对象中有相应的数据,成员变量和成员函数,当这个流直接把对象存到硬盘上,对象就存到硬盘上了,我们使用的时候,直接读取这硬盘上的数据就可以了。

ObjectOutputStream来说,它的构造函数会接收一个OutputStream对象,明确目的。这里面的方法和OoutputStream中方法都是一样,但是注意几个特有的方法。

它有可以操作基本数据类型的写方法,比如writeIntint value),writeCharchar ch)等。当然还有操作对象的方法,writeObjectObject obj)。

注意一点:有ObjectOutputStream存的对象,必须有ObjectInputStream读取。这里面的readObject方法也是一个个往下读,和OutputStreamread机制是一样的。

然后我们建立ObjectOutputStream的对象即可。

ObjectOutputStream oos = newObjectOutputStream(new FileInputStream(“obj.object”));

oos.writeObject(new Person());

但此时,对象不能被直接序列化,想要序列化,必须实现一个Serializable接口,实现它就可进行序列化。这个接口中没有方法,在java中就相当于标记接口,就是说对象实现了这个接口,就是给这个对象打上标记,表明其可序列化。

而每个被序列化的对象,就需要一个序列号,在标记这个对象,是这个对象唯一。在程序中是这样表示的:static  final long  serialVersioUID = 42L。这个序列号的产生,是根据对象中成员用某种算法算出来的。比如A对象被算出来一个序列号45L,当A对象所属的类中我们进行修改后,在生产对象,那么这个序列号就会改变,因为其中的成员有了变动,如果成员不变动的话,那么就以同一个对象了。

ObjectInputStream读取的时候,那个对象要有对应的class文件,这样才会读取成功,因为读取的时候,我们需要强转到我们需要的那个对象中。此时,如果把原有的class文件删除,在原有对象的类的基础上加上一个新成员变量,在生产同名的class文件,这是,即便是有我们需要的class文件,但是因为序列号不一样了,所以就会读取是把。

这个过程证明了两点:一、序列号是根据成员计算出来的,二、读取时要有原有的class文件。

当然,我们可以自定义自己的序列号,只需把一条语句中加入到我们的对象所属的类中即可:

public  static  final long serialVersioUID = 42L

这样,就相当于把这类所属的对象的序列号固定了,即便是改变了类中的成员,序列号也是不变的。

成员中的静态成员不能被序列号,因为静态成员时随类产生的,我们对象序列化目标地对象,内存中位置和产生时间就不一样。

如果想把非静态成员序列化,那么就在前面加上关键字 transient

 

 

 

管道流PipedInputStreamPipedOutputStream

一般我们IO中操作,输出流和输入流是分开的,弄一个中间部分是两部连接起来,比如我们是用的数组。管道流就是把输入和输出连接起来。通常,数据由某个线程从PipedInputStream对象读取,并有其他线程写入到相应的PipedOutputStream,不见对两个对象使用单个线程,因为这样可能死锁线程。因为管道输入流和管道输出流相连接,而输出方面是read方法,此方法是阻塞式方法,如果输入端没有数据输入,那么次方法就会造成当前线程等待,所以单线程执行管道流会死锁。

管道流是IO流中涉及到多线程的流对象。

管道流就是把输入和输出连接上,所以建立连接方法:

一、PipedInputStream的构造函数中传入PipedOutputStream的对象。

二、用PipedInputStream中的connectPiedOutputStream p)方法,是输入和输出连接上

看示例程序:

PipedInputStream in= new PipedInputStream()

PipedOutptuStream out = newPipedOutputStream();

in.connect(out);

 

 

 

RandomAccessFile

随机访问文件,自身具备读写的方法。此类的实例支持对随机访问文件的读取和写入操作。随机访问文件的行为类似存储在文件系统中的一个大型byte数组,存在指向该隐含数组的指针,称为文件指针,输入操作从文件指针开始读取字节,并随着字节的读取而前移此文件指针。

RandomAccessFile,该类不算是IO体系中子类,而是直接继承Object。但是它是IO包中的成员,因为它具备读和写的功能。它内部封装了一个数组,而且通过指针对数组的元素进行操作。

关于指针:

1、可以通过getFilePointer获取指针位置

2、可通过seek改变指针位置。

其完成读写原理就是内部封装了字节输入流和字节输出流。

这个类的实例只能操作文件,构造函数是文件或文件名加一个mode,这个mode只接收四种值:

“r”  以只读方式打开。 

“rw”打开以便读取和写入,如果文件不存在,则尝试创建该文件

“rws” 打开以便读取和写入,对文件内容或元数据的每个更新同步到底层存储设备

“rwd” 打开以便读取和写入,对文件内容的每个更新都同步写入到底层存储设备

如果模式为r的话,不会创建文件,会读取一个已存在的文件,不存在的话就会报异常。如果模式为rw的话,要操作的文件不存在在,则会自动创建,如果存在,不会覆盖。

现在看一下示例:

       RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");

       raf.write(258);

       raf.writeInt(258);

       raf.close();

                    上面例子就是建立一个RandomAccessFile的对象,连接一个文件,模式moderw,就是读写模式。如果此处mode”r”的话,就只能用读的方法,不能用写的方法。注意的是,这个类中读和写都存在。

                    write方法中,只会把我们传入的数字的int型的最后八位写入,而下面的writeInt则是会把int型的全部八位都会写入。所以,在写入数据的时候要注意位数,这样在读取的时候,最好用相应的读取方法来读取,数据才不会出错。如,我们在写数据时,用writeInt方法写入98,这时文件中存入的是4个自己的int数据,如果我们在读取的时候,用write方法并存到一个4字节中,这样读取比较麻烦,这时用writeInt方法,就会自动读取4字节,并自动转换为int型。

                   我们在建立新对象读取ran.txt中的数据时,可以通过移动指针,在完成数据的读取:raf.seek(8),这就是把指针放到了第八位,从第八位开始读取数据。通过这个方法,我们就可以读取到文件的任意位置的数据了。但数据最好是有规律可循的。同样,我们写入的时候,也可以先设置指针位置,raf.seek(16),写入数据就可以在16的位置开始写,如果位置上有数据,就会覆盖这部分数据。而且还可以用到多线程,是多个线程在不同位置同时写入。

                    还有方法就是skipBytesint I),指针跳过跳过n个字节,但是不能往回跳,只能往前跳。

 

 

总结来说注意三点:

1、构造函数只接受File或文件名,还有mode模式,常用r(只读)和rw(读写)。

2、注意writeInt等方法的写入和读取。

3seekint key),调整指针位置。

 

 

 

 

DataInputStreamDataOutputStream

可以操作基本数据类型的流对象。构造函数是接受的InputStreamOutputStream的对象

当我们操作的只有基本数据类型的时候,就可以使用这个流对象。这个流对象注意的是,当我们写入基本数据类型的时用到的方法,比如writeInt,最好读取的时候也用相应的读取方法,,也比如ReadInt方法,否则我们自己想读取到正确的值,就要先存入四个字节的数组,转成字符串再强转成int,比较麻烦。

 

 

ByteArrayInputStreamByteArrayOutputStream

用于操作自己数组的流对象。

ByteArrayInputStream:在构造的时候,需要接收一个数据源,而且数据源是一个字节数组。

ByteArrayOutputStream:在构造的时候,不需要定义目的,因为该对象内部封装了一个可变长度的字节数组,这就是数据的目的地。

因为这两个流对象操作的数组,并没有使用系统资源,所以不
0 0
原创粉丝点击