一、概述
把不同类型的输入、输出都抽象为流(Stream)。按流的方向,可分为输入流与输出流。从JDK1.4起,加了 java.nio 包, JDK1.7 作了改进,称nio2
字节流的抽象基类:InputStream,OutputStream。
字符流的抽象基类:Reader,Writer。
由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如:InputStream的子类FileInputStream。Reader的子类FileReader。
如果要操作文字数据,优先考虑字符流。
二、字符流
字符流集成体系简图
Reader类
Reader的重要方法是read()
public int read(); //需要将int转成char
ppublic int read(char b[]);
ppublic int read(char[] b, int off, int len);
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) throws IOException{
FileReader fr = new FileReader("demo.txt");
//方法一:用reader()方法读取字符
int ch = 0;
while((ch=fr.read()) != -1){
System.out.println((char)ch);
}
fr.close();
//方法二:使用read(char[])读取文本文件数据
//先创建字符数组
char[] buf = new char[3];
int len = 0;
while((len = fr.read(buf)) != -1){
System.out.println(new String(buf, 0, len));
}
fr.close();
}
}
Writer类
Writer的方法有:
public void write (int b);// 将参数b的低两字节写入到输出流
public void write (char b[]);// 将字符数组b[]中的全部字节顺序写入到输出流
public void write(char[] b, int off, int len);// 将字节数组b[]中从off开始的len个字节写入到流中
public void write( String s);// 将字符串写入流中
public void write( String s, int off, int len);// 将字符串写入流中, off为位置,len为长度
public void flush ();// 刷新流
public void close();// 关闭流
IO流的异常处理方式:为防止代码异常导致流无法关闭,因此在finally中对流进行关闭。
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) throws IOException{
//创建一个可以往文件中写入字符数据的字符输出流对象
FileWriter fw = null;
try{
fw = new FileWriter("demo.txt");
//调用writer对象中的write(string)方法,将数据写入到临时存储缓冲区中
fw.write("abcde");
//刷新,将数据直接写入到目的地中
fw.flush();
}catch(IOException e){
System.out.println(e.toString());
}finally{
try{
//关闭流,关闭资源,在关闭前会先调用flush刷新缓冲中的数据到目的地
fw.close();
}catch(IOException e){
throw new RuntimeException("Failed close");
}
}
}
}
复制文本文件的两种方式:
- 使用read()读取文本文件数据。
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTextTest {
public static void main(String[] args) throws IOException{
//读取一个已有的文本文件,使用字符读取流和文件相关联
FileReader fr = new FileReader("demo.txt");
//创建一个目的,用于存储读到数据
FileWriter fw = new FileWriter("copyText_1.txt");
//频繁的读写操作
int ch = 0;
while((ch = fr.read())!=-1){
fw.write(ch);
}
//关闭字符流
fw.close();
fr.close();
}
}
- 使用read(char[])读取文本文件数据。
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTextTest2 {
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args){
FileReader fr = null;
FileWriter fw = null;
try{
fr = new FileReader("demo.txt");
fw = new FileWriter("copytest_2.txt");
//创建一个临时容器,用于缓存读取到的字符
char[] buf = new char[BUFFER_SIZE];
//定义一个变量记录读取到的字符数(其实就是往数组里装的字符个数)
int len = 0;
while((len = fr.read(buf))!=-1){
fw.write(buf,0,len);
}
}catch(Exception e){
throw new RuntimeException("读写失败");
}finally{
if(fw != null){
try{
fw.close();
}catch(IOException e){
System.out.println(e.toString());
}
}
if(fr != null){
try{
fw.close();
}catch(IOException e){
System.out.println(e.toString());
}
}
}
}
}
字符流的缓冲区
缓冲区的出现提高了对数据的读写效率。
作用:在流的基础上对流的功能进行了增强。
BufferedWriter类,BufferedReader类
/*
BufferWriter示例
*/
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException{
FileWriter fw = new FileWriter("buf.txt");
//为了提高写入的效率,使用了字符流的缓冲区
//创建了一个字符写入流的缓冲区对象,并且指定与要被缓冲的流对象相关联
BufferedWriter bufw = new BufferedWriter(fw);
for(int x = 1; x <= 4; x++){
//使用缓冲区的写入方法将数据先写入到缓冲区中
bufw.write("abcdef"+x);
//写入内容换行方法:newLine();
bufw.newLine();
bufw.flush();
}
//使用缓冲区的刷新方法将数据刷目的地中
bufw.flush();
//关闭缓冲区,其实关闭的就是被缓冲的流对象
fw.close();
}
}
/*
BufferReader示例
*/
import java.io.BufferedReader;
import java.io.FileReader;
public class BufferedReaderDemo {
public static void main(String[] args) throws Exception{
FileReader fr = new FileReader("buf.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
//读取一行数据使用BufferedReader类中的readLine()方法
while((line = bufr.readLine()) != null){
System.out.println(line);
}
bufr.close();
}
}
/*
使用缓冲区复制文件
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class CopyTextBufTest {
public static void main(String[] args) throws Exception{
FileReader fr = new FileReader("buf.txt");
BufferedReader bufr = new BufferedReader(fr);
FileWriter fw = new FileWriter("buf_copy.txt");
BufferedWriter bufw = new BufferedWriter(fw);
//方式一
String line = null;
while((line = bufr.readLine()) != null){
bufw.write(line);
bufw.newLine();
bufw.flush();
}
/*方式二
int ch = 0;
while((ch = bufr.read()) != -1){
bufw.write(ch);
}
*/
bufr.close();
bufw.close();
}
}
LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
public class LineNumberReaderDemo {
public static void main(String[] args) throws IOException{
FileReader fr = new FileReader("LineNumberReaderDemo.java");
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;
lnr.setLineNumber(100);
while((line = lnr.readLine()) != null){
System.out.println(lnr.getLineNumber()+" : "+line);
}
lnr.close();
}
}
装饰设计模式
对原有类进行了功能的改变,增强。
class Person{
void eat(){
System.out.println("hava a meal");
}
}
//采用装饰的方式增强Person类
class NewPerson{
private Person p;
NewPerson(Person p){
this.p = p;
}
public void eat(){
System.out.println("wine");
p.eat();
System.out.println("dessert");
}
}
//采用继承的方式增强Person类
class NewPerson2 extends Person{
public void eat(){
System.out.println("wine");
super.eat();
System.out.println("dessert");
}
}
public class PersonDemo {
public static void main(String[] args){
Person p = new Person();
NewPerson np1 = new NewPerson(p);
np1.eat();
System.out.println("------------------");
NewPerson2 np2 = new NewPerson2();
np2.eat();
}
}
装饰和继承都能实现一样的特点:进行功能的扩展增强。但装饰比继承灵活.它将缓冲进行单独的封装,哪个对象需要缓冲就将哪个对象和缓冲关联。装饰类和被装饰类都必须所属同一个接口或者父类。
/*
* 定义一个读取缓冲区类,模拟一个BufferedReader
*/
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
class MyBufferedReader{
private Reader r;
//定义一个数组作为缓冲区
private char[] buf = new char[1024];
//定义一个指针用于操作这个数组中的元素,当操作到最后一个元素后,指针应该归0
private int pos = 0;
//定义一个计数器用于记录缓冲区中的数据个数,当该数据减到0,就从源中继续获取数据到缓冲区中
private int count = 0;
MyBufferedReader(Reader r){
this.r = r;
}
//该方法从缓冲区中一次取一个字符
public int myRead() throws IOException{
if (count == 0){
count = r.read(buf);
//每次获取数据到缓冲区后,角标归0
pos = 0;
}
if (count < 0)
return -1;
char ch = buf[pos];
pos ++;
count --;
return ch;
}
public String myReadLine() throws IOException{
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = myRead()) != -1){
if (ch == '\r')
continue;
if (ch == '\n')
return sb.toString();
//将从缓冲区读到的字符,存储到缓冲行数据的缓冲区中
sb.append((char)ch);
}
if (sb.length() != 0){
return sb.toString();
}
return null;
}
public void myClose() throws IOException{
r.close();
}
}
public class MyBufferedReaderDemo {
public static void main(String[] args) throws IOException{
FileReader fr = new FileReader("buf.txt");
MyBufferedReader bufr = new MyBufferedReader(fr);
String line = null;
while((line = bufr.myReadLine()) != null){
System.out.println(line);
}
bufr.myClose();
}
}
三、字节流
字节流集成体系简图
字节流不仅可以操作字符,还可以操作其他媒体文件
InputStream类
read()方法:逐字节地以二进制的原始方式读取数据
public int read(); 读入一个字节,-1表示无
public int read(byte b[]); 返回读入的字节数
public int read(byte[] b, int off, int len);
OutputStream类
write()方法
它的功能是将字节写入流中
public void write (int b);// 将参数b的低位字节写入到输出流
public void write (byte b[]);// 将字节数组b[]中的全部字节顺序写入到输出流
public void write(byte[] b, int off, int len);// 将字节数组b[]中从off开始的len个字节写入到流中
public void flush (); 刷新缓存,实际写入到文件、网络
public void close(); 关闭流
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo {
public static void main(String[] args) throws IOException{
demo_write();
demo_read1();
System.out.println("-----------------");
demo_read2();
System.out.println("-----------------");
demo_read3();
}
public static void demo_write() throws IOException{
//创建字节输出流对象,用于操作文件
FileOutputStream fos = new FileOutputStream("bytedemo.txt");
//写数据,直接写入到了目的地中
fos.write("abcdefg".getBytes());
//关闭资源
fos.close();
}
//读取方式一
public static void demo_read1() throws IOException{
//创建一个读取流对象,和指定文件关联
FileInputStream fis = new FileInputStream("bytedemo.txt");
//打印字符字节大小,不过要少用,文件太大会溢出
byte[] buf = new byte[fis.available()];
fis.read(buf);
System.out.println(new String(buf));
fis.close();
}
//读取方式二 —— 推荐使用
public static void demo_read2() throws IOException{
FileInputStream fis = new FileInputStream("bytedemo.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1){
System.out.println(new String(buf,0,len));
}
fis.close();
}
//读取方式三
public static void demo_read3() throws IOException{
FileInputStream fis = new FileInputStream("bytedemo.txt");
//一次读取一个字节
int ch = 0;
while((ch = fis.read()) != -1){
System.out.print((char)ch);
}
fis.close();
}
}
FileOutputStream、FileInputStream的flush方法内容为空,没有任何实现,调用没有意义。
字节流的缓冲区:提高了字节流的读写效率。
/*
* 通过两种方式对MP3进行拷贝,比较它们的效率
*/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyMp3Test {
public static void main(String[] args) throws IOException{
copy_1();
copy_2();
}
//使用字节流原始方式
public static void copy_1() throws IOException{
FileInputStream fis = new FileInputStream("0.mp3");
FileOutputStream fos = new FileOutputStream("1.mp3");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1){
fos.write(buf,0,len);
}
fis.close();
fos.close();
}
//使用字节流缓冲区
public static void copy_2() throws IOException{
FileInputStream fis = new FileInputStream("0.mp3");
BufferedInputStream bufis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("1.mp3");
BufferedOutputStream bufos = new BufferedOutputStream(fos);
int ch = 0;
while((ch = bufis.read()) != -1){
bufos.write(ch);
}
bufis.close();
bufos.close();
}
}
键盘本身就是一个标准的输入设备。对于java而言,对于这种输入设备都有对应的对象。
1、获取键盘录入数据,然后将数据流向显示器,那么显示器就是目的地。
通过System类的setIn,setOut方法可以对默认设备进行改变。
System.setIn(new FileInputStream(“1.txt”));//将源改成文件1.txt。
System.setOut(new PrintStream(“2.txt”));//将目的改成文件2.txt
因为字节流处理的是文本数据,可以转换成字符流,操作更方便。
BfferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
2、默认的输入和输出系统不需要关,它会随着系统的结束而消失。
/*
* 实例:获取用户键盘录入的数据并将数据变成大写显示在控制台上,如果用户输入的是over,结束键盘录入。
*/
import java.io.IOException;
import java.io.InputStream;
public class ReadKey {
public static void main(String[] args) throws IOException{
readKey();
}
public static void readKey() throws IOException{
//创建容器
StringBuilder sb = new StringBuilder();
//获取键盘读取流
InputStream in = System.in;
//定义变量记录读取到的字节,并循环获取
int ch = 0;
while ((ch = in.read()) != -1){
//在存储之前需要判断是否是换行标记
if (ch == '\r')
continue;
if (ch == '\n'){
String temp = sb.toString();
if ("over".equals(temp))
break;
System.out.println(temp);
sb.delete(0,sb.length());
}else{
//将读取到的字节存储到StringBuilder中
sb.append((char)ch);
}
}
}
}
四、转换流
转换流——字符流与字节流之间的桥梁。
应用:字节流中的数据都是字符时,转成字符流操作更高效。
InputStreamReader:字节到字符的桥梁,解码。
OutputStreamWriter:字符到字节的桥梁,编码。
使用场景:
1)源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁,提高对文本操作的便捷。
2)操作文本涉及到具体的指定编码表时,必须使用转换流。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
//import java.io.InputStream;
import java.io.InputStreamReader;
//import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class TransStreamDemo {
public static void main(String[] args) throws IOException{
/*字节流
InputStream in = System.in;
//将字节转成字符
InputStreamReader isr = new InputStreamReader(in);
//对字符流进行高效修饰,缓冲区
BufferedReader bufr = new BufferedReader(isr);
*/
//上面三行代码可以简化为:
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
/*字节流
OutputStream out = System.out;
//将字符转成字节
OutputStreamWriter osw = new OutputStreamWriter(out);
//对字节流进行高效修饰,缓冲区
BufferedWriter bufw = new BufferedWriter(osw);
*/
//上面三行代码可以简化为:
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
//读取到了字符串数据
while ((line = bufr.readLine()) != null){
if ("over".equals(line))
break;
//将字符数据用缓冲区对象写入缓冲区,目的地是osw
bufw.write(line.toUpperCase());
bufw.newLine();
//osw.write(line.toUpperCase() + "\r\n");可以替代上面两行代码
bufw.flush();
}
}
}
流的操作规律:
想要知道对象的开发时用到哪些对象,要明确以下四点:
1)源和目的
源:InputStream,Reader 目的:OutputStream,Writer
2)数据是否是纯文本数据
是纯文本:Reader,Writer 不是纯文本:InputStream,OutputStream
3)设备
硬盘:File 键盘:System.in 控制台:System.out 内存:数组 网络:Socket流
4)是否需要高效(转换流,缓冲区)
/*
* 将一个中文字符串数据按照指定的编码表写入到一个文本文件中。
* 目的:OutputStream,Writer
* 是纯文本:Writer
* 设备:硬盘File
* 任何Java识别的字符数据使用的都是Unicode码表,但是FileWriter写入本地文件使用的是本地编码, 也就是GBK码表。
* 而OutputStreamWriter可使用指定的编码将要写入流中的字符编码成字节。
*/
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class StreamDemo1 {
public static void main(String[] args) throws IOException{
writeText();
}
public static void writeText() throws IOException{
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"GBK");
//UTF-8编码,一个中文三个字节。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
osw.write("您好");
osw.close();
//输出文本文件内容
InputStreamReader isr = new InputStreamReader(new FileInputStream("d.txt"),"UTF-8");
char[] buf = new char[10];
int len = isr.read(buf);
String str = new String(buf,0,len);
System.out.println(str);
isr.close();
}
}