一、概述。
IO流是用来处理设备之间的数据传输的,在Java中对数据传输的操作是用流的方式,而且这些流对象都存在于IO包中。
流按照操作数据可以分成:字节流和字符流。其中字节流是根本,字符流内部封装了字节流,即字符流是以指定的方式(GBK或者UTF-8)进行编码和解码的字节流,它主要是对文本文件——有字符的文件进行操作,而字节流主要用于对非文本文件——图片,mp3,视频等进行操作。
流按照流向可以分成:输入流和输出流。
二、IO流常用抽象基类
字节流的抽象基类有:InputStream——字节输入流;OutputStream——字节输出流
字符流的抽象基类有:Reader——字符输入流;Writer——字符输出流
这四个类都是抽象类,需要子类分别继承这些类,而且子类名称都以它的父类为后缀。如:
FileInputStream就是InputStream的子类
FileReader就是Reader的子类
三、字符流对象的基本创建。
那么,我们如何创建字符流流对象呢?
首先,我们要操作流对象必须有流类,流类都在java.io包中,需要导入该包。
我们一字符输出流FileWriter为例子:
1、创建流对象
FileWriter fw = new FileWriter(“路径或者文件名,或者路径加上文件名”);//注意,数据从流中输出到该文件中,若文件在当前路径(若指定路径了则在指定路径判断)中不存在,则创建该文件,若存在,则覆盖该文件,如果想要在文件中续写则为下面的格式:
FileWriter fw = new FileWriter("路径或者文件名,或者路径加上文件名",true)
2、进行流操作
fw.Write("字符数据");//将流中的二进制字符数据存入缓冲区中,然后解码进入文件中。
3、刷新
fw.flush();//因为字符输出流是一个用字节输出流(一个字节一个字节输出,但是需要一个或者多个字节才能解码一个字符)然后解码的过程,所以需要一个缓冲区(存放解码的结果),这种情况下需要对缓冲区进行刷新操作才能将数据(解码结果)真正的存入文件中。所以字符输出流需要刷新操作而字节输出流不需要刷新操作。
4、关闭资源
fw.close();//关闭资源时也会执行刷新操作。
注意,不论是创建流对象,输入或者输出数据还是关闭流资源都会产生异常,需要处理,而且在finally代码块之中关闭流资源,因为该操作是必须要完成的。
与其对应的是字符输入流FileReader
1、创建流对象
FileReader fr = new FileReader("文件名或者路径加上文件名");//该文件必须存在,若不存在会产生异常。
2、进行输入操作
fr.read();//该操作代表将文件中一个字符按照指定方式编码成二进制格式读入流中,返回值是一个int类型的整数代表该字符(以GBK或者UTF-8的字符和数字对应形式),读到文件末尾会返回-1代表文件读完了。
fr.read(arr[]);//该操作代表将文件中若干字符编码成二进制形式读入数组arr[]中,该数组每一个元素存放一个字符,返回的是一个int类型整数代表存入数组多少个字符。读到文件末尾会返回-1代表文件读完了。
3、关闭流资源
fr.close;
注意,不论是创建流对象,输入或者输出数据还是关闭流资源都会产生异常,需要处理,而且在finally代码块之中关闭流资源,因为该操作是必须要完成的。
例子,从一个文件中读取数据然后存入另一个文件中:
package com.itheima;
import java.io.*;
public class WriterReaderDemo
{
public static void main(String[] args)
{
FileReader fr = null;//对象的引用声明
//FileWriter fw1 = null;
FileWriter fw2 = null;//必须要在代码块try之外定义,不然finally代码块中无法访问这三个对象
try
{
//该文件必须存在,若不存在会产生异常。
fr=new FileReader("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-12-1.txt");
//注意,数据从流中输出到该文件中,若文件在当前路径(若指定路径了则在指定路径判断)中不存在,则创建该文件,若存在,则覆盖该文件
//fw1=new FileWriter("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-12-1(复件).txt");
//如果想要在文件中续写则为下面的格式
fw2=new FileWriter("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-12-1(复件).txt",true);
/*int x=0;
while((x=fr.read())!=-1)该操作代表将文件中一个字符按照指定方式编码成二进制格式读入流中,返回值是一个int类型的整数代表该字符(以GBK或者UTF-8的字符和数字对应形式),
读到文件末尾会返回-1代表文件读完了。
{
fw1.write(x);//将流中的二进制字符数据存入缓冲区中,然后解码进入文件中。
fw1.flush();因为字符输出流是一个用字节输出流(一个字节一个字节输出,但是需要一个或者多个字节才能解码一个字符)然后解码的过程,
所以需要一个缓冲区(存放解码的结果),这种情况下需要对缓冲区进行刷新操作才能将数据(解码结果)真正的存入文件中。
所以字符输出流需要刷新操作而字节输出流不需要刷新操作。
}*/
char[] arr=new char[1024];
int lien =0;
while((lien=fr.read(arr))!=-1)//读入流中的第二种方式:将文件中若干字符串解码读入流中然后存在字符数组中,返回为int整数代表读入多少个字符,若读完了则返回-1
{
fw2.write(arr, 0, lien);//写出流的第二种方式,将数组中的0角标到lien角标位置的数据解码存入文件中,续写方式
fw2.flush();
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
fr.close();
}
catch(IOException e)
{
e.printStackTrace();
}
/*try
{
fw1.close();
}
catch(IOException e)
{
e.printStackTrace();
}*/
try
{
fw2.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
四、字符流缓冲区和装饰设计模式
创建缓冲区是为了提高对数据的读写效率,其对应的类为:BufferedWriter和BufferedReader,它们需要结合流来使用是在流的基础上对流功能的加强。
其用法如下:
package com.itheima;
import java.io.*;
public class BufferedReaderWriter
{
public static void main(String[] args)
{
BufferedReader br =null;
BufferedWriter bw = null;
try
{ //定义一个BufferdReader将FileReader对象传入其中
br=new BufferedReader(new FileReader("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-11-30.txt"));
//定义一个BufferdWriter将FileWriter对象传入其中
bw=new BufferedWriter(new FileWriter("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-11-30(复件).txt"));
String line = null;//定义空引用
while((line=br.readLine())!=null)/*BufferedReader的方法,用于读取一行的数据,若为字符流则编码一行字符读入流中,若为字节流则读入一行字节,该方法
返回值为字符串,若读取完毕则返回null,它读取一行不会读取换行符,所以在写出时要加上换行符*/
{ //bw.write(line+"\r\n");
bw.write(line);
bw.newLine();//是BufferdWriter的方法,相当于"\r\n"换行
bw.flush();//BufferedReader也需要刷新!
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
br.close();
}
catch (IOException e2)
{
e2.printStackTrace();
}
try
{
bw.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
主要注意的就是BufferedReader的readLine()方法和BufferWriter的newLine()方法
所谓装饰设计模式就类似于BufferedReader这种类型的类,它对FileReader进行了增强和功能的改变,其基本格式就如对BufferedReader的实例化一样,它和继承有什么区别:
装饰类比继承要灵活,谁需要被强化就将该被强化对象传入装饰类,避免了继承体系的臃肿(若是继承体系则每一个需要强化的类都要建立装饰类子类),而且逻辑上来说,装饰类和被装饰类并没有子父类关系,所以它们不能仅是为了代码复用而继承,用装饰设计模式很好的降低了装饰类和被装饰类之间的关系。
装饰类作为被装饰类的强化版,具备的功能和被装饰类一样,只不过增加了新的功能,所以装饰类和被装饰类都是同一个体系中的。
现在我们来自己做一个MyBufferedReader装饰类:
package com.itheima;
import java.io.*;
class MyBufferedReader extends Reader//BufferedReader继承了Reader这个抽象类,需要复写其方法
{
Reader r;//字符流的最上级抽象类
MyBufferedReader(Reader r)
{
this.r=r;
}
public String myReadLine() throws IOException
{
int x=0;
String s="";//请注意""和null的区别,一个有对象一个没有对象!
while((x=r.read())!=-1)//readLIne方法只读一行不读换行符
{
if(x=='\r')
continue;
if(x=='\n')
return s;
else
s=s+(char)x+"";//null+字符和""+字符是有区别的!
}
if(s.length()!=0)//最后的一行若没有换行符!
return s;
return null;
}
public int read(char[] cbuf, int off, int len) throws IOException
{
int x=r.read(cbuf, off, len);
return x;
}
public void close() throws IOException
{
r.close();
}
}
public class MyBufferedReaderDemo
{
public static void main(String[] args) throws IOException
{
MyBufferedReader mbr = new MyBufferedReader(new FileReader("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-11-30.txt"));
String line = null;
while((line=mbr.myReadLine())!=null)
{
System.out.println(line);
}
mbr.close();
}
}
如何自定义一个装饰类?
该装饰类必须继承最上级抽象类,然后在构造方法中传入抽象类引用,利用多态的特点指向子类对象,然后用传入的子类对象的复写方法复写装饰类中的抽象方法,利用传入子类对象的方法定义自己的强化方法。如上面的例子。
五、LineNumberReader类和自定义的LineNumberReader类
1、setLineNumber(int number);这个方法可以设置当前的行号
注意:LineNumberReader也有readLIne方法
package com.itheima;
import java.io.*;
public class LineNumberReaderDemo
{
public static void mian(String[] args) throws IOException
{
LineNumberReader lnr = new LineNumberReader(new FileReader("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-11-30.txt"));
String line = null;
lnr.setLineNumber(100);
while((line=lnr.readLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
现在我们自己定义一个MyLineNumberReader强化类,主要定义三个方法setLineNumber(),getLineNumber(),readLine()
package com.itheima;
import java.io.*;
class MyLineNumberReader extends Reader
{
private int line = 0;
Reader r;
public int getLineNumber()
{
return line;
}
public void setLineNumber(int line)
{
this.line=line;
}
MyLineNumberReader(Reader r)
{
this.r=r;
}
public String myReadLine() throws IOException
{
int x=0;
StringBuilder sb=new StringBuilder();
while((x=r.read())!=-1)
{
if(x=='\r')
continue;
if(x=='\n')
{
line++;
return sb.toString();
}
else
sb.append((char)x);
}
if(sb.length()!=0)
{
line++;
sb.toString();
}
return null;
}
public int read(char[] cbuf, int off, int len) throws IOException {
return r.read(cbuf, off, len);
}
public void close() throws IOException {
r.close();
}
}
public class MyLineNumberReaderDemo
{
public static void mian(String[] args) throws IOException
{
MyLineNumberReader lnr = new MyLineNumberReader(new FileReader("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-11-30.txt"));
String line = null;
lnr.setLineNumber(100);
while((line=lnr.myReadLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
六、字节流读写基本操作
想要操作图片等数据,这些数据和文本数据有什么不同呢?其实这些数据都不需要进行编码和解码的操作,这是因为图片等非文本数据全部都是有二进制组成的,所以不需要转换格式来表示信息。
这是就要使用到字节流对象,所谓字节流,它是以字节为基本单位进行输入和输出的,不需要编码和解码。它的两个抽象基类分别是:InputStream和OutputStream。
它的内部封装的方法基本和Reader和Writer差不多,但在read(arr[])方法中数组是字节数组,而且write()方法不需要刷新,原因已经在字符流的write()里面都说明完毕了。
package com.itheima;
import java.io.*;
public class InputOutputStreamDemo
{
public static void main(String[] args) throws IOException
{
FileInputStream fi=new FileInputStream("C:\\Users\\彭攀\\Desktop\\简历.files\\ppphoto.jpg");
FileOutputStream fo=new FileOutputStream("C:\\Users\\彭攀\\Desktop\\简历.files\\ppphoto(复件).jpg");
//method_1(fi,fo);
method_2(fi,fo);
}
public static void method_1(InputStream is,OutputStream os) throws IOException
{
int x = 0;//read()方法返回的一样是int类型的整数,代表读取的一个字节,但是是4个字节表示的,若读取完毕则返回-1
while((x=is.read())!=-1)
{
os.write(x);//以最末尾一个字节写出流对象
}
is.close();
os.close();
}
public static void method_2(InputStream is,OutputStream os) throws IOException
{
byte[] arr = new byte[1024];//每一次最多可以读取1024个字节
int x=0;//read(arr[])返回的是存放在数组中字节的个数,最多1024个,读取完毕则返回-1
while((x=is.read(arr))!=-1)
{
os.write(arr, 0, x);//将角标从0-x的字节写出!这里不需要解码,所以不需要刷新
}
is.close();
os.close();
}
}
七、字节流缓冲区以及自定义字节流缓冲区。
package com.itheima;
import java.io.*;
public class BufferedInputStreamDemo
{
public static void main(String[] args) throws IOException
{
BufferedInputStream bis= new BufferedInputStream(new FileInputStream("C:\\Users\\彭攀\\Desktop\\简历.files\\ppphoto.jpg"));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("C:\\Users\\彭攀\\Desktop\\简历.files\\ppphoto(复件).jpg"));
int line=0;
byte[] arr=new byte[bis.available()];//定义一个文件总容量的容器
/*while((line=bis.read())!=-1)
{
bos.write(line);//不需要刷新
}*/
while((line=bis.read(arr))!=-1)
{
bos.write(arr, 0, line);//不需要刷新!
}
bos.close();
bis.close();
}
}
现在我们自己定义一个字节流的缓冲区,通过这个字节流缓冲区我们才能真的明白它的缓冲意义在哪里。方法是首先定义一个用于缓冲的字节类型数组,每次都读取一个字节数组的数据,定义指正pos和计数器count,每次读取一个字节,然后指针++,计数器--。这样的意义在于流不用频繁的从目标文件中读取字节,依次就读取大量的字节存入数组(缓冲区)中。
package com.itheima;
import java.io.*;
class MyBufferedInputStream extends InputStream
{
InputStream is;
int pos=0;
int count=0;
byte[] arr=new byte[1024*4];
MyBufferedInputStream(InputStream is)
{
this.is=is;
}
public int read() throws IOException
{
if(count==0)
{
count=is.read(arr);//read()方法可能会返回-1
if(count<0)
return -1;
byte b =arr[pos];
pos++;
count--;
return b&255;//之所以&255这是因为返回的需要一个int而b是byte类型,自动提升会导致最后8位的改变,所以&000000ff可以转换成int类型而且可以保留最后8位。
}
if(count>0)
{
byte b =arr[pos];
pos++;
count--;
return b&255;
}
return -1;
}
}
public class MyBufferedInputStreamDemo
{
public static void main(String[] args) throws IOException
{
MyBufferedInputStream bis= new MyBufferedInputStream(new FileInputStream("C:\\Users\\彭攀\\Desktop\\简历.files\\ppphoto.jpg"));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("C:\\Users\\彭攀\\Desktop\\简历.files\\ppphoto(复件).jpg"));
int line=0;
//byte[] arr=new byte[bis.available()];//定义一个文件总容量的容器
while((line=bis.read())!=-1)
{
bos.write(line);//不需要刷新
}
/*while((line=bis.read(arr))!=-1)
{
bos.write(arr, 0, line);//不需要刷新!
}*/
bos.close();
bis.close();
}
}
八、转换流和键盘输入、控制台输出
如果在键盘上打印一串字母串并输出其大写字母串,其实就是利用输入流输入一行字符串的原理,我们希望利用readLine()方法,但是显而易见的System.in是一个字节流,而该方法是只有在字符流缓冲区中才能有的,所以我们希望将字节流转换成字符流的形式,再传入缓冲区中来使用读取键盘上一行数据的方法。
在这里我们就用上了InputStreamReader和OutputStreamWriter类,这两个类就是转换流
InputStreamReader这个流是Reader子类,它在实例化时传入一个字节输入流对象和指定的编码格式(若没有则是系统默认的编码格式),将文件按照字节流的形式读取,然后按照指定的编码方式再次编码,是字节流通向字符流的桥梁,它是一个字符流。
OutputStreamWriter这个流是Writer的子类,它在实例化过程中传入一个字节输出流对象和指定的解码格式(若没有则默认系统的编码格式),将文件按照字节流的形式写出,然后按照指定的解码方式再次解码,是字符流通向字节流的桥梁,它是一个字符流。
package com.itheima;
import java.io.*;
public class TransStreamDemo
{
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("over".equals(line))
break;
bw.write(line.toUpperCase());
bw.newLine();
bw.flush();//缓冲区字符流,必须刷新
}
br.close();
bw.close();
}
}
当流的对象很多个,不知道用哪一个的时候,要明确两个要素:
1、明确源和目的:源:输入流,目的:输出流
2、操作的数据是否为纯文本:是则用字符流,否则用字节流
3、要用到哪个设备?是硬盘,还是内存还是控制台还是键盘。
九、System类
package com.itheima;
import java.io.*;
public class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
System.setIn(new FileInputStream("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-11-30.txt"));
System.setOut(new PrintStream(new FileOutputStream("C:\\Users\\彭攀\\Desktop\\彭攀的Java学习日记\\2014-11-30(复件).txt")));//注意一定要是打印字节流
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("over".equals(line))
break;
bw.write(line.toUpperCase());
bw.newLine();
bw.flush();//缓冲区字符流,必须刷新
}
br.close();
bw.close();
}
}
在某一个程序发生异常时,在try{}catch{}处理块中可以定义一个流,将异常信息写入到异常日志,调用e.printStackTrace(字节流)方法。
调用System.getProperties()方法可以获取一个Properties对象,该对象代表系统日志,是一个Map集合,内部封装了一对对代表系统属性的键值对。
该对象调用getProperty(key)获取值,用setproperty(key,value)存入一组键值对,若初见值相同则老值被替换,用list(打印字节/字符流流)将该集合信息存入到一个文件中,用load(输入字节流)方法将配置文件中信息导入到Properties集合中,用store(输出流,配置字符串)将修改过的Properties集合输入到配置文件中,用StringProoertyNames()方法来获取键的Set集合