------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
IO流中其他对象
PrintWriter和PrintStream
PrintWriter和PrintStream
打印流,为其他输出流添加了功能,是它们能够方便的打印各种数据值的表现形式。该流提供了答应方法,可以将各种数据类型的数据都原样打印。
字节打印流PrintStream,构造函数可以接受的类型:
1、File对象 File
2、字符串路径 String
3、字节输出流 OutputStream
字符打印流PtineWrite,构造函数可以接受的类型是:
1、File对象 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是,就会自动刷新,否则不会,但只对方法中的println、printf,format有效。如下:
PrintWriter pw = new PrintWriter(System.out,true);
这样使用println的时候,就不用在使用刷新方法了。还有一点要注意,这个构造方法只能用在目的地是流的构造方法中,如果目的地是一个文件,就不可以使用了。
但是我们可以把文件编程流对象,这样既可以把数据打印到文件里,又可以自动刷新。
PrintWriter pw = new PrintWriter(new FileWriter(demo.txt),true);
序列流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.txt,2.txt等等。
3、记得关流。
当然,既然有分割就有合并,合并方法就是上面序列流的方法了。
ObjectInputStream与ObjectOutputStream
这个流是可以直接操作对象的流。一般我们在操作对象的时候,都是将对象加载到堆内存中,一旦使用完了就会被回收,而这个流可以直接把对象存到硬盘上,这个叫做对象的持久化,也叫做对象的序列化。当然对象中有相应的数据,成员变量和成员函数,当这个流直接把对象存到硬盘上,对象就存到硬盘上了,我们使用的时候,直接读取这硬盘上的数据就可以了。
ObjectOutputStream来说,它的构造函数会接收一个OutputStream对象,明确目的。这里面的方法和OoutputStream中方法都是一样,但是注意几个特有的方法。
它有可以操作基本数据类型的写方法,比如writeInt(int value),writeChar(char ch)等。当然还有操作对象的方法,writeObject(Object obj)。
注意一点:有ObjectOutputStream存的对象,必须有ObjectInputStream读取。这里面的readObject方法也是一个个往下读,和OutputStream的read机制是一样的。
然后我们建立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
管道流PipedInputStream和PipedOutputStream
一般我们IO中操作,输出流和输入流是分开的,弄一个中间部分是两部连接起来,比如我们是用的数组。管道流就是把输入和输出连接起来。通常,数据由某个线程从PipedInputStream对象读取,并有其他线程写入到相应的PipedOutputStream,不见对两个对象使用单个线程,因为这样可能死锁线程。因为管道输入流和管道输出流相连接,而输出方面是read方法,此方法是阻塞式方法,如果输入端没有数据输入,那么次方法就会造成当前线程等待,所以单线程执行管道流会死锁。
管道流是IO流中涉及到多线程的流对象。
管道流就是把输入和输出连接上,所以建立连接方法:
一、PipedInputStream的构造函数中传入PipedOutputStream的对象。
二、用PipedInputStream中的connect(PiedOutputStream 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的对象,连接一个文件,模式mode为rw,就是读写模式。如果此处mode为”r”的话,就只能用读的方法,不能用写的方法。注意的是,这个类中读和写都存在。
write方法中,只会把我们传入的数字的int型的最后八位写入,而下面的writeInt则是会把int型的全部八位都会写入。所以,在写入数据的时候要注意位数,这样在读取的时候,最好用相应的读取方法来读取,数据才不会出错。如,我们在写数据时,用writeInt方法写入98,这时文件中存入的是4个自己的int数据,如果我们在读取的时候,用write方法并存到一个4字节中,这样读取比较麻烦,这时用writeInt方法,就会自动读取4字节,并自动转换为int型。
我们在建立新对象读取ran.txt中的数据时,可以通过移动指针,在完成数据的读取:raf.seek(8),这就是把指针放到了第八位,从第八位开始读取数据。通过这个方法,我们就可以读取到文件的任意位置的数据了。但数据最好是有规律可循的。同样,我们写入的时候,也可以先设置指针位置,raf.seek(16),写入数据就可以在16的位置开始写,如果位置上有数据,就会覆盖这部分数据。而且还可以用到多线程,是多个线程在不同位置同时写入。
还有方法就是skipBytes(int I ),指针跳过跳过n个字节,但是不能往回跳,只能往前跳。
总结来说注意三点:
1、构造函数只接受File或文件名,还有mode模式,常用r(只读)和rw(读写)。
2、注意writeInt等方法的写入和读取。
3、seek(int key),调整指针位置。
DataInputStream和DataOutputStream
可以操作基本数据类型的流对象。构造函数是接受的InputStream和OutputStream的对象。
当我们操作的只有基本数据类型的时候,就可以使用这个流对象。这个流对象注意的是,当我们写入基本数据类型的时用到的方法,比如writeInt,最好读取的时候也用相应的读取方法,,也比如ReadInt方法,否则我们自己想读取到正确的值,就要先存入四个字节的数组,转成字符串再强转成int,比较麻烦。
ByteArrayInputStream和ByteArrayOutputStream
用于操作自己数组的流对象。
ByteArrayInputStream:在构造的时候,需要接收一个数据源,而且数据源是一个字节数组。
ByteArrayOutputStream:在构造的时候,不需要定义目的,因为该对象内部封装了一个可变长度的字节数组,这就是数据的目的地。
因为这两个流对象操作的数组,并没有使用系统资源,所以不