黑马程序员技术交流社区
标题:
“第六贴”之字符流、转换流、异常信息保存
[打印本页]
作者:
曹睿翔
时间:
2013-5-14 21:46
标题:
“第六贴”之字符流、转换流、异常信息保存
本帖最后由 曹睿翔 于 2013-5-14 21:50 编辑
Writer
体系结构:
|-----------Writer
|----OutputStreamWriter:字符流通向字节流的桥梁,可以指定编码。
|--FileWriter:文件写入流,会创建一个文件,通过构造函数定义是续写还是覆盖。
|----BufferedWriter:字符写入流,定义了缓冲区,可以提高效率;
缓冲区为字符数组,可定义缓冲区大小,默认缓冲区大小为8k。
示例代码:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FIleReadDemo1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
File file = new File("d:"+File.separator+"java.txt");
if(!file.exists()){//判断文件是否存在,不存在则创建。
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
FileWriter fw = null;//目的是为了提升其作用域,因为流必须关掉以释放资源。否则finally读不到fw
try {
fw = new FileWriter(file,true);//文件续写:传递一个true参数,代表不覆盖一个已有的文件,并在已有文件的末尾处进行数据的续写
fw= new FileWriter("demo.txt");
/* 这句和注释外那句的区别:
创建一个FileWriter对象,该对象已被初始化就必须要明确要被操作的文件。
而且该文件会被创建到指定目录下。如果该目录下已有同名文件,则同名文件将被覆盖,。 其实该步就是在明确数据要存放的目的地。 */
//fw.write("回顾一下java的学习,稳扎稳打吧、\r\n一定要给力点\");
fw.write("再写一次");
/*
write可以写入字符、字符数组(或字符数组的一部分)、字符串(或字符串的一部分)。
注意:windows中,换行用"\r\n"两个字符表示,而Lunix中用"\n"一个字符表示。
所以,若只用“\n”,使用windows自带的记事本不能看到换行效果。
*/
fw.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
if(fw!=null){
try {
//这里必须判断一下,因为如果fw的初始化没有成功,会抛出空指针异常。这里的fw操作是针对fw.write()方法抛出异常后进行的处理。
fw.close();
/*IO底层调用了系统的资源,IO流建立成功使用结束时,一定要释放资源。fw.close()之前,会先调用flush方法,
这两个方法的区别是::flush刷新后,流可以继续使用;close刷新后,会将流关闭,若再次使用流会抛出异常。
注意:如果没有调用flush也没用调用close方法,那么磁盘文件中看不到写入效果。 */
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
复制代码
Reader
|-----------Reader
|----InputStreamReader:字节流通向字符流的桥梁,可以指定编码。
|--FileReader:文件读取流,文件需存在,否则抛出异常。
|----BufferedReader:字符读取流,定义了缓冲区,可以提高效率;缓冲区为字符数组,可定义缓冲区大小,默认缓冲区大小为8k。
Reader
文件读取
第一种方式
(一次读一个字节读
:
read()方法,一个字符一个字符的读取,返回值是字符的编码值,如果到流结束处,则返回-1,它能读取到换行符。
第二种方式
(一次读一个字符数组)
:
read(char[]ch),把读取的字符放入字符数组中,返回的读取的字符个数,如果到流结束处,则返回-1;它能读取到换行符。
readLine方法原理
:
读一行,获取读取的多个字符,最终都是在硬盘上一个一个读取,所以最终使用的还是read()方法一次读取一个的方法(因为只有一个一个读取,它才能筛选判断出换行符,判断一行是否到结尾)。只不过它不是读一个就写一个,而是读一行就放入缓冲区,读完一行再,再刷新缓冲区,把缓冲区的数据写入硬盘文件中。
readLine返回的是字符串,如果读到末尾,返回值为null;这是与read不同的地方。
自定义读取流缓冲区与装饰设计模式
自定义缓冲区
,模拟BufferedReader
,而BufferedReader用到了
装饰
设计模式,所以要先明白装饰设计模式。
代码实现:
PS:借用毕老师的代码,另外也见到过,学生在
MyBufferedReader
中私有一个
FileReader然后传给构造器,而不用继承Reader这个抽象类也就不用复写他的抽象方法了,好方法
import java.io.*;
class MyBufferedReader extends Reader
{
private Reader r;
MyBufferedReader(Reader r)
{
this.r = r;
}
//可以一次读一行数据的方法。
public String myReadLine()throws IOException
{
//定义一个临时容器。原BufferReader封装的是字符数组。
//为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=r.read())!=-1)
{
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
/*
覆盖Reader类中的抽象方法。
*/
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 void myClose()throws IOException
{
r.close();
}
}
class MyBufferedReaderDemo
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("abc.txt");
MyBufferedReader myBuf = new MyBufferedReader(fr);
String line = null;
while((line=myBuf.myReadLine())!=null)
{
System.out.println(line);
}
myBuf.myClose();
}
}
复制代码
作者:
曹睿翔
时间:
2013-5-14 21:46
本帖最后由 曹睿翔 于 2013-5-14 21:46 编辑
读取键盘录入
/*
读取键盘录入。
System.out:对应的是标准输出设备,控制台。
System.in:对应的是标准的输入设备,键盘。
通过键盘录入数据。
当录入一行数据后,就将改行数据打印。
如果录入的数据是over,那么停止录入。
*/
import java.io.InputStream;
public class SystemIn {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
// 新建一个键盘输入流
InputStream in = System.in;
//创建一个缓冲区,将数据存入缓冲区中
StringBuilder sb = new StringBuilder();
int ch = 0;
while(true){
ch = in.read();
/*
read()是一个阻塞式的方法,它每次读取一个字节,如果没有数据,它会一直等待。
1. 为什么它读硬盘上的文件文件时,没有阻塞?
因为硬盘上的文件就算是空,为0字节,因为文件有结束标记(windows系统给每个磁盘文件都加有结束标记),会返回-1,而不阻塞。 而在dos命令行窗口下,启动后,既没有数据,也没有结束标记,所以一直阻塞,而“ctrl+c”命令,其实就是输入一个结束标记。
2. 为什么dos命令窗口下,只用我敲回车键后,控制台才打印,而不是我录入一个字符它就打印?
因为,录入的字符,只有在敲回车键后才写入in关联的流里边,这时流里边才有数据,从而被read()方法读取到。
3.为什么我敲入一个字符回车后,却打印出来3个数字?
因为,windows中,敲回车键,就是加入换行符,而windows中的换行符是用\r\n两个字符表示的,所以会有3个。
*/
if(ch == '\r')
continue;
if(ch == '\n'){
String s = sb.toString();
if("over".equals(s)){
break;
}
//大写输出
System.out.println(s.toUpperCase());
//清空缓冲区,否则,结束标记失效、之前打印的将载累加
sb.delete(0, sb.length());
}else{//如果没有遇到回车符,就继续往缓冲中添加
sb.append((char)ch);
}
}
}
}
复制代码
转换流
上边读取键盘录入的代码,与BufferedReader的readLine()方法很相似,那么能不能用readLine()来代替?因为System.in是字节流,它要使用字符流缓冲区的方法,这就需要使用到两个转换流:
InputStreamReader
和
OutputStreamWriter。
在接下来会讲到指定编码表的问题。
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//获取键盘录入对象。
//InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流。InputStreamReader
//InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
//BufferedReader bufr = new BufferedReader(isr);
//键盘的最常见写法。
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw =
new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}
复制代码
流操作的基本规律
IO操作最头痛的是如何其体系中选择需要的类,这个过程可以通过三个步骤来完成:
1.明确源和目的。
源:输入流,InputStream和Reader;
目的:输出流,OutputStream和Writer
2.明确操作的数据是否是纯文本。
是:字符流
不是:字节流。
3.当体系明确后,再明确要使用哪个具体的对象。
通过设备来区分:
源设备:内存、硬盘、键盘
目的设备:内存、硬盘、控制台。
此外:
如果需要提高效率,则换用对应的缓冲区。
如果需要指定编码,则换用对应的转换流。
小知识点:
System类中的setIn()、setOut()、setError()方法,可以改变标准输入流、输出流和错误输出流。
异常信息
的保存
class ExceptionInfo {
Public static void main(String[] args) throws IOException {
try {
int [] arr = newint[2];
System.out.println(arr[3]); //空指针异常
}
catch (Exception e) {
try {
Date d= new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-DDHH:mm:ss");
String s =sdf.format(d);
//保存异常信息到文件中,可以续写。
PrintStream ps = new PrintStream(new FileOutputStream("exception.log",true));
ps.println(s); //打印异常的日期
System.setOut(ps);
}
catch (IOException ex)
{
throw new RuntimeException("日志文件创建失败");
}
e.printStackTrace(System.out);
}
}
}
异常信息序列化
class SystemInfo {
public static void main(String[]args) throws IOException {
Properties prop = System.getProperties();
System.out.println(prop); //数组的形式打印
prop.list(System.out);//不用像在集合时一样,用遍历集合
//将属性列表输出到指定的输出流
prop.list(new PrintStream("d:"+File.separator+"sysinfo.txt")); //列表的形式打印到sysinfo.txt.
}
}
复制代码
作者:
曹睿翔
时间:
2013-5-14 21:46
装饰设计模式
当想要对已有的对象进行功能增强时,可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
装饰类的特点:装饰类通常会通过构造方法接受被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
装饰和继承的区别:
一开始我们定义一个读取数据的类MyReader,其基本体系如下:
MyReader
|---MyTextReader
|---MyMediaReader
|---MyDataReader
过一段时间,出现了新的技术(缓冲区),如果是使用继承,那么其他体系结构如下
MyReader
|---MyTextReader
|---MyBufferedTextReader
|---MyMediaReader
|---MyBufferedMediaReader
|---MyDataReader
|---MyBufferedDataReade
发现其体系臃肿,而且每定义一个新的子类都要再定义一个新的基于它的MyBuffered子类。那么可以考虑在设计体系时,将实现新技术的类与之前类体系的关系由继承关系转为组合,使用装饰模式。
好处:装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。
装饰类因为是增强已有对象,具备的功能和已有对象的功能是相同的,只不过是提供了更强的功能所以装饰类和被装饰类通常是都属于一个体系中。
设计时,可以写继承,但如果过于臃肿,可以考虑采用装饰设计模式。
最终体系如下:
MyReader
|---MyTextReader
|---MyMediaReader
|---MyDataReader
|---MyBufferedReader
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2