IO流
一、字符流的缓冲区
1.缓冲区的出现提高了对数据的读写效率。
2.对应类:BufferedReader,BufferedWriter。
3.缓冲区要结合流才可以使用。
4.在流的基础上对流的功能进行了增强。
BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入。
缓冲区:的出现是为了提高流的操作效率而出现的,所以在创建缓冲区之前,必须先有流对象。
例:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
//创建一个字符写入流对象
FileWriter fw = new FileWriter("buf.txt");
//为了提高字符写入流的效率,加入了缓冲技术。
//只要将要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
BufferedWriter bw = new BufferedWriter(fw);
bw.write("abce");
//记住,只要用到缓冲区,就要记得刷新。
bw.flush();
//其实关闭缓冲区就是在关闭缓冲区中的流对象。
bw.close();
}
}
newLine():该缓冲区中提供了一个跨平台的换行符newLine()。
例:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
FileWriter fw = new FileWriter("buf.txt");
BufferedWriter bw = new BufferedWriter(fw);
for(int x=1;x<5;x++){
bw.write("abce"+x);
bw.newLine();//换行
bw.flush();
}
bw.close();
}
}
BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符,数组和行的高效读取。
readLine():该缓冲区提供了一个一次读一行的方法readLine(),方便与对文本数据的获取。当返回null时,表示读到了文件末尾,readLine()返回的时候,只返回回车符之前的内容,并不返回回车符。
例:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
//创建一个读取流对象和文件相关联。
FileReader fr = new FileReader("buf.txt");
//为了提高效率,加入缓冲技术。将字符读取流对象作为参数传递给缓冲区对象的构造函数。
BufferedReader br = new BufferedReader(fr);
String line = null;
while((line = br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
练习:通过缓冲区复制一个.java文件。
import java .io.*;
class Demo
{
public static void main(String []args) throws IOException{
//复制其实就是将文件里面的数据拷贝到另一个文件中。
//创建流对象和文件向关联,字符读取流和字符写入流,并与文件关联
FileReader fr = new FileReader("Demo.java");
FileWriter fw = new FileWriter("Demo_copy.java");
//为了提高读写效率,采用字符流缓冲区技术。
BufferedReader br = new BufferedReader(fr);
BufferedWriter bw = new BufferedWriter(fw);
String line = null;
while((line=br.readLine())!=null){
bw.write(line);
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
}
readLine()原理:无论是读一行,还是获取多个字符。其实都是在硬盘上一个一个读取,所以最终使用的还是read()方法一次读一个。
练习:模拟readLine方法。
import java .io.*;
class Demo
{
public static void main(String []args) throws Exception{
FileReader fr = new FileReader("Demo.java");
FileWriter fw = new FileWriter("Demo_copy.java");
MyBufferedReader br = new MyBufferedReader(fr);
BufferedWriter bw = new BufferedWriter(fw);
String line = null;
while((line=br.myReadLine())!=null){
bw.write(line);
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
}
class MyBufferedReader extends Reader
{
private Reader r;
MyBufferedReader(Reader r){
this.r = r;
}
public String myReadLine() throws IOException{
StringBuilder sb = new StringBuilder();
int len = 0;
//当读到换行时,就把字符串返回给调用者。在windows中“\r\n”代表换行。
while((len = r.read())!=-1){
char ch = (char)len;
if('\r'==ch)
continue;
if('\n'==ch)
return sb.toString();
sb.append(ch);
}
return null;
}
public void close() throws IOException{
r.close();
}
public int read(char[] cbuf,int off,int len) throws IOException{
return r.read(cbuf,off,len);
}
}
装饰设计模式:当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有对象的功能并提供加强功能,那么自定义的该类,就称为装饰类。装饰类通常通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
例:
class Perso
{
public void chifan(){
System.out.println("吃饭");
}
}
class SuperPerson
{
private Person p;
SuperPerson(Person p){
this.p = p;
}
public void superChifan(){
System.out.println("开胃酒");
p.chifan();
System.out.println("甜点");
System.out.println("来一根");
}
}

答:
MyReader
|——MyTextReader
|————MyBufferedTextReader
|——MyMediaReader
|————MyBufferedMediaReader
|——MyDateReader
|————MyBufferedDateReader
虽然为了扩展功能,可以使用继承的思想,但是会使得体系过于臃肿。由于他们的功能都是读取数据,那么可以抽取出来使用装饰类的思想进行优化。
class MyBufferedReader
{
MyBufferedReader(MyTextReader text){
}
MyBufferedReader(MyMediaReader media){
}
MyBufferedReader(MyDateReader date){
}
}
其实上面代码的扩展性还是很差,可以找其参数的共同类型,通过多态的形式,可以提高扩展性。装饰类因为增强已有对象具备的功能和已有的是相同的,只不过提供了更强的功能,所以装饰类和被装饰类通常都属于一个体系中。
class MyBufferedReader extends MyReader
{
MyBufferedReader(MyReader r){
}
}
这样一来不仅代码上简化了,还提高了扩展性,降低了类与类之间的关系。
注:继承不要写得过多,不要为了几个功能而去继承,这样会很臃肿。推荐使用装饰设计模式。
LineNumberReader:带行号的读取流,也是一种包装类。顾名思义就是能读取行号。
例:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
LineNumberReader lnr = new LineNumberReader(new FileReader("Demo.java"));
String line = null;
lnr.setLineNumber(100);
while((line = lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
练习:模拟一个带行号的缓冲区对象
第一种方式:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
MyLineNumberReader lnr = new MyLineNumberReader(new FileReader("Demo.java"));
String line = null;
lnr.setLineNumber(100);
while((line = lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
class MyLineNumberReader
{
private Reader r;
private int num = 0;
MyLineNumberReader(Reader r){
this.r = r;
}
public void setLineNumber(int num){
this.num = num;
}
public int getLineNumber(){
return num;
}
public String readLine() throws IOException{
num++;
int len = 0;
StringBuilder sb = new StringBuilder();
while((len = r.read())!=-1){
char ch = (char)len;
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
sb.append(ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
}
第二种方式继承BufferefReader:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
LineNumberReader lnr = new LineNumberReader(new FileReader("Demo.java"));
String line = null;
lnr.setLineNumber(100);
while((line = lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
class MyLineNumberReader extends BufferedReader
{
private int num = 0;
MyLineNumberReader(Reader r){
super(r);
}
public void setLineNumber(int num){
this.num = num;
}
public int getLineNumber(){
return num;
}
public String readLine() throws IOException{
num++;
return super.readLine();
}
}
二、字节流
字节流:InputStream,OutputStream。
需求:写入数据到“.txt”文件中。
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
FileOutputStream fos = new FileOutputStream("Test.txt");
fos.write("abc".getBytes());
}
}
分析:程序没有刷新方法,但数据成功写入文件。其实字符流底层走的是字节流的方法,因为要进行查询编码表操作,会用到缓冲区,所以多了一刷新操作。直接使用字节流如果没有指定缓冲区是不用刷新的。
字节读取流演示:
方式一:一个一个读
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
method_01();
}
public static void method_01() throws IOException{
FileInputStream fis = new FileInputStream("Test.txt");
int ch = 0;
while((ch = fis.read())!=-1){
System.out.print((char)ch);
}
fis.close();
}
}
方式二:一个数组一个数组读。
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
method_02();
}
public static void method_02() throws IOException{
FileInputStream fis = new FileInputStream("Test.txt");
byte[] by = new byte[1024];
int len = 0;
while((len = fis.read(by))!=-1){
System.out.print(new String(by,0,len));
}
fis.close();
}
}
方式三:全部读取完
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
method_03();
}
public static void method_03() throws IOException{
FileInputStream fis = new FileInputStream("Test.txt");
byte[] by = new byte[fis.available()];//定义一个刚刚好的缓冲区,不用再循环。缺点:只适合小文件。
int len = fis.read(by);
System.out.print(new String(by,0,len));
fis.close();
}
}
练习:复制一个图片。
思路:
1.用字节读取流对象和图片关联。
2.用字节写入流创建一个图片文件,用于存储获取到的图片数据。
3.通过循环读写完成数据的存储。
4.关闭资源。
import java.io.*;
class Demo
{
public static void main(String []args){
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
byte[] by = new byte[1024];
int len = 0;
while((len = fis.read(by))!=-1){
fos.write(by,0,len);
}
}
catch(IOException e){
throw new RuntimeException("图片复制失败");
}
finally{
try{
if(fis==null){
fis.close();
}
}
catch(IOException e){
System.out.println("读取流关闭失败");
}
try{
if(fos==null){
fos.close();
}
}
catch(IOException e){
System.out.println("写入流关闭失败");
}
}
}
}
注:字符流不能操作媒体文件,因为字符流会去查询编码表,这时文件将会损坏。
练习:MP3的复制,通过缓冲区。
import java.io.*;
//模板设计模式,用来获取出现运行时间
abstract class RunCode
{
public abstract void runCode() throws Exception;
public final void startRun() throws Exception{
long start = System.currentTimeMillis();
runCode();
long end = System.currentTimeMillis();
System.out.println(end-start+"ms");
}
}
class Demo extends RunCode
{
public static void main(String []args) throws Exception{
Demo d = new Demo();
d.startRun();
}
public void runCode() throws IOException{
copy_Mp3();
}
public static void copy_Mp3() throws IOException{
FileInputStream fis = new FileInputStream("1.mp3");
FileOutputStream fos = new FileOutputStream("2.mp3");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int by = 0;
while((by = bis.read())!=-1){
bos.write(by);
}
bis.close();
bos.close();
}
}
练习:自定义字节流缓冲区。
import java.io.*;
abstract class RunCode
{
public abstract void runCode() throws Exception;
public final void startRun() throws Exception{
long start = System.currentTimeMillis();
runCode();
long end = System.currentTimeMillis();
System.out.println(end-start+"ms");
}
}
class Demo extends RunCode
{
public static void main(String []args) throws Exception{
Demo d = new Demo();
d.startRun();
}
public void runCode() throws IOException{
copy_Mp3();
}
public static void copy_Mp3() throws IOException{
FileInputStream fis = new FileInputStream("1.mp3");
FileOutputStream fos = new FileOutputStream("2.mp3");
MyBufferedInputStream bis = new MyBufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int by = 0;
while((by = bis.read())!=-1){
bos.write(by);
}
bis.close();
bos.close();
}
}
class MyBufferedInputStream extends InputStream
{
private int count;
private int pos;
private byte[] by = new byte[1024*1024];
private byte b;
private InputStream in;
MyBufferedInputStream(InputStream in){
this.in = in;
}
public int read() throws IOException{
if(count==0){
count = in.read(by);
if(count<0)
return -1;
pos = 0;
b = by[pos];
count--;
return b&255;
}
else if(count>0){
b = by[pos];
pos++;
count--;
return b&255;
}
else
return -1;
}
}

这时byte转int会在前面添1,这与int的-1相同,条件不满足就会跳过循环导致数据遗漏。要避免这种情况就要在byte提升时前面添0而不是添1。办法就是&255。

答:因为write做了强制转换动作。强制转为了字节,只取了最低8位。
练习:读取键盘录入。
System.out:对应的是标准输出设备。
System.in:对应的是标准输入设备(默认是键盘)。
例:
import java.io.*;
class Demo
{
public static void main(String []args) throws Exception{
BufferedInputStream bis = new BufferedInputStream(System.in);
int by1 = bis.read();
int by2 = bis.read();
int by3 = bis.read();
System.out.println(by1);
System.out.println(by2);
System.out.println(by3);
}
}
输入“abc”
输入“a”
解析:因为回车是“\r\n”,“\r”是13,“\n”是10。
练习:通过键盘录入数据,当录入一行数据后,就将该行数据进行打印,如果录入的数据是“over”,那么停止录入。
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while(true){
int ch = in.read();
if(ch=='\r')
continue;
if(ch=='\n'){
String s = sb.toString();
if(s.equals("over"))
break;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length());
}
else
sb.append((char)ch);
}
in.close();
}
}
通过上面实例发现,其实就是读一行数据的原理,也就是readLine方法。

答:readLine方法是字符流BufferedReader类中的方法。而键盘录入的read方法是字节流InputStream的方法。

答:使用InputStreamReader。
InputStreamReader:字节流通向字符流的桥梁。
优化:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
//获取键盘录入对象
InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流:InputStreamReader
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作,使用BufferedReader
BufferedReader br = new BufferedReader(isr);
String line = null;
while((line = br.readLine())!=null){
if(line.equals("over"))
break;
System.out.println(line.toUpperCase());
}
}
}
OutputStreamWriter:字符流通向字节流的桥梁。
例:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
OutputStream os = System.out;
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(osw);
String line = null;
while((line = br.readLine())!=null){
if(line.equals("over"))
break;
bw.write(line.toUpperCase());
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
}
简化:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));(键盘录入最常见写法)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
例:
import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line = br.readLine())!=null){
if(line.equals("over"))
break;
bw.write(line.toUpperCase());
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
}
static void setIn(InputStream in)
重新分配“标准”输入流。
static void setOut(PrintStream out)
重新分配“标准”输出流。
练习:将异常信息存储到文件中。import java.io.*;
class Demo
{
public static void main(String []args) throws IOException{
try{
int[] arr = new int[2];
System.out.println(arr[3]);
}
catch(Exception e){
try{
System.setOut(new PrintStream("exception.log"));
e.printStackTrace(System.out);
}
catch(IOException ex){
throw new RuntimeException("日志文件创建失败");
}
}
}
}
专门的日志工具包:log4j
Properties:
void list(PrintStream out)
将属性列表输出到指定的输出流。
例:
import java.io.*;
import java.util.*;
class Demo
{
public static void main(String []args){
Properties pro = System.getProperties();
pro.list(System.out);
}
}
打印到文件里:
import java.io.*;
import java.util.*;
class Demo
{
public static void main(String []args) throws IOException{
Properties pro = System.getProperties();
pro.list(new PrintStream("exception.log"));
}
}