Java之 IO流、字符流、字节流、缓冲流、对象流等各种流
File类:使用Java进行操作文件,通过一些方法进行操作。比如创建文件,删除文件,判断是否存在,文件大小,文件的目录等等,还有文件夹的一些操作。
IO流:根据类别可以进行分类。
按照流向:输入流Input 输出流Output
按照字节个数:字节流和字符流
字节流:
InputStream:抽象类,无法直接使用,通过其子类FileInputStream,从文件中获取字节。
OutputStream:抽象类,无法直接使用,通过其子类FileOutputStream,向文件写入字节。
字符流:
Reader:抽象类,无法直接使用,通过其子类的子类FileReader从文件中获取字符。
Writer:抽象类,无法直接使用,通过其子类的子类FileWriter向文件写入字符。
以上通过其子类的形式从文件中读取数据或向文件中写入数据,这种形式太局限。
比如,FileInputStream和FileOutputStream是操纵字节数据,当操纵字符数据时会有报错的可能,而且每次写入输出只是单个,所以并不能拿出来单独使用;FileReader和FileWriter可以操纵字符数据,且文件的编码应该和Java所定的编码匹配,当遇到编码不符的情况,则会乱码。
所以我们不推荐这四种单一的形式。那么,我们如何使得文件中的字符数据顺利读入,Java中的字符数据也可顺利输出到文件呢?
针对上面的问题,我们使用了另外两种类,即InputStreamReader和OutputStreamWriter,这两种是Reader和Writer的子类,也是FIleReader和FileWriter 的父类。它们是字节到字符和字符到字节的桥梁,即可以将文件中的带有字符的字节数据顺利读入到Java中,将Java中的数据成功写入到文件中。在使用的过程中,我们需要指定编码,即文件和Java中指定的编码相匹配,这样编码相同了,字符也可以和字节互转了,我们可以成功的操纵数据了。
以上FileInputStream、FileOutputStream、FileReader、FileWriter这几种,每次只能操作一个字节或字符,具局限性(纵然我们可以通过自定义字节字符缓冲来实现多量获取),可是数据的效率还是不高,所以我们使用到了另外的四个类,即BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter,这是针对字节流和字符流而存在的高效缓冲区,名曰缓冲流,它们存在于内存中。使用了它们两个,那数据传输可就跑快快啦。在使用缓冲流之前,和硬盘中文件的交互是一次一个的,硬盘本身的效率就低,IO交互频繁,效率肯定低了。缓冲流的意思,就是先将硬盘中的数据放置在内存中,当缓冲流满了后,一次性读取出来,效率可见很高了。
而这四种缓冲流,或者这两类缓冲流,用法也是不一样的。BufferInputStream和BufferedOutputStream二者,只能操作字节,无法将字节数据转换为字符读入到Java程序,更无法将字符写入到文件中;而BufferReader和BufferWriter却可以操纵字符或者字节,在传输的时候再搭配InputStreamReader和OutputStreamWriter,附以指定编码,将字节转为字符读入或将字符转为字节写入文件,InputStreamReader和OutputStreamWriter又成为字节和字符之间的桥梁。。
所以,我们一般通过这种形式来实现输入输出,即缓冲流+转换流+字节流的形式。
代码如下:
读入文件:
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(文件路径));
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(文件路径)), 编码);
写文件的话呢,将InputStream和Reader改为Writer即可。
在使用以上两种的话,针对不同的情况来使用就可以啦,但是先要明白传输的数据是什么样子的。
总结,我们在数据和文件进行交互的时候,文件中存储的都是字节,而文件中存在的字符,实际上也是字节的形式。在进行数据读取时,需要将带有字符的字节经过转码变成可识别的数据;而进行写入文件时,也许将字符转为字节,然后经过转码为文件可识别的数据,然后将字节进行传输,最终文件中存储的还是字节。
所以上面所讲的,FileReader和FileWriter因为二者是直接操纵字符, 而遇到字符编码不匹配则会报错,我们在开发过程中,不会单一的使用FileReader和FileWriter,如果在字节和字符的桥梁中使用这二者是没法使用的,只能单独使用这二者,那么遇到编码问题可能会出错的。
补充:在输出流这里,我们还有一种流成为打印流,即PrintStream,它是属于OutputStream。在我们使用输出流的时候,我们一般会使用FileInputStream来指定文件,我们可以将FileInputStream修改为PrintStream,同样可以输出到文件中。
所以我们来说一下另外一个流,打印流,简单概述一下。
那这个PrintStream是个什么东东呢?大家都知道在我们控制台中可以输出数据吧,System.out.println(“哈哈”); 对的,就是这个样子,我们是很熟悉的打印数据,我们为什么称之为打印呢,原因来源于PrintStream。我们在输入System.in后,它会返回一个类PrintStream,然后我们可以通过println(), 来输出数据,这个方法是在PrintStream类中的,可以输出各种各样的数据,所以我们才每次打印数据那么的方便,来源就是在于PrintStream。我们可以打印数据到控制台,同样可以打印到文件,所以我们创建PrintStream对象,来指定文件,即可将数据打印到文件中。
上面我们说了几种流,有IO流、字节流、字符流、字节或字符缓冲流、转换流、打印流。下面我们来说说对象流,这个流也是比较重要的,我们可以将创建的对象放置在流中进行传输,传输的形式有两种,一种是文件传输,一种是网络传输,即写到硬盘文件中,通过网络传输出去。我们在使用对象流时,用到了两个类,ObjectInputStream和ObjectOutputStream,无疑,从单词上我们可以看出,将Object对象使用字节流InputStream传送,最终以字节的形式存在文件中。
方法:
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(文件名));
ObjectOutputStream ois=new ObjectOutputStream(new FileOutputStream(文件名));
然后再调用相应方法获取即可。
我们将对象存到硬盘中,需要一个关键的步骤,序列化,将对象作为一个序列化文件存储到硬盘中,所以我们需要将用到的对象类实现接口Serializable,然后必须指定一个序列化号serialVersionUID,这个号是唯一的,当我们进行反序列化的时候,才不会报错。因为在反序列化的过程中,会自动将序列化文件和类进行比对,再和类中的序列号进行比对,当完全无误后,方可序列化成功。
然后,我们还有一个读写于一体的类,RandomAccessFile,这个类中读取和写入是一体的,在之前我们学的IO流中,都是IO分开,需要用到两个类才能输入输出数据。这个类不同,只需要调用不同的方法即可访问文件中的数据。而且,可以随机访问文件中指定位置的数据,比如指定下标偏移量(seek方法),跳过多少字节(skipBytes),通过这两种方式来获取随机位置的数据,如果不通过这二者方法,则是无法获取数据的。
这个类,在多线程下载处使用的多,比如,迅雷下载,能够看到进度条中一小块一小块的,这大概就是在一块一块获得数据吧,哈哈小编了解的不多,可能是这个样子,还有待研究的。
最后呢,我们在文件中存储数据和读取数据,还有一个至为关键的,名曰Properties,这个类是一个属性类,我们在Web地方用的最多了,它是和两个知识点有关。
首先,Properties是属于集合的分支,是HashTable类的子类,HashTable又是Map的子类,所以它是集合的类。由于HashTable是同步的,不允许为空,且存储也是无序,所以线程安全,存的也是键值对。其次,Properties又和IO流有关,通过list方法,或者store方法,然后指定输出流,再指定路径,即可写入到文件中。在文件中,存储也是以键值对的形式存放,但是都是String类型。
Properties文件存到硬盘中后,我们时常需要取到该文件中的数据,所以我们需要将其像输入流一样载入进来。我们通过其Properties类的load方法,然后在参数中指定输入流即可,这里,我们指定的输入流可以是FileReader,也可以是转换流+字节流。我们在之前说FileReader是一次读一个字符,而且当字符不匹配会乱码,因为字符是多个字节,我们无法保证可以将字节拼接起来,因为字符对应的字节数是不定的,所以我们是不推荐这种方式读入的。可是在这里,由于Properties的特殊性,我们可以使用FileReader直接读入了,不会出现乱码的情况,因为它读入了,不需要拼接,直接输出即可。
在Properties写入文件操作时,我们通过字节流写入文件,则会将当前java中的字符编码的字节形式写入到文件中,如果硬盘文件的字符编码和Java的字符编码不同,则会出现乱码;如果我们通过字符流的形式写入,那无论硬盘文件的编码是否一致,都不会出现乱码。这也就是为什么使用list写入不会乱码,而通过store形式会有可能乱码的。
声明一点,由于Properties是线程安全的,它存放键和值的属性除了String,再也不允许其它类型数据,否则会报错,导致不安全。
所以Properties是既带有集合的特点,又可以实现IO流的方法,且它是没有泛型的,这一点又和Map集合不相同,所以属于自成一派的。
经过小编这么一通撤,小编也思路清晰许多了呢,欢迎各为大牛前来点评。
妈妈说哪里不好点哪里……