字节流与和字符流的使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同呢?
区别:实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件,如图12-6所示。
下面以两个写文件的操作为主进行比较,但是在操作时字节流和字符流的操作完成之后都不关闭输出流。
范例:使用字节流不关闭执行
- package org.lxh.demo12.byteiodemo;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.OutputStream;
- public class OutputStreamDemo05 {
- public static void main(String[] args) throws Exception {
-
- File f = new File("d:" + File.separator + "test.txt");
-
- OutputStream out = null;
-
- out = new FileOutputStream(f);
-
-
- String str = "Hello World!!!";
-
- byte b[] = str.getBytes();
-
- out.write(b);//读一个写一个,没有缓存
-
-
-
-
- }
- }
程序运行结果:
此时没有关闭字节流操作,但是文件中也依然存在了输出的内容,证明字节流是直接操作文件本身的。而下面继续使用字符流完成,再观察效果。
范例:使用字符流不关闭执行
- package org.lxh.demo12.chariodemo;
- import java.io.File;
- import java.io.FileWriter;
- import java.io.Writer;
- public class WriterDemo03 {
- public static void main(String[] args) throws Exception {
-
- File f = new File("d:" + File.separator + "test.txt");
-
- Writer out = null;
-
- out = new FileWriter(f);
-
-
- String str = "Hello World!!!";
-
- out.write(str);//读出来写到缓存(内存)中,还没写到磁盘上
-
-
-
-
- }
- }
程序运行结果:
程序运行后会发现文件中没有任何内容,这是因为字符流操作时使用了缓冲区,而 在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的,所以得出结论:字符流使用了缓冲区,而字节流没有使用缓冲区。
提问:什么叫缓冲区?
在很多地方都碰到缓冲区这个名词,那么到底什么是缓冲区?又有什么作用呢?
回答:缓冲区可以简单地理解为一段内存区域。
可以简单地把缓冲区理解为一段特殊的内存。
某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。
在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。
如果想在不关闭时也可以将字符流的内容全部输出,则可以使用Writer类中的flush()方法完成。
范例:强制性清空缓冲区
- package org.lxh.demo12.chariodemo;
- import java.io.File;
- import java.io.FileWriter;
- import java.io.Writer;
- public class WriterDemo04 {
- public static void main(String[] args) throws Exception {
-
- File f = new File("d:" + File.separator + "test.txt");
- 对象
-
- Writer out = null;
-
- out = new FileWriter(f);
-
-
- String str = "Hello World!!!";
-
- out.write(str);
-
- out.flush();
-
-
-
-
- }
- }
程序运行结果:
此时,文件中已经存在了内容,更进一步证明内容是保存在缓冲区的。这一点在读者日后的开发中要特别引起注意。
提问:使用字节流好还是字符流好?
学习完字节流和字符流的基本操作后,已经大概地明白了操作流程的各个区别,那么在开发中是使用字节流好还是字符流好呢?
回答:使用字节流更好。
在回答之前,先为读者讲解这样的一个概念,所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节流使用较为广泛 。
字节流与字符流主要的区别是他们的的处理方式
流分类:
1.Java的字节流
InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先。
2.Java的字符流
Reader是所有读取字符串输入流的祖先,而writer是所有输出字符串的祖先。
InputStream,OutputStream,Reader,writer都是抽象类。所以不能直接new
字节流是最基本的,所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的
但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化
这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联
在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的
在从字节流转化为字符流时,实际上就是byte[]转化为String时,
public String(byte bytes[], String charsetName)
有一个关键的参数字符集编码,通常我们都省略了,那系统就用操作系统的lang
而在字符流转化为字节流时,实际上是String转化为byte[]时,
byte[] String.getBytes(String charsetName)
也是一样的道理
至于java.io中还出现了许多其他的流,按主要是为了提高性能和使用方便,
如BufferedInputStream,PipedInputStream等
(注意:BufferedInputStream稍有不同,它是带缓存的)
再解释一遍:不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!这就是两者的区别
下面是字节流和字符流常见用法:
首先看个图:(如果你也是初学者,我相信你看了真个人都不好了,想想java设计者真是煞费苦心啊!)
先简单介绍下FilterInputStream:这个流用于对基础流的过滤操作,即对基础流进行包装,使之拥有更多实用的功能
这是java io 比较基本的一些处理流,除此之外我们还会提到一些比较深入的基于io的处理类,比如console类,SteamTokenzier,Externalizable接口,Serializable接口等等一些高级用法极其原理。
一、java io的开始:文件
1. 我们主要讲的是流,流的本质也是对文件的处理,我们循序渐进一步一步从文件将到流去。
2. java 处理文件的类 File,java提供了十分详细的文件处理方法,举了其中几个例子,其余的可以去
- package com.hxw.io;
- import java.io.*;
-
- public class FileExample{
- public static void main(String[] args) {
-
- createFile();
- }
-
-
-
-
- public static void createFile() {
- File f=new File("E:/电脑桌面/jar/files/create.txt");
- try{
- f.createNewFile();
- System.out.println("该分区大小"+f.getTotalSpace()/(1024*1024*1024)+"G");
- f.mkdirs();
-
- System.out.println("文件名 "+f.getName());
- System.out.println("文件父目录字符串 "+f.getParent());
-
- }catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
二、字节流:
1.字节流有输入和输出流,我们首先看输入流InputStream,我们首先解析一个例子(FileInputStream)。
- package com.hxw.io;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- public class FileCount {
-
-
-
- publicstatic void main(String[] args) {
-
- int count=0;
- InputStreamstreamReader = null;
- try{
- streamReader=newFileInputStream(new File("D:/David/Java/java 高级进阶/files/tiger.jpg"));
-
-
-
- while(streamReader.read()!=-1) {
- count++;
- }
- System.out.println("---长度是: "+count+" 字节");
- }catch (final IOException e) {
-
- e.printStackTrace();
- }finally{
- try{
- streamReader.close();
- }catch (IOException e) {
-
- e.printStackTrace();
- }
- }
- }
-
- }
我们一步一步来,首先,上面的程序存在问题是,每读取一个自己我都要去用到FileInputStream,我输出的结果是“---长度是: 64982 字节”,那么进行了64982次操作!可能想象如果文件十分庞大,这样的操作肯定会出大问题,所以引出了缓冲区的概念。可以将streamReader.read()改成streamReader.read(byte[]b)此方法读取的字节数目等于字节数组的长度,读取的数据被存储在字节数组中,返回读取的字节数,InputStream还有其他方法mark,reset,markSupported方法,例如:
markSupported 判断该输入流能支持mark
和 reset
方法。
mark用于标记当前位置;在读取一定数量的数据(小于readlimit的数据)后使用reset可以回到mark标记的位置。
FileInputStream不支持mark/reset操作;BufferedInputStream支持此操作;
mark(readlimit)的含义是在当前位置作一个标记,制定可以重新读取的最大字节数,也就是说你如果标记后读取的字节数大于readlimit,你就再也回不到回来的位置了。
通常InputStream的read()返回-1后,说明到达文件尾,不能再读取。除非使用了mark/reset。
2.FileOutputStream 循序渐进版, InputStream是所有字节输出流的父类,子类有ByteArrayOutputStream,FileOutputStream,ObjectOutputStreanm,这些我们在后面都会一一说到。先说FileOutputStream
我以一个文件复制程序来说,顺便演示一下缓存区的使用。(Java I/O默认是不缓冲流的,所谓“缓冲”就是先把从流中得到的一块字节序列暂存在一个被称为buffer的内部字节数组里,然后你可以一下子取到这一整块的字节数据,没有缓冲的流只能一个字节一个字节读,效率孰高孰低一目了然。有两个特殊的输入流实现了缓冲功能,一个是我们常用的BufferedInputStream.)
- package com.hxw.io;
- import java.io.*;
-
- public class FileCopy {
-
- public static void main(String[] args) {
-
- byte[] buffer=new byte[512];
- int numberRead=0;
- FileInputStream input=null;
- FileOutputStream out =null;
- try {
- input=new FileInputStream("D:/David/Java/java 高级进阶/files/tiger.jpg");
- out=new FileOutputStream("D:/David/Java/java 高级进阶/files/tiger2.jpg");
-
- while ((numberRead=input.read(buffer))!=-1) {
- out.write(buffer, 0, numberRead);
- }
- } catch (final IOException e) {
-
- e.printStackTrace();
- }finally{
- try {
- input.close();
- out.close();
- } catch (IOException e) {
-
- e.printStackTrace();
- }
-
- }
- }
-
- }
3.读写对象:ObjectInputStream 和ObjectOutputStream ,该流允许读取或写入用户自定义的类,但是要实现这种功能,被读取和写入的类必须实现Serializable接口,其实该接口并没有什么方法,可能相当于一个标记而已,但是确实不合缺少的。实例代码如下:
- package com.hxw.io;
-
- import java.io.*;
-
- public class ObjetStream {
-
-
-
-
- public static void main(String[] args) {
-
- ObjectOutputStream objectwriter=null;
- ObjectInputStream objectreader=null;
-
- try {
- objectwriter=new ObjectOutputStream(new FileOutputStream("D:/David/Java/java 高级进阶/files/student.txt"));
- objectwriter.writeObject(new Student("gg", 22));
- objectwriter.writeObject(new Student("tt", 18));
- objectwriter.writeObject(new Student("rr", 17));
- objectreader=new ObjectInputStream(new FileInputStream("D:/David/Java/java 高级进阶/files/student.txt"));
- for (int i = 0; i < 3; i++) {
- System.out.println(objectreader.readObject());
- }
- } catch (IOException | ClassNotFoundException e) {
-
- e.printStackTrace();
- }finally{
- try {
- objectreader.close();
- objectwriter.close();
- } catch (IOException e) {
-
- e.printStackTrace();
- }
-
- }
-
- }
-
- }
- class Student implements Serializable{
- private String name;
- private int age;
-
- public Student(String name, int age) {
- super();
- this.name = name;
- this.age = age;
- }
-
- @Override
- public String toString() {
- return "Student [name=" + name + ", age=" + age + "]";
- }
-
-
- }
运行后系统输出:
Student [name=gg, age=22]
Student [name=tt, age=18]
Student [name=rr, age=17]
4.有时没有必要存储整个对象的信息,而只是要存储一个对象的成员数据,成员数据的类型假设都是Java的基本数据类型,这样的需求不必使用到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream来写入或读出数据。下面是一个例子:(DataInputStream的好处在于在从文件读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时将停止,使用对应的readUTF()和readInt()方法就可以正确地读入完整的类型数据。)
- package com.hxw;
- public class Member {
- private String name;
- private int age;
- public Member() {
- }
- public Member(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public void setName(String name){
- this.name = name;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public int getAge() {
- return age;
- }
- }
打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象。下面的代码简单示范了如何实现这个需求。
- package com.hxw;
- import java.io.*;
- public class DataStreamDemo
- {
- public static void main(String[]args)
- {
- Member[] members = {newMember("Justin",90),
- newMember("momor",95),
- newMember("Bush",88)};
- try
- {
- DataOutputStreamdataOutputStream = new DataOutputStream(new FileOutputStream(args[0]));
-
- for(Member member:members)
- {
-
- dataOutputStream.writeUTF(member.getName());
-
- dataOutputStream.writeInt(member.getAge());
- }
-
-
- dataOutputStream.flush();
-
- dataOutputStream.close();
-
- DataInputStreamdataInputStream = new DataInputStream(new FileInputStream(args[0]));
-
-
- for(inti=0;i
- {
-
- String name =dataInputStream.readUTF();
-
- int score =dataInputStream.readInt();
- members[i] = newMember(name,score);
- }
-
-
- dataInputStream.close();
-
-
- for(Member member : members)
- {
- System.out.printf("%s\t%d%n",member.getName(),member.getAge());
- }
- }
- catch(IOException e)
- {
- e.printStackTrace();
- }
- }
- }
5.PushbackInputStream类继承了FilterInputStream类是iputStream类的修饰者。提供可以将数据插入到输入流前端的能力(当然也可以做其他操作)。简而言之PushbackInputStream类的作用就是能够在读取缓冲区的时候提前知道下一个字节是什么,其实质是读取到下一个字符后回退的做法,这之间可以进行很多操作,这有点向你把读取缓冲区的过程当成一个数组的遍历,遍历到某个字符的时候可以进行的操作,当然,如果要插入,能够插入的最大字节数是与推回缓冲区的大小相关的,插入字符肯定不能大于缓冲区吧!下面是一个示例。
- package com.hxw.io;
-
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.io.PushbackInputStream;
-
-
-
-
- public class PushBackInputStreamDemo {
- public static void main(String[] args) throws IOException {
- String str = "hello,rollenholt";
- PushbackInputStream push = null;
- ByteArrayInputStream bat = null;
- bat = new ByteArrayInputStream(str.getBytes());
- push = new PushbackInputStream(bat);
- int temp = 0;
- while ((temp = push.read()) != -1) {
- if (temp == ',') {
- push.unread(temp);
- temp = push.read();
- System.out.print("(回退" + (char) temp + ") ");
- } else {
- System.out.print((char) temp);
- }
- }
- }
- }
6.SequenceInputStream:有些情况下,当我们需要从多个输入流中向程序读入数据。此时,可以使用合并流,将多个输入流合并成一个SequenceInputStream流对象。SequenceInputStream会将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 合并流的作用是将多个源合并合一个源。其可接收枚举类所封闭的多个字节流对象。
- package com.hxw.io;
-
- import java.io.*;
- import java.util.Enumeration;
- import java.util.Vector;
-
- public class SequenceInputStreamTest {
-
-
-
-
-
-
- public static void main(String[] args) {
- doSequence();
- }
-
- private static void doSequence() {
-
- SequenceInputStream sis = null;
-
- BufferedOutputStream bos = null;
- try {
-
- Vector vector = new Vector();
- vector.addElement(new FileInputStream("D:\text1.txt"));
- vector.addElement(new FileInputStream("D:\text2.txt"));
- vector.addElement(new FileInputStream("D:\text3.txt"));
- Enumeration e = vector.elements();
-
- sis = new SequenceInputStream(e);
-
- bos = new BufferedOutputStream(new FileOutputStream("D:\text4.txt"));
-
- byte[] buf = new byte[1024];
- int len = 0;
- while ((len = sis.read(buf)) != -1) {
- bos.write(buf, 0, len);
- bos.flush();
- }
- } catch (FileNotFoundException e1) {
- e1.printStackTrace();
- } catch (IOException e1) {
- e1.printStackTrace();
- } finally {
- try {
- if (sis != null)
- sis.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try {
- if (bos != null)
- bos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
7.PrintStream 说这个名字可能初学者不熟悉,如果说System.out.print()你肯定熟悉,System.out这个对象就是PrintStream,这个我们不做过多示例
三、字符流(顾名思义,就是操作字符文件的流)
1.java 使用Unicode存储字符串,在写入字符流时我们都可以指定写入的字符串的编码。前面介绍了不用抛异常的处理字节型数据的流ByteArrayOutputStream,与之对应的操作字符类的类就是CharArrayReader,CharArrayWriter类,这里也会用到缓冲区,不过是字符缓冲区,一般讲字符串放入到操作字符的io流一般方法是
CharArrayReaderreader=mew CharArrayReader(str.toCharArray()); 一旦会去到CharArrayReader实例就可以使用CharArrayReader访问字符串的各个元素以执行进一步读取操作。不做例子
2.我们用FileReader ,PrintWriter来做示范
- package com.hxw.io;
-
- import java.io.FileNotFoundException;
- import java.io.FileReader;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.nio.CharBuffer;
-
- public class Print {
-
-
-
-
- public static void main(String[] args) {
-
- char[] buffer=new char[512];
- int numberRead=0;
- FileReader reader=null;
- PrintWriter writer=null;
-
- try {
- reader=new FileReader("D:/David/Java/java 高级进阶/files/copy1.txt");
- writer=new PrintWriter(System.out);
- while ((numberRead=reader.read(buffer))!=-1) {
- writer.write(buffer, 0, numberRead);
- }
- } catch (IOException e) {
-
- e.printStackTrace();
- }finally{
- try {
- reader.close();
- } catch (IOException e) {
-
- e.printStackTrace();
- }
- writer.close();
- }
-
- }
-
- }
3.相对我们前面的例子是直接用FileReader打开的文件,我们这次使用链接流,一般比较常用的都用链接流,所谓链接流就是就多次对流的封装,这样能更好的操作个管理数据,(比如我们利用DataInputStream(BufferedInputStream(FileInputStream))将字节流层层包装后,我们可以读取readByte(),readChar()这样更加具体的操作,注意,该流属于字节流对字符进行操作,)字符流用CharArrayReader就可以了。下面的示例我们将用到j2se 5中的一个可变参数进行一个小度扩展。使用BufferedWriter 和BufferedReader用文件级联的方式进行写入,即将多个文件写入到同一文件中(自带缓冲区的输出输出流BufferedReader和BufferedWriter,该流最常用的属readLine()方法了,读取一行数据,并返回String)。
- package com.hxw.io;
-
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.util.Iterator;
-
- public class FileConcatenate {
-
-
-
-
- public static void main(String[] args) {
-
- try {
- concennateFile(args);
- } catch (IOException e) {
-
- e.printStackTrace();
- }
- }
- public static voidconcennateFile(String...fileName) throws IOException{
- String str;
-
- BufferedWriter writer=new BufferedWriter(new FileWriter("D:/David/Java/java 高级进阶/files/copy2.txt"));
- for(String name: fileName){
- BufferedReader reader=new BufferedReader(new FileReader(name));
-
- while ((str=reader.readLine())!=null) {
- writer.write(str);
- writer.newLine();
- }
- }
- }
-
- }
-
4.Console类,该类提供了用于读取密码的方法,可以禁止控制台回显并返回char数组,对两个特性对保证安全有作用,平时用的不多,了解就行。
5.StreamTokenizer 类,这个类非常有用,它可以把输入流解析为标记(token), StreamTokenizer 并非派生自InputStream或者OutputStream,而是归类于io库中,因为StreamTokenizer只处理InputStream对象。
首先给出我的文本文件内容:
'水上漂'
青青草
"i love wyhss"
{3211}
23223 3523
i love wyh ,。
. ,
下面是代码:
- package com.hxw.io;
- import java.io.BufferedReader;
- import java.io.FileReader;
- import java.io.IOException;
- import java.io.StreamTokenizer;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public class StreamTokenizerExample {
-
-
-
-
-
-
- public static void main(String[] args) {
- String fileName = "D:/David/Java/java 高级进阶/files/copy1.txt";
- StreamTokenizerExample.statis(fileName);
- }
- public static long statis(String fileName) {
-
- FileReader fileReader = null;
- try {
- fileReader = new FileReader(fileName);
-
- StreamTokenizer st = new StreamTokenizer(new BufferedReader(
- fileReader));
-
-
-
- st.ordinaryChar('\'');
- st.ordinaryChar('\"');
- st.ordinaryChar('/');
-
- String s;
- int numberSum = 0;
- int wordSum = 0;
- int symbolSum = 0;
- int total = 0;
-
-
- while (st.nextToken() !=StreamTokenizer.TT_EOF) {
-
- switch (st.ttype) {
-
- case StreamTokenizer.TT_EOL:
- break;
-
- case StreamTokenizer.TT_NUMBER:
-
- s = String.valueOf((st.nval));
- System.out.println("数字有:"+s);
- numberSum ++;
- break;
-
- case StreamTokenizer.TT_WORD:
-
- s = st.sval;
- System.out.println("单词有: "+s);
- wordSum ++;
- break;
- default:
-
- s = String.valueOf((char) st.ttype);
- System.out.println("标点有: "+s);
- symbolSum ++;
- }
- }
- System.out.println("数字有 " + numberSum+"个");
- System.out.println("单词有 " + wordSum+"个");
- System.out.println("标点符号有: " + symbolSum+"个");
- total = symbolSum + numberSum +wordSum;
- System.out.println("Total = " + total);
- return total;
- } catch (Exception e) {
- e.printStackTrace();
- return -1;
- } finally {
- if (fileReader != null) {
- try {
- fileReader.close();
- } catch (IOException e1) {
- }
- }
- }
- }
-
-
- }
//自己试一试
FileInputStream fis=new FileInputStream(new File("D://test.txt"));
byte[] buf=new byte[5];
while(fis.read(buf)!=-1)//最后指针指向结尾。
System.out.println(new String(buf));
FileInputStream fis2=new FileInputStream(new File("D://test.txt"));
FileOutputStream fos=new FileOutputStream(new File("d://test2.txt"),true);//追加到文件尾部
BufferedOutputStream bos=new BufferedOutputStream(fos);
int len;
int n = 0;
while ((len=fis2.read(buf)) != -1) {
bos.write(buf, 0, len);
n++;
}
System.out.println(n);
String str="add some words in the file.";
bos.write(str.getBytes());
bos.flush();
fis.close();
bos.close();