字符流的缓冲区
缓冲区的出现是为了提高流的操作效率而出现的。
所以在创建缓冲区之前,必须要先有流对象。
BufferedWriter
该缓冲区中提供了一个跨平台的换行符。newLine
//字符写入流缓冲区的基本方法演示
import java.io.*;
public class BufferedWriterDemo {
//为了演示方便,这里先不进行异常的处理
public static void main(String[] args) throws IOException
{
//先创建一个写入流对象
FileWriter fw = new FileWriter("bufw.txt");
//创建一个写入流缓冲区对象,只要将流对象传进来即可
BufferedWriter bufw = new BufferedWriter(fw);
//那么开始在缓冲区中写入数据:
for (int x = 0;x<5 ;x++ )
{
bufw.write("abcde"+x);
bufw.flush();//记住:只要用到缓冲区就记得刷新
bufw.newLine();//该方法是缓冲区中特有的换行的方法,适用于各种平台(系统)
}
bufw.close();//这里缓冲区对象其实引用的是流对象,所以这里只要关闭了缓冲区对象的资源就可以了
}
}
运行结果:
BufferedReader
该缓冲区提供了一个可以读取整行的方法readLine,方便与对文本数据的获取。
当返回 为空时,表示已经读到流的末尾。
返回的是回车符之前的数据,不包含回车符。
//读取流缓冲区的基本演示
import java.io.*;
public class BufferedReaderDemo {
//为了演示方便,先不进行异常的处理
public static void main(String[] args) throws IOException
{
//先创建一个读取流对象,和文件关联
FileReader fr = new FileReader("bufw.txt");
//创建一个字符串缓冲区对象,将流对象作为参数传递给缓冲区对象的构造函数
BufferedReader bufr = new BufferedReader(fr);
//开始读取文件中的数据
//缓冲区对象的readLine方法可以直接获取整行的数据,
//返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
//这就限定了可以用while循环的条件
String line=null;
while ((line = (bufr.readLine()))!=null)
{
System.out.println(line);
}
}
}
运行结果为:
练习1:用缓冲区对象拷贝一个文本文件。
//用缓冲区对象拷贝一个文本文件
import java.io.*;
public class CopyTextByBuf {
public static void main(String[] args)
{
//先创建一个流对象的引用
BufferedWriter bufw = null;
BufferedReader bufr = null;
try
{
bufw = new BufferedWriter(new FileWriter("textByCopy.txt"));
bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
//用readLine方法读取数据
String line = null;
while ((line = bufr.readLine())!=null)
{
bufw.write(line);
bufw.newLine();//readLine方法只获取数据,没有换行符,所以要自己添加换行符
bufw.flush();//每次存完记得刷新,以免出现意外情况倒置数据的丢失
}
}
catch (IOException e)
{
throw new RuntimeException("读取流对象创建失败");
}
//关闭资源
finally
{
if (bufw!=null)
{
try
{
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流对象关闭失败");
}
}
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流对象关闭失败");
}
}
}
}
}
运行结果为:
readLine方法的原理
无论是读一行,获取多个字符,其实最终都是在硬盘上一个一个的读取,所以最终使用的还是read方法一次读一个的方法。
所以根据readLine方法的原理,我们来写一个自己的BufferedReader,实现和BufferedReader同样的方法
//写一个自己的BufferedReader,要求和BufferedReader的方法功能一样
import java.io.*;
class MyBufferedReader
{
//因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来
private FileReader r;
MyBufferedReader(FileReader r)
{
this.r = r;
}
public String myReadLine() throws IOException
{
//因为BufferedReader底层用的是数组来存储读到的字符,当读到回车符的时候,将数组中的字符转成字符串返回去
//这里为了演示方便就用StringBuilder来代替,效果一样的,
//原理就是,read方法读一个字符,将该字符存储到StringBuilder中,当读到回车符的时候,就将StringBuilder中的字符串返回去。
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch = r.read())!=-1)
{
char c = (char)ch; //将返回的数字转换成字符
if(c=='\r') //判断是\r的话,继续循环
continue;
if(c=='\n') //判断是\n的话,遇到了回车符,该行结束,将sb中的字符串返回去
return sb.toString();
else //如果没有遇到回车符号,就把读到的字符存储到字符串缓冲区中,
sb.append(c);
}
if(sb.length()!=0)
return sb.toString(); //当某一行没有回车符的时候,这一行会读到,并且存进sb中,但是读不到回车符,
//不能将sb中剩下的东西返回去,所以这里要进行一次判断,
//如果read方法运行完,sb中还有东西,就返回来。
return null; //如果read方法返回-1,说明read方法读不到数据了,退出while循环,按照
//BufferedReader中的规则,这里应该返回null,
}
//同样的BufferedReader中还有关闭资源的方法
public void myClose() throws IOException
{
r.close();
}
}
public class MyBufferedReaderDemo {
//这里为了演示方便先不对异常进行处理
public static void main(String[] args) throws IOException
{
//创建流对象
FileReader fr = new FileReader("bufw.txt");
//创建缓冲区对象
MyBufferedReader mybuf = new MyBufferedReader(fr);
//开始读取数据
String line = null;
while ((line = mybuf.myReadLine())!=null)
{
System.out.println(line);
}
//关闭资源
mybuf.myClose();
}
}
运行结果为:
我们把这种像BufferedReader中readLine方法这样的基于read方法的加强功能的方法的设计模式称为装饰设计模式。
装饰设计模式:
当想要对已有的对象进行功能增强时,
可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能
那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰类的对象
并基于被装饰的对象的功能,提供更强的功能。
写一个装饰类的例子:
//装饰类的演示
//古时候的人们生活条件差,吃饭仅仅是吃饭而已
class Person
{
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("来一根");
}
}
public class SuperPersonDemo {
public static void main(String[] args)
{
Person p = new Person();
SuperPerson sp = new SuperPerson(p);
sp.superChifan();
}
}
运行结果:
在以上的示例中,SuperPerson类对Person类的chifan方法的功能增强就是装饰设计模式了。
可是在以上程序中,使用装饰设计模式实现的功能用子类继承父类,然后复写父类的方法也可以实现相同的功能的,那么,装饰设计模式和继承的区别在哪里呢?
举个例子来说
我有自己的读取的一个类MyReader
同时有具体到读取文件类型的类比如:MyTextReader,MyMediaReader
而为了提高读取文件的功能,提供了提高效率的类,MyBufferedTextReder,MyBufferedMediaReader
那么类与类继承的体系应该是这样的
- MyReader
- MyTextReader
- MyBufferedTextReader
- MyMediaReader
- MyBufferedMediaReader
- 如果来了新的读取文件类型,这里还要改代码,不提倡使用这种方法,
- 而且每一个类都有自己的增强类,每次都这样写很麻烦,而且不利于程序的扩展性。
- MyTextReader
于是,我们想到了定义一个类,直接将对象传进来,在类中定义增强读取的方法。
class MyBufferedReader
{
MyBufferedReader(MyTextReader mtr)
{
}
MyBufferedReader(MyMediaReader mdr)
{
}
.....
}
还是不爽,为什么呢?因为每有一个对象创建,都要有一个新的构造函数建立,如果来了新的数据类型,还是要修改代码,于是我们想到了多态,提高其扩展性,只要在构造函数中传入父类对象。
class MyBufferedReader
{
MyBufferedReader(MyReader mr)
{
}
}
这样,不用每一个数据类型,都写一个构造函数,而且新来的数据类型,只要是MyReader的子类就可以直接传进来对象使用增强方法。
于是体系结构就变成了这样子:
- MyReader
- MyTextReader
- MyMediaReader
- MyBufferedReader
装饰类避免了继承体系的臃肿模式,降低了类与类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所以装饰类和被装饰类通常都是 属于一个体系的。
于是我们就可以自己定义包装类了。
还是上边自定义readLine方法的例子
//自定义的装饰类,要求在Read的体系中
import java.io.*;
class MyBufferedReader extends Reader//在人家的体系中当然是先继承人家了
{
//因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来
private Reader r;
MyBufferedReader(Reader r)
{
this.r = r;
}
public String myReadLine() throws IOException
{
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch = r.read())!=-1)
{
char c = (char)ch;
if(c=='\r')
continue;
if(c=='\n')
return sb.toString();
else
sb.append(c);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
//因为Read类中有抽象方法read和close,所以在这里要进行覆盖
public void close() throws IOException
{
r.close();
}
public int read(char[] cbuf, int off, int len) throws IOException
{
return r.read(cbuf,off,len);
}
public void myClose() throws IOException
{
r.close();
}
}
public class MyBufferedReaderDemo {
public static void main(String[] args) throws IOException
{
//创建流对象
FileReader fr = new FileReader("bufw.txt");
//创建缓冲区对象
MyBufferedReader mybuf = new MyBufferedReader(fr);
//开始读取数据
String line = null;
while ((line = mybuf.myReadLine())!=null)
{
System.out.println(line);
}
//关闭资源
mybuf.myClose();
}
}
LineNumberReader
获取行号的缓冲区,继承自BufferedReader
那么就来演示一下
//获取行号的演示
import java.io.*;
public class LineNumberReaderDemo {
public static void main(String[] args)
{
FileReader fr =null;
try
{
fr = new FileReader("SuperPersonDemo.java");
//创建一个读取行号的对象,
LineNumberReader lnr = new LineNumberReader(fr);
//也有整行读取的方法
String line =null;
while ((line = lnr.readLine())!=null)
{
//先写行号再写内容
System.out.println(lnr.getLineNumber()+" "+line);
}
}
catch (IOException e)
{
throw new RuntimeException("读取失败");
}
//关闭资源
finally
{
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭资源失败");
}
}
}
}
运行结果:
练习:定义一个自己的获取行号的方法。
//写一个自己的获取行号的对象
import java.io.*;
class MyLineNumberReader
{
private FileReader fr;
MyLineNumberReader(FileReader fr)
{
this.fr = fr;
}
private int count = 0;//这里定义一个行号,起始值为0
public String myReadLine() throws IOException
{
StringBuilder sb = new StringBuilder();
int num = 0;
while ((num = fr.read())!=-1)
{
char ch = (char)num;
if(ch=='\r')
continue;
if(ch=='\n')
{
//当读到回车符的时候,count自增一次
count++;
return sb.toString();
}
else
sb.append(ch);
}
if((sb.length())!=0)
return sb.toString();
return null;
}
public void mySetLineNumber(int count)//设置count的值
{
this.count = count;
}
public int myGetLineNumber()//获取count值
{
return count;
}
}
public class MyLineNumberReaderDemo {
public static void main(String[] args)
{
FileReader fr =null;
try
{
fr = new FileReader("SuperPersonDemo.java");
//创建一个读取行号的对象,
MyLineNumberReader mylnr = new MyLineNumberReader(fr);
//也有整行读取的方法
String line =null;
while ((line = mylnr.myReadLine())!=null)
{
//先写行号再写内容
System.out.println(mylnr.myGetLineNumber()+" "+line);
}
}
catch (IOException e)
{
throw new RuntimeException("读取失败");
}
//关闭资源
finally
{
try
{
if(fr!=null)
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭资源失败");
}
}
}
}
运行结果:
发现,这里的代码和MyBufferedReader里边的代码只有几点不一样的地方,于是,我们可以让该获取行号的类继承MyBufferedReader类,将获取行号的类的代码进行优化。
class MyLineNumberReader extends MyBufferedReader
{
MyLineNumberReader(FileReader fr)
{
super(fr);
}
private int count = 0;//这里定义一个行号,起始值为0
public String myReadLine() throws IOException
{
count++;
return super.myReadLine();
}
public void mySetLineNumber(int count)//设置count的值
{
this.count = count;
}
public int myGetLineNumber()//获取count值
{
return count;
}
}
其主要原理也就是在某些地方调用了父类(MyBufferedReader)的方法
字节流
InputStream读取字节流
OutputStream写入字节流
API中发现,和字符流中的方法差不多。。
直接演示
//字节流写入方法演示
import java.io.*;
public class FileStream {
//为了演示方便,先不进行异常的处理
public static void main(String[] args) 2 IOException
{
writeFile();
}
public static void writeFile() throws IOException
{
//创建一个字节流对象,并且和文件关联
FileOutputStream fos = new FileOutputStream("fos.txt");
//字节流,当然是要写入字节了。
fos.write("abcdef".getBytes());
}
}
运行结果:
发现没有刷新动作,也没有关流的动作,但是文件确确实实写进去了。这是为什么呢?
这是因为,字符流底层用的也是字节的缓冲区,
因为编码表中一个字符对应的两个字节,所以在字符流中,当读取到一个字节的时候,会将字节都放在一个缓冲区中,
然后再去查表,找到字节相应的字符进行操作,
而在字节流中,底层数据类型是字节,不涉及到查表,所以可以实现读一个写一个的动作,
那么写完文件了,,我们试试读取文件
//字节流写入方法演示
import java.io.*;
public class FileStream {
//为了演示方便,先不进行异常的处理
public static void main(String[] args) throws IOException
{
readText_1();
}
//读取文件
public static void readText_1()throws IOException
{
//创建字节流读取对象,并与文件关关联
FileInputStream fis = new FileInputStream("fos.txt");
//read方法一样会返回int类型的值
int num = 0;
while((num = fis.read())!=-1)
{
System.out.println((char)num);
}
fis.close();
}
}
运行结果为:
但是这种方法一个字节一个字节的读取,效率超慢的,所以我们用数组缓冲区来提高效率。
//字节流写入方法演示
import java.io.*;
public class FileStream {
//为了演示方便,先不进行异常的处理
public static void main(String[] args) throws IOException
{
readText_2();
}
//读取文件
public static void readText_2()throws IOException
{
//创建字节流读取对象,并与文件关关联
FileInputStream fis = new FileInputStream("fos.txt");
int len = 0;
//和字符流一样的方法过程,只是注意数据类型变成了字节
byte[] buf = new byte[1024];
while ((len = fis.read(buf))!=-1)
{
System.out.println(new String(buf,0,len));
}
fis.close();
}
}
运行结果:
这种方法效率稍高,
发现InputStream类中有一个available方法,返回int类型的值,
是流对象获取到的所有的字节数(包括\r\n这样的字符)
这样的话,我们就可以直接定义数组缓冲区的大小了。就不用再while循环了。
//字节流写入方法演示
import java.io.*;
public class FileStream {
//为了演示方便,先不进行异常的处理
public static void main(String[] args) throws IOException
{
readText_3();
}
//读取文件
public static void readText_3()throws IOException
{
//创建字节流读取对象,并与文件关关联
FileInputStream fis = new FileInputStream("fos.txt");
//和字符流一样的方法过程,只是注意数据类型变成了字节
byte[] buf = new byte[fis.available()];//这里定义一个和文件字符数量一样大小的数组。
//将读取到数据存到数组中
fis.read(buf);
//将数组直接转换成字符串打印出来
System.out.println(new String(buf));
fis.close();
}
}
运行结果:
这种方法只适用于操作比较小的数据,因为jvm分配的内存一共64M,万一文件过大了,容易造成内存溢出。所以在操作比较大的数据的时候还是以创建一个1024字节的数组来循环比较靠谱。
练习:拷贝一个图片:
//用字节流复制一张图片
import java.io.*;
public class CopyPic {
public static void main(String[] args)
{
//在最外边创建两个引用
FileInputStream fis = null;
FileOutputStream fos = null;
try
{
//开始创建读取流和写入流,并和文件关联
fis = new FileInputStream("11.jpg");
fos = new FileOutputStream("22.jpg");
//开始读写操作
int len = 0;
byte[] buf = new byte[1024];
while ((len = fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("读取失败");
}
//关闭资源
finally
{
if (fis!=null)
{
try
{
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败");
}
}
if (fos!=null)
{
try
{
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流关闭失败");
}
}
}
}
}
运行结果:
练习二:拷贝一个MP3文件,用缓冲区
//拷贝MP3用流缓冲区
import java.io.*;
public class CopyMp3 {
public static void main(String[] args)
{
//在最外边建立一个引用
BufferedInputStream bufis = null;
BufferedOutputStream bufos = null;
try
{
//创建流对象
FileInputStream fis = new FileInputStream("1.mp3");
FileOutputStream fos = new FileOutputStream("2.mp3");
bufis = new BufferedInputStream(fis);
bufos = new BufferedOutputStream(fos);
//读取返回的字节
int by = 0;
while ((by = bufis.read())!=-1)
{
bufos.write(by);//写入字节
}
}
catch (IOException e)
{
throw new RuntimeException("读取失败");
}
//关闭资源
finally
{
if(bufis!=null)
{
try
{
bufis.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败");
}
}
if(bufos!=null)
{
try
{
bufos.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流关闭失败");
}
}
}
}
}
实验结果:
其实字节流缓冲区中也是有一个数组的,加上缓冲区是为了增强read方法功能。
缓冲区读取数据时过程是这样的。
调用FileInputStream的read方法,从硬盘上读取1024个字节的数据,放在定义的长度为1024的数组中
然后再用BufferedInputStream中的read方法从缓冲区中获取数组中的元素。然后写入到硬盘上来。
具体过程图例:
那么了解了字节流缓冲区的原理呢,我们就自己定义一个字节流缓冲区。
//子定义一个字节流缓冲区
import java.io.*;
class MyBufferedInputStream
{
private InputStream is;
//因为底层是用的数组,所以这里需要定义指针,和计数器
//思路是:
//先拿一堆数据过来,myRead方法通过指针获取数组中的值,然后count用来计算数组中的数据是否全部取出
//如果count为0了,再去取数据,如果不为0,再从缓冲区中取数据
private int pos = 0;
private int count = 0;
MyBufferedInputStream(InputStream is)
{
this.is = is;
}
//read方法一次只取一个字节
public int myRead() throws IOException
{
byte[] buf = new byte[1024];
//如果count为0,抓一把数据进来,读第一位数,返回去
if (count==0)
{
count = is.read(buf);//这个时候count的值就不是0了,如果读到末尾,发现count的值返回为-1 了,
pos = 0; //说明数据已经读取完毕了,直接返回-1,读到末尾
if (count==-1)
{
return -1;
}
byte b = buf[pos];
pos++;
count--;
return b & 255;//把数组中的元素返回去,注意:这里返回去的是byte类型的,但是上边函数上声明的是int类型的
//所以会进行一次自动提升类型操作
//read方法一次读取一个字节,如果MP3文件的前8位都是1呢?那么myRead方法返回的数直接就是-1了
//这样的话在下边调用myRead方法的时候,遇到这样的情况,就会造成一种数据已经读完的假象
//造成数据复制的损失,怎么解决呢?
//假如遇到了全是1的情况,b的值为-1,二进制类型为1111-1111
//这里return b会进行一次,提升,提升为int类型的,完了之后b的值还是-1,其二进制形式为
//11111111-11111111-11111111-11111111
//但是我们可以在int类型的前24位补0,只留下最后的8位,这样就可以解决这种情况了
//只获取最后的八位,只要用这个数和255进行与运算就可以了。原理是:
// 11111111-11111111-11111111-11111111 -1
//&00000000-00000000-00000000-11111111 255
//=00000000-00000000-00000000-11111111 255
//所以这里进行的一次之获取后八位的运算方法就是&255
}
else if(count>0)//第二次进来的时候count就不为0了,所以不用抓数据,直接取就可以了
{
byte b = buf[pos];
pos++;
count--;
return b &255;//把数组中的元素返回去,
}
return -1;
}
public void myClose()throws IOException
{
is.close();
}
}
class MyBufferedInputStreamDemo
{
public static void main(String[] args)
{
//在最外边建立一个引用
MyBufferedInputStream bufis = null;
BufferedOutputStream bufos = null;
try
{
//创建流对象
FileInputStream fis = new FileInputStream("1.mp3");
FileOutputStream fos = new FileOutputStream("2.mp3");
bufis = new MyBufferedInputStream(fis);
bufos = new BufferedOutputStream(fos);
//读取返回的字节
int by = 0;
while ((by = bufis.myRead())!=-1)
{
bufos.write(by);//写入字节
}
}
catch (IOException e)
{
throw new RuntimeException("读取失败");
}
//关闭资源
finally
{
if(bufis!=null)
{
try
{
bufis.myClose();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败");
}
}
if(bufos!=null)
{
try
{
bufos.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流关闭失败");
}
}
}
}
}
运行结果:
获取键盘录入
读取键盘上录入的数据实际上也是读取流在操作:
简单演示一下获取键盘录入的例子
//读取键盘录入
import java.io.*;
public class SystemInDemo {
public static void main(String[] args) throws IOException
{
//获取一个读取流对象,与键盘录入相关联
InputStream is = System.in;
int ch = 0;
while((ch = is.read())!=-1)
{
System.out.println(ch);//打印所有读到的内容
}
}
}
运行结果为:
发现,我只输入了一个a,却打印了三个值,
这是因为我还敲了一次回车,虚拟机把回车键的ASCII值也打印出来了。
那么需求变了,我要让键盘上敲的字母打印出来全部大写的,而且一直敲到over,程序就结束。
//读取键盘录入
import java.io.*;
public class SystemInDemo {
public static void main(String[] args)
{
//获取一个读取流对象,与键盘录入相关联
InputStream is = null;
try
{
is = System.in;
int num = 0;
//穿件一个字符串缓冲区,将读取到的字符先存到缓冲区中,
StringBuilder sb = new StringBuilder();
while((num = is.read())!=-1)
{
char ch = (char)num;
if(ch=='\r')
continue;
if(ch=='\n')
{
//碰到回车符,先进行判断是否为over,如果不是的话打印字符串,如果是的话,跳出循环
String s = sb.toString();
if(s.equals("over"))
break;
System.out.println(s.toUpperCase());
//每打印完一次,sb就要清空一次,要不然前边的东西会一直占着
sb.delete(0,sb.length());
}
else
sb.append(ch);
}
}
catch (IOException e)
{
throw new RuntimeException("流创建失败");
}
}
}
运行结果:
转换流对象
发现上边的有一部分代码和在自定义readLine方法的时候写的代码有点相似。
那么就想到,用键盘录入的时候,能不能直接用readLine方法,直接读取一整行呢?
可是,readLine方法是在BufferedReader中的,属于字符流对象的,
而键盘录入对象是属于字节流的,在InputStream中的。
InputStreamReader:将字节流转换为字符流对象,可以使用readLine方法。
代码实例:
//转换流对象演示
import java.io.*;
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);
String line = null;
while ((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
}
运行结果为:
OutputStreamWriter
将字符流转换为字节流
比如,在上边的例子中,键盘录入的是字符,但是存在硬盘的时候用的就是字节,所以需要将字符流转换为字节流。
示例:
//转换流对象演示
import java.io.*;
public class TransStreamDemo {
//为了演示方便,先不进行异常的处理
public static void main(String[] args) throws IOException
{
//获取一个键盘录入对象
InputStream in = System.in;
//将字节流装换为字符流对象
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法
BufferedReader bufr = new BufferedReader(isr);
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法
String line = null;
while ((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加
bufw.flush();//每次写完要记得刷新一下,要不然出不来数据
}
bufr.close();
bufw.close();
}
}
运行结果
流操作的基本规律
通过两个明确来完成
- 明确源和目的
- 源:输入流。InputStream ;Reader
- 目的:输出流:OutputStream Writer
- 操作的数据是否是存文本
- 是:字符流
- 不是:字节流
- 当体系明确后,再明确要使用哪个具体的对象
- 通过设备来进行区分
- 源设备:内存,硬盘,键盘
- 目的设备:内存,硬盘,控制台。
- 通过设备来进行区分
练习:将一个图片文件存储到另一个文件中,复制文件,要求按照以上格式完成三个明确
//复制图片,要求完成三个明确
import java.io.*;
public class CopyPicTest {
public static void main(String[] args)
{
/*
源:输入流:Reader,InputStream
目的:输出流:Writer,OutputStream
操作的数据是否是纯文本?
不是:InputStream,和OutputStream
设备:
源:硬盘中的文件 FileInputStream
母的,硬盘中的文件 FileOutputStream
需要提高效率吗?是的,BufferedInputStream,BufferedOutputStream
*/
//开始按照分析思路创建对象
BufferedInputStream bufis =null;
BufferedOutputStream bufos =null;
try
{
//开始读写操作
bufis = new BufferedInputStream(new FileInputStream("11.jpg"));
bufos = new BufferedOutputStream(new FileOutputStream("22.jpg"));
int by = 0;
while ((by = bufis.read())!=-1)
{
bufos.write(by);
}
}
catch (IOException e)
{
throw new RuntimeException("读取写入失败");
}
//关闭资源
finally
{
if(bufos!=null)
{
try
{
bufos.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流关闭失败");
}
}
if(bufis!=null)
{
try
{
bufis.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流关闭失败");
}
}
}
}
}
练习二:将键盘录入的数据保存到一个文件中
//将键盘录入的数据保存到一个文件中
/*
源:InputStream Reader
是不是纯文本?是!Reader
设备:键盘。对应的对象是System.in
不是选择Reader吗?System.in对应的不是字节流吗?
为了操作键盘的文字数据方便,转换成字符流按照字符串操作是最方便的。
所以既然明确了Reader那么就将System.in转换为Reader
用了Reader中的转换流,InputStreamReader
需要提高效率吗?需要,BufferedReader
目的:OutputStream Writer
是否是存文本。是!Writer
设备:硬盘,一个文件,使用FileWriter
需要提高效率吗?需要
BufferedWriter bufw = new BufferedWriter()
*/
import java.io.*;
public class Test2 {
public static void main(String[] args)
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
bufr = new BufferedReader(new InputStreamReader(System.in));
bufw = new BufferedWriter(new FileWriter("systemin.txt"));
String line = null;
while ((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("读写发生异常啦");
}
finally
{
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流发生异常");
}
}
if (bufw!=null)
{
try
{
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流发生异常");
}
}
}
}
}
扩展一下:
想要把录入的数据按照指定的编码表(UTF-8),将数据存到文件中。
目的:OutputStream,Writer
是否是存文本?是,Writer
设备,硬盘,一个文件,使用FileWriter
但是FileWriter是使用默认的编码表GBK
但是存储时,需要加入指定 编码表utf-8,而指定的编码表只有转换流可以指定
所以要使用的对象是OutputStreamWriter
而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“d.txt”),“utf-8”);
需要高效吗?需要
BufferedWriter bufw = nwe BufferedWriter(osw);
所以记住,转换流什么时候使用,字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流
指定编码表的键盘录入写入文件的程序:
//将键盘录入的数据保存到一个文件中,指定utf-8码表
import java.io.*;
public class Test2 {
public static void main(String[] args)
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
bufr = new BufferedReader(new InputStreamReader(System.in));
//指定编码表,只能用转换流指定。
bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d2.txt"),"utf-8"));
String line = null;
while ((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("读写发生异常啦");
}
finally
{
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取流发生异常");
}
}
if (bufw!=null)
{
try
{
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入流发生异常");
}
}
}
}
}
运行结果,指定GBK和utf-8的写入同样的字,你好,两个文件的占内存大小不一样。
改变标准输入输出设备
System.setIn(InputStream is)//将标准输入流从键盘录入改为一个字节读取流
System.setOut(PrintStream ps);//将标准的输出流从控制台改为一个打印流(参数为一个文件名称)
演示一下:
//转换流对象演示
import java.io.*;
public class TransStreamDemo2 {
//为了演示方便,先不进行异常的处理
public static void main(String[] args) throws IOException
{
//把默认的输入流改变为文件读取流
System.setIn(new FileInputStream("CopyPic.java"));
//获取一个标准录入对象
InputStream in = System.in;
//将字节流装换为字符流对象
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法
BufferedReader bufr = new BufferedReader(isr);
//改变标准输出流改变为一个文件
System.setOut(new PrintStream("zz.txt"));
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法
String line = null;
while ((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加
bufw.flush();//每次写完要记得刷新一下,要不然出不来数据
}
bufr.close();
bufw.close();
}
}
运行结果为:
将异常的信息打印到异常日志文件中,带上时间。
//将异常信息导入到异常日志文件中,要带上时间
import java.io.*;
import java.util.*;
import java.text.*;
public class ExceptionLogDemo {
public static void main(String[] args)
{
//发现异常
try
{
int[] arr = new int[2];
System.out.println(arr[4]);//创建一个数据角标越界的异常
}
//进行处理
catch (Exception e)
{
//加上时间,获取能够看得懂的时间信息
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 kk:mm:ss");
String time = sdf.format(d);
//将标准输出流换成文件打印流,并且与日志文件相关联
try
{
PrintStream ps = new PrintStream("exception.log");
System.setOut(ps);
}
catch (IOException ex)
{
throw new RuntimeException("异常日志文件创建失败");
}
System.out.println(time);
e.printStackTrace(System.out);//将异常打印在指定的输出流中
}
}
}
运行结果:
想要将系统信息,输出到指定的文件夹
发现系统信息对象Properties有自己的指定输出流的方法list
于是,我们就可以直接进行调用:
//将系统信息输出到指定的文件中
import java.io.*;
import java.util.*;
public class SystemInfo {
public static void main(String[] args)
{
//获取系统信息
Properties prop = System.getProperties();
//Properties中的list方法,可以将系统信息输出到指定的输出流
try
{
//输出流和文件相关联
prop.list(new PrintStream("systeminfo.txt"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
运行结果: