完整的流家族
IO(Input Output)流
- IO 流用来处理设备之间的数据传输
- Java 对数据的操作时通过流的方式
- Java 用于操作流的对象都在 IO 包中
- 流按操作数据分为两种: 字节流和字符流
- 流按类型分为: 输入流, 输出流
IO 流常用基类
- 字节流的抽象基类对象: InputStream, OutputStream
- 字符流的抽象基类对象: Reader, Writer
注:有这四个类派生出来的子类名称都是由其父类名作为子类名的后缀
如: InputStream 的子类 FileInputStream
如: Reader 的子类 FileReader
可以进行二进制形式进行图片, 音乐等文件的读写
1 2 3 4 5 6 7 8 9 10
| public static void copyFile() throws IOException{ InputStream fis = new FileInputStream("D:\\source.jpg" ); OutputStream fos = new FileOutputStream("D:\\dst.jpg" ); int len = 0; byte[] buf = new byte[1024]; while((len=fis.read(buf))!=-1){ fos.write(buf, 0, len); } fis.close();
|
拷贝一首歌(使用字节流的 Buffered 缓冲区)
1 2 3 4 5
| InputStream inputStream = new BufferedInputStream( new FileInputStream("D:" + File.separator + "刘涛 - 说不出口.mp3")); OutputStream outputStream = new BufferedOutputStream( new FileOutputStream("D:" + File.separator + "群星 - 那就别说.mp3")); ...
|
字符流 FileReader 和 FileWriter
创建一个 FileWriter 对象,该文件会在指定目录下创建。
如果同名则覆盖, 除非构造方法第二个参数 append 为 true; 由此可得默认为 false。
1 2 3 4 5 6 7 8
| public static void createFile(String str) throws IOException{ / /在 D 盘下创建 abc.txt. 如果同名则覆盖,除非构造方法第二个参数append为true; File file = new File("D:" + File.separator + "abc.txt"); FileWriter fw = new FileWriter(file, false); fw.write(str); fw.flush(); fw.close(); }
|
IO 异常的标准处理方式一(以 FileWriter 为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| String fileName = "D:" + File.separatorChar + "abc.txt";
FileWriter fw = null; try { fw = new FileWriter(fileName); fw.write("balabala..."); fw.flush(); } catch (IOException e) { throw new RuntimeException("产生 IO 异常"); } finally { if (fw != null) try { fw.close(); } catch (IOException e) { throw new RuntimeException("流关闭异常"); } }
|
IO 异常的标准处理方式二(SE 7 try-with-resources方式)
1 2 3 4 5 6 7 8 9
| String fileName = "D:" + File.separatorChar + "abc.txt";
try (FileWriter fw = new FileWriter(fileName)) { fw.write("balabala..."); fw.flush(); } catch (IOException e) { throw new RuntimeException("产生IO异常"); }
|
使用 FileReader 读取文本文件方式一
1 2 3 4
| int ch = 0; while((ch=fr.read())!=-1){ }
|
使用 FileReader 读取文本文件方式二(较方法一好,推荐使用)
1 2 3 4 5
| int len= 0; char[] buf = new char[1024]; while((len=fr.read(buf))!=-1){ }
|
字符流的缓冲流 BufferedReader 与 BufferedWriter
- 提高了对数据的读写效率
- 对应类: BufferedReader 和 BufferedWriter
- 缓冲区要结合流才可以使用
- 在流的基础上对流的功能进行了增强
BufferedWriter
- 为提高字符写入流的效率,只要将需要提高效率的流对象作为参数传递到 BufferedWriter 的构造方法
- BufferedWriter 有自己特有的 readLine()方法, 这是不包含行结束符的
- 如果需要每次换行则 bfr.newLine(), 并且还要 flush() 一下。最后不要忘记 close 流
我的理解是 BufferedWriter 和 BufferedReader 都是基于且衷于原流。提供了每行的写与读,而不参杂多余的行终止符.所以要每次自己换行
BufferedReader 用于读取数据
1 2 3 4
| String line = null; while((line = bfr.readLine()) != null){ }
|
使用 BufferedWriter, BufferedReader 拷贝文件关键代码
1 2 3 4 5 6
| String line = null; while((line = bfr.readLine()) != null){ bfr.write(line); bfr.newLine(); bfr.flush(); }
|
牵扯到装饰设计模式
- 当先要对已有对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。
- 装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
- 装饰模式比继承要灵活,避免了继承体系臃肿.而且降低了类与类之间的关系,装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能.所以装饰类和比装饰类通常是都属于一个体系中。
LineNumberReader
BufferedReader()的子类,只是多了标号而已.
通过setLineNumber 设置初始行号, 和输出可以 getLineNumber 获取每行的行号
和 InputStreamReader 类似,是字符流通向字节流的桥梁
1 2 3 4 5 6
| OutputStream os = System.out;
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(os); outputStreamWriter.write("cba"); outputStreamWriter.flush(); outputStreamWriter.close();
|
键盘录入
System.out: 对应的是标准输入设备,比如控制台
System.in: 对应的是标准输出设备,比如键盘
练习: 通过键盘录入,当输入一行数据后将改行数据进行打印,如果录入的数据是 over,那么停止录入.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
public static void method1() throws IOException{ final InputStream in = System.in; int character ; final StringBuilder stringBuilder = new StringBuilder(); while(true){ character = in.read();
switch (character) { case '\r': break; case '\n': final String result = stringBuilder.toString(); if("over".equals(result)){ return; } / /StringBuilder 的清空方式 stringBuilder.delete(0, stringBuilder.length()); break; default: stringBuilder.append((char)character); break; } } }
public static void method2() throws IOException{ final InputStream in = System.in; final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)); String line = null; while ((line = bufferedReader.readLine()) != null) { if ("over".equals(line)){ break; } } }
public static void method3(String[] args) throws IOException { final InputStream in = System.in; String line = null; final Scanner scanner = new Scanner(in); while(scanner.hasNextLine()){ line = scanner.nextLine(); if("over".equals(line)){ break; } } }
|
打印流 PrintStream 和 PrintWriter
该流提供了打印方法,可以将各种类型的数据原样打印
PrintStream
PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream;这意味着可在写入 byte 数组之后自动调用 flush 方法,可调用其中一个 println 方法,或写入一个换行符或字节 (‘\n’)。
PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
PrintWriter
1 2 3 4 5 6 7 8
| PrintWriter pw = new PrintWriter(System.out, true); String line = null; BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in)); while ((line = bfr.readLine()) != null) { pw.println(line); }
|
练习: 文件的分割与合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public static void split() throws IOException{ FileInputStream fis = new FileInputStream("d:\\123.pdf" ); FileOutputStream fos = null; int len = 0; byte[] buf = new byte[1024*1024]; int i=0; while((len=fis.read(buf))!=-1){ fos = new FileOutputStream("d:\\" +(++i)+ ".part"); fos.write(buf, 0, len); fos.flush(); fos.close();; } fis.close(); }
public static void meger() throws IOException{ Vector<FileInputStream> v = new Vector<FileInputStream> (); for(int i=1;i<4;i++) v.add( new FileInputStream("d:\\" + i+".part")); SequenceInputStream sis = new SequenceInputStream(v.elements()); FileOutputStream fos = new FileOutputStream("d:\\kk.pdf" ); int len = 0; byte[] buf = new byte[1024]; while((len=sis.read(buf)) != -1){ fos.write(buf, 0, len); } sis.close(); fos.close(); }
|
- 被操作的对象需要实现 Serializable(标记接口)
- 可序列化类可以通过声明名为 “serialVersionUID” 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
这样生成新的类不会改变UID,而不是使用系统生成的 UID.
- 另外非静态成员变量可以transient修饰不被序列化,同样类(static)变量也不会序列化.
输入输出可以直接连接,结合线程使用
PipedInputStream,接收 InputStream 对象
用于与另一输出管道相连, 读取写入到输出管道中的数据,用于程序中线程的通信
PipedOutputStream, 可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。通常,数据由某个线程写入 PipedOutputStream 对象,并由其他线程从连接的 PipedInputStream 读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| public class DemoPipedStream {
public static void main(String[] args) throws IOException { PipedInputStream pis = new PipedInputStream(); PipedOutputStream pos = new PipedOutputStream(pis); new Thread(new Write(pos)).start(); new Thread(new Read(pis)).start(); } }
class Read implements Runnable{ private PipedInputStream pis; public Read(PipedInputStream pis){ this.pis = pis; } @Override public void run(){ byte[] buf = new byte[200]; try{ System.out.println("---读取流开始获取信息"); int len = pis.read(buf); System.out.println("--"+new String(buf,0,len)); System.out.println("---读取流读取流信息完毕"); } catch (IOException e){ throw new RuntimeException("流读取异常"); } finally { if (pis!=null) try {pis.close();} catch(IOException e) { throw new RuntimeException("流关闭异常"); } } } }
class Write implements Runnable{ private PipedOutputStream pos; public Write(PipedOutputStream pos){ this.pos = pos; } public void run(){ try { System.out.println("写入流开始写入流信息,持续3S"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } pos.write("Hello PipedStram".getBytes()); System.out.println("写入流开始写入完毕"); } catch (IOException e) { throw new RuntimeException("流写入异常"); } finally { if(pos!=null) try{pos.close();} catch(IOException e){ throw new RuntimeException("流关闭异常"); } } } }
|
操作基本数据类型的流
以二进制格式读写基本 Java 类型
DataInputStream 与 DataOutputstream
操作字节数组
ByteArrayInputStream 与 ByteArrayOutputStream
操作字符数组
CharArrayReader 与 CharArrayWriter
操作字符串
StringReader 与 StringWriter
总结:
以二进制格式写出数据,可以使用 DataOutputStream。
以文本格式写出数据,可以使用 PrintWriter。
1
| PrintWriter printWriter = new PrintWriter("setting.dat", "UTF-8");
|
字符集
在过去,国际化字符集已经得到了处理,但是处理得很不系统,散布在 Java 类库的各处。在 Java SE 1.4 中引入的 java.nio 包用 Charset 类统一了对字符集的转换(注意 s 是小写的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Charset.defaultCharset(); Charset charset = Charset.forName("UTF-8");
for(String alias: charset.aliases()){ System.out.println(alias); }
ByteBuffer bytBuffer = charset.encode("自古中秋月最明, 凉风届候夜弥清"); byte[] bytes = bytBuffer.array();
CharBuffer cbuf = charset.decode(bytBuffer); System.out.println(cbuf.toString());
|
字符编码
//“你好”–>"??"是 GBK 变 utf-8 ; -->"浣犲ソ"是utf-8变GBK
编一次解一次即可
记录
改变标准输入输出设备
System 的 setIn()方法 重新分配“标准”输入流。否则标准输入流一般都是键盘 InputStream.
System 的 setOut()方法 重新分配“标准”输出流。否则标准输入流一般都是键盘 PrintStream.
可以利用这两个已关联的流进行相关操作
流操作规律
- 明确源和目的
- 是否是纯文本(字节流 和 字符流的选取), 具体使用哪个对象
- 是否需要提高效率而加入缓冲
这其中涉及到的 OutputStream(OutputStream out, String charsetName)就是字符转字节的桥梁,并可以指定自定义编码,例如 “UTF-8”,这也是转换流出现的原因。
总结
关闭流
总体可以认为是“先开后关”原则,原因是 IO 流的打开顺序是固定且层层依托的。
然后是 closeable 关闭的优化
如果 new FileOutputStream, 然后上级目录不存在会抛出 FileNotFoundException
异常, 所以需要先行创建上层文件夹。