一、java 流操作有关的类和接口:
类 说明
File 文件类
RandomAccessFile 随机存取文件类
InputStream 字节输入流
OutputStream 字节输出流
Reader 字符输入流
Writer 字符输出流
二、Java 流类图结构:
三、流的概念和作用:
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
四、流的分类:
根据处理数据类型的不同分为:字符流和字节流:
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:
读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
根据数据流向不同分为:输入流和输出流:
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
五、java I/O流对象:
1.输入字节流InputStream在IO 中输入字节流的继承图可见上图,可以看出:
① InputStream 是所有的输入字节流的父类,它是一个抽象类。
②ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。
③ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。
2.输出字节流OutputStream在IO 中输出字节流的继承图可见上图,可以看出:
①OutputStream 是所有的输出字节流的父类,它是一个抽象类。
②ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据。
③ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。
3.字符输入流Reader在IO继承关系图中可以看出:
①Reader 是所有的输入字符流的父类,它是一个抽象类。
②CharReader、StringReader 是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
③BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
④FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
⑤InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。
4.字符输出流Writer
①Writer 是所有的输出字符流的父类,它是一个抽象类。
②CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据。
③BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
④BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
⑤OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。
5.字符流与字节流转换:
转换流的特点:
①其是字符流和字节流之间的桥梁。
②可对读取到的字节数据经过指定编码转换成字符。
③可对读取到的字符数据经过指定编码转换成字节。
何时使用转换流?
①当字节和字符之间有转换动作时。
②流操作的数据需要编码或解码时。
具体的对象体现:
①InputStreamReader:字节到字符的桥梁。
②OutputStreamWriter:字符到字节的桥梁
六、
1. InputStream(输入流)
继承自InputStream的流都是用于向程序中输入数据,且数据的单位为字节。
InputStream的基本方法:
int read(); //读取一个字节并以整数的形式返回(0-255),如果返回-1表示到输入流的末尾。
int read(byte[] buffer );//读取一系列字节并存储到一个数组中,返回实际读取的字节数,如果已经读取到输入流的末尾则返回-1.
int read(byte[] buffer,int offset,int length);//读取length个字节,并存储至字节数组buffer,从length位置开始,返回实际读取的字节数,如果到流的末尾则返回-1。
close();//关闭流释放内存资源
long skip(long n);跳过n个字节不读,返回实际跳过的字节。
read()方法是一个字节一个字节地往外读,每读取一个字节,就处理一个字节。read(byte[] buffer)方法读取数据时,先把读取到的数据填满这个byte[]类型的数组buffer(buffer是内存里面的一块缓冲区),然后再处理数组里面的数据。这就跟我们取水一样,先用一个桶去接,等桶接满水后再处理桶里面的水。如果是每读取一个字节就处理一个字节,这样子读取也太累了。
2.OutputStream(输出流)的基本方法:
void write();//向输出流中写入一个字节数据,该字节数据为参数b的低8位。
void write(byte[] b);//将一个字节类型的数组的数据写入到输出流。
void write(byte[] b,int off,int length);//将一个字节类型的数组中的从指定位置off 开始len个字节写入到输出流。
void close();//关闭释放内存资源
void flush();//将输出流中缓冲的数据全部写到目的地。
3.Reader流和inputStream一模一样,唯一的区别在于读的数据单位不同。
Reader的基本方法:
int read();//读取一个字符并以整数的形式返回(0-255),如果返回-1则表示到流的末尾。
int read(char[] chbr) ;//读取一系列字符并存储到一个数组buffer,返回实际读取的字符数,如果读取前已经到了流的末尾则返回-1。
int read(char[] cbur,int offset,int length);//读取length的字符并存储至一个数组中,从length位置开始返回实际读取的字符数,如果读取前以到流的末尾则返回-1.
close();//关闭流释放内存单元。
long skip(long n);//跳过n个字符不读,返回实际跳过的字符个数。
4.Writer 流的基本方法:
void write(int c);//向输入流中写入一个数据
void write(char[] chr);//将一个字符类型的数组写入输出流
void write(char[] chr,int offset,,int length);//将一个字符类型的数组从指定的位置 offset 开始的length个字符写入到输出流。
void write(String str);//将一个字符串的字符写入到输出流
void write(String string,int offset,int length);//将一个字符串从指定的位置offset 开始的length个字符写入到输出流。
void close();//关闭流释放内存单元
void flush();//将输出流中的缓冲的数据全部写出到目的地。
七、节点流:
使用FileInputStream流来读取FileInputStream.java文件的内容:
package com.anshuo; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class TestFileInputStream { public static void main(String args[]) { int b = 0;//使用变量b来装调用read()方法时返回的整数 //使用FileInputStream流来读取有中文的内容时,读出来的是乱码,因为使用InputStream流里面的read()方法读取内容时是一个字节一个字节地读取的, //而一个汉字是占用两个字节的,所以读取出来的汉字无法正确显示。 FileInputStream in = null; // FileReader in = null; //使用FileReader流来读取内容时,中英文都可以正确显示,因为Reader流里面的read()方法是一个字符一个字符地读取的, //这样每次读取出来的都是一个完整的汉字,这样就可以正确显示了。 try { in = new FileInputStream("F:\\test.txt"); //in = new FileReader("F:\\test.txt"); } catch (FileNotFoundException e) { System.out.println("系统找不到指定文件!"); System.exit(-1);// 系统非正常退出 } long num = 0;// 使用变量num来记录读取到的字符数 try {// 调用read()方法时会抛异常,所以需要捕获异常 while ((b = in.read()) != -1) { // 调用int read() throws Exception方法时,返回的是一个int类型的整数 // 循环结束的条件就是返回一个值-1,表示此时已经读取到文件的末尾了。 // System.out.print(b+"\t");//如果没有使用“(char)b”进行转换,那么直接打印出来的b就是数字,而不是英文和中文了 System.out.print((char) b+"-------"); // “char(b)”把使用数字表示的汉字和英文字母转换成字符输入 num++; } in.close();// 关闭输入流 System.out.println(); System.out.println("总共读取了" + num + "个字节的文件"); } catch (IOException e1) { System.out.println("文件读取错误!"); } } }
范例:使用FileOutputStream流往一个文件里面写入数据
package com.anshuo; import java.io.*; public class TestFileOutputStream { public static void main(String args[]) { int b = 0; FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("F:\\test.txt"); out = new FileOutputStream("F:\\test1.txt"); // 指明要写入数据的文件,如果指定的路径中不存在TestFileOutputStream1.java这样的文件,则系统会自动创建一个 while ((b = in.read()) != -1) { out.write(b); // 调用write(int c)方法把读取到的字符全部写入到指定文件中去 } in.close(); out.close(); } catch (FileNotFoundException e) { System.out.println("文件读取失败"); System.exit(-1);// 非正常退出 } catch (IOException e1) { System.out.println("文件复制失败!"); System.exit(-1); } System.out.println("test.txt文件里面的内容已经成功复制到文件test1.txt里面"); } }
FileInputStream和FileOutputStream这两个流都是字节流,都是以一个字节为单位进行输入和输出的。所以对于占用2个字节存储空间的字符来说读取出来时就会显示成乱码。
范例:使用FileWriter(字符流)向指定文件中写入数据:
package com.anshuo; import java.io.*; public class TestFileWriter{ /*使用FileWriter(字符流)向指定文件中写入数据写入数据时以1个字符为单位进行写入*/ public static void main(String args[]){ /*使用FileWriter输出流从程序把数据写入到Uicode.dat文件中使用FileWriter流向文件写入数据时是一个字符一个字符写入的*/ FileWriter fw = null; try{ fw = new FileWriter("F:\\text1.txt"); //字符的本质是一个无符号的16位整数 //字符在计算机内部占用2个字节 //这里使用for循环把0~60000里面的所有整数都输出 //这里相当于是把全世界各个国家的文字都0~60000内的整数的形式来表示 for(int c=0;c<=100;c++){ fw.write("Hello,world"); //使用write(int c)把0~60000内的整数写入到指定文件内 //调用write()方法时,我认为在执行的过程中应该使用了“(char)c”进行强制转换,即把整数转换成字符来显示 //因为打开写入数据的文件可以看到,里面显示的数据并不是0~60000内的整数,而是不同国家的文字的表示方式 } fw.flush(); /*使用FileReader(字符流)读取指定文件里面的内容读取内容时是以一个字符为单位进行读取的*/ int b = 0; long num = 0; FileReader fr = null; fr = new FileReader("F:\\text1.txt"); while((b = fr.read())!= -1){ System.out.print((char)b + "\t"); num++; } System.out.println(); System.out.println("总共读取了"+num+"个字符"); }catch(Exception e){ e.printStackTrace(); } } }
按行读取:
/** * 从本地文件中文件中读取内容 * @return */ public String getContent(String path,String str){ StringBuilder result = new StringBuilder(); try{ File file=new File(path+File.separator+str); BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件 String s = null; while((s = br.readLine())!=null){//使用readLine方法,一次读一行 result.append(s+"\r\n"); } br.close(); }catch(Exception e){ e.printStackTrace(); return null; } return result.toString(); }
FileReader和FileWriter这两个流都是字符流,都是以一个字符为单位进行输入和输出的。所以读取和写入占用2个字节的字符时都可以正常地显示出来,以上是以File(文件)这个类型为例对节点流进行了讲解,所谓的节点流指定就是直接把输入流或输出插入到数据源上,直接往数据源里面写入数据或读取数据。
八、缓冲流(Buffering):
BufferedReader 提供了按行读取(readLine)方法,用于读取一行字符串,已\r 或 \n分割。带有缓冲区的,缓冲区(Buffer)就是内存里面的一小块区域,读写数据时都是先把数据放到这块缓冲区域里面,减少io对硬盘的访问次数,保护我们的硬盘。可以把缓冲区想象成一个小桶,把要读写的数据想象成水,每次读取数据或者是写入数据之前,都是先把数据装到这个桶里面,装满了以后再做处理。这就是所谓的缓冲。先把数据放置到缓冲区上,等到缓冲区满了以后,再一次把缓冲区里面的数据写入到硬盘上或者读取出来,这样可以有效地减少对硬盘的访问次数,有利于保护我们的硬盘。
缓冲流测试代码:
package com.anshuo; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; public class TestBufferStream { public static void main(String args[]) { FileInputStream fis = null; try { fis = new FileInputStream("F:\\text1.txt"); // 在FileInputStream节点流的外面套接一层处理流BufferedInputStream BufferedInputStream bis = new BufferedInputStream(fis); int c = 0; System.out.println((char) bis.read()); System.out.println((char) bis.read()); bis.mark(100);// 在第100个字符处做一个标记 for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) { System.out.print((char) c); } System.out.println(); bis.reset();// 重新回到原来标记的地方 for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) { System.out.print((char) c); } bis.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e1) { e1.printStackTrace(); } } }
缓存读写:
package com.anshuo; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; public class TestBufferStream1 { public static void main(String args[]){ try{ BufferedWriter bw = new BufferedWriter(new FileWriter("F:\\text11.txt")); //在节点流FileWriter的外面再套一层处理流BufferedWriter String s = null; for(int i=0;i<100;i++){ s = String.valueOf(Math.random());//“Math.random()”将会生成一系列介于0~1之间的随机数。 // static String valueOf(double d)这个valueOf()方法的作用就是把一个double类型的数转换成字符串 //valueOf()是一个静态方法,所以可以使用“类型.静态方法名”的形式来调用 bw.write(s);//把随机数字符串写入到指定文件中 bw.newLine();//调用newLine()方法使得每写入一个随机数就换行显示 } bw.flush();//调用flush()方法清空缓冲区 BufferedReader br = new BufferedReader(new FileReader("F:\\text11.txt")); //在节点流FileReader的外面再套一层处理流BufferedReader while((s = br.readLine())!=null){ //使用BufferedReader处理流里面提供String readLine()方法读取文件中的数据时是一行一行读取的 //循环结束的条件就是使用readLine()方法读取数据返回的字符串为空值后则表示已经读取到文件的末尾了。 System.out.println(s); } bw.close(); br.close(); }catch(Exception e){ e.printStackTrace(); } } }
程序的输入指的是把从文件读取到的内容存储到为程序分配的内存区域里面去。流,什么是流,流无非就是两根管道,一根向里,一根向外,向里向外都是对于我们自己写的程序来说,流分为各种各样的类型,不同的分类方式又可以分为不同的类型,根据方向来分,分为输入流和输出流,根据读取数据的单位的不同,又可以分为字符流和字节流,除此之外,还可以分为节点流和处理流,节点流就是直接和数据源连接的流,处理流就是包在其它流上面的流,处理流不是直接和数据源连接,而是从数据源读取到数据以后再通过处理流处理一遍。缓冲流也包含了四个类:BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter。流都是成对的,没有流是是不成对的,肯定是一个in,一个out。
九、轮换流
转换流非常的有用,它可以把一个字节流转换成一个字符流,转换流有两种,一种叫InputStreamReader,另一种叫OutputStreamWriter。InputStream是字节流,Reader是字符流,InputStreamReader就是把InputStream转换成Reader。OutputStream是字节流,Writer是字符流,OutputStreamWriter就是把OutputStream转换成Writer。把OutputStream转换成Writer之后就可以一个字符一个字符地通过管道写入数据了,而且还可以写入字符串。我们如果用一个FileOutputStream流往文件里面写东西,得要一个字节一个字节地写进去,但是如果我们在FileOutputStream流上面套上一个字符转换流,那我们就可以一个字符串一个字符串地写进去。
转换流测试代码:
package com.anshuo; import java.io.FileOutputStream; import java.io.OutputStreamWriter; public class TestTransform1 { public static void main(String args[]) { try { OutputStreamWriter osw = new OutputStreamWriter( new FileOutputStream("F:\\222222.txt")); osw.write("MircosoftsunIBMOracleApplet");// 把字符串写入到指定的文件中去 System.out.println(osw.getEncoding());// 使用getEncoding()方法取得当前系统的默认字符编码 osw.close(); osw = new OutputStreamWriter(new FileOutputStream( "F:\\222222.txt", true), "ISO8859_1"); // 如果在调用FileOutputStream的构造方法时没有加入true,那么新加入的字符串就会替换掉原来写入的字符串,在调用构造方法时指定了字符的编码 osw.write("MircosoftsunIBMOracleApplet");// 再次向指定的文件写入字符串,新写入的字符串加入到原来字符串的后面 System.out.println(osw.getEncoding()); osw.close(); } catch (Exception e) { e.printStackTrace(); } } }
package cn.gacl.test; import java.io.*; public class TestTransform2{ public static void main(String args[]){ try{ InputStreamReader isr = new InputStreamReader(System.in); //System.in这里的in是一个标准的输入流,用来接收从键盘输入的数据 BufferedReader br = new BufferedReader(isr); String s = null; s = br.readLine();//使用readLine()方法把读取到的一行字符串保存到字符串变量s中去 while(s != null){ System.out.println(s.toUpperCase());//把保存在内存s中的字符串打印出来 s = br.readLine();//在循环体内继续接收从键盘的输入 if(s.equalsIgnoreCase("exit")){ //只要输入exit循环就结束,就会退出 break; } } }catch(Exception e){ e.printStackTrace(); } } }
十、数据流
DataInputStream 和DataOutPutStream 分别继承自 InputStream 和OutputStream ,它属于处理流,需要套接在InputStream 和OutputStream 类型的节点流上。
DataInputStream 和DataOutPutStream 提供了可以存取与机器无关的java原始类型数据,如int 和double的方法。
DataInputStream 和DataOutPutStream 的构造方法为:
DataInputStream (InputStream in) DataOutPutStream(OutputStream out)
数据流测试代码:
package com.anshuo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; public class TestDataStream { public static void main(String args[]){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); //在调用构造方法时,首先会在内存里面创建一个ByteArray字节数组 DataOutputStream dos = new DataOutputStream(baos); //在输出流的外面套上一层数据流,用来处理int,double类型的数 try{ dos.writeDouble(Math.random());//把产生的随机数直接写入到字节数组ByteArray中 dos.writeBoolean(true);//布尔类型的数据在内存中就只占一个字节 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); System.out.println(bais.available()); DataInputStream dis = new DataInputStream(bais); System.out.println(dis.readDouble());//先写进去的就先读出来,调用readDouble()方法读取出写入的随机数 System.out.println(dis.readBoolean());//后写进去的就后读出来,这里面的读取顺序不能更改位置,否则会打印出不正确的结果 dos.close(); bais.close(); }catch(Exception e){ e.printStackTrace(); } } }
通过bais这个流往外读取数据的时候,是一个字节一个字节地往外读取的,因此读出来的数据无法判断是字符串还是bool类型的值,因此要在它的外面再套一个流,通过dataInputStream把读出来的数据转换就可以判断了。注意了:读取数据的时候是先写进去的就先读出来,因此读ByteArray字节数组数据的顺序应该是先把占8个字节的double类型的数读出来,然后再读那个只占一个字节的boolean类型的数,因为double类型的数是先写进数组里面的,读的时候也要先读它。这就是所谓的先写的要先读。如果先读Boolean类型的那个数,那么读出来的情况可能就是把double类型数的8个字节里面的一个字节读了出来。
十一、I/O 流总结