一. IO体系
字节流(只列出常用):
InputStream顶层抽象类
|----FileInputStream字节文件读取流
|----ObjectInputStream反序列化读取流
|----FilterInputStream过滤流
|----BufferedInputStream字节缓冲读取流
|----DataInputStream基本数据字节读取流
|----PipedInputStream字节管道读取流
|----SequenceInputStream合并字节流。没有与之相对应的输出流
|----ByteArrayInputStream字节数组读取流
OutputStream顶层抽象类
|----FileOutputStream字节文件写入流
|----ObjectOutputStream序列化写入流
|----FilterOutputStream过滤流
|----BufferedOutputStream字节缓冲写入流
|----DataOutputStream基本数据字节写入流
|----PrintStream字节打印流
|----PipedOutputStream字节管道写入流
|----ByteArrayOutputStream字节数组写入流
字符流(只列出常用的)
Reader顶层抽象类
|----FileReader字符文件读取流
|----InputStreamReader字节转换读取流
|----BufferedReader字符缓冲读取流
|----PipedReader字符管道读取流
|----StringReader字符串读取流
|----StringWriter字符数组读取流
Writer顶层抽象类
|----FileWriter字符文件写入流
|----BufferedWriter字符缓写入冲流
|----OutStreamWriter字符转换写入流
|----PipedWriter 字符管道写入流
|----PrintWriter 字符文本打印写入流
|----StringWriter字符串写入流
|----CharArrayWriter字符数组写入流
二. 字符流和字节流的缓冲区
1. 字节缓冲流:
读取流:BufferedInputStream: 创建 BufferedInputStream
时即创建了一个内部缓冲区数组。
写入流:BufferedOutputStream:每一次写入都记得使用falsh()方法刷新流。
2. 字符缓冲流:
读取流:BufferedReader: 从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取,并提供了readLine()方法读取一行字符,其子类LineNumberReader也提供了readLine()方法,此外还提供了getLineNumber()方法获取行号。
写入流:BufferedWriter: 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入,提供了newLine()方法,写入一个分行符号,使用是要记得flash()。
三. 字节流和字符流的转换
1. 字节流转换成字符流: InputStreamReader是字节流通向字符流的桥梁,每次调用 InputStreamReader 中的一个 read() 方法都会导致从基础输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从基础流读取更多的字节,使其超过满足当前读取操作所需的字节。 为了达到最高效率,可要考虑在BufferedReader 内包装InputStreamReader。
例如:
BufferedReader in= new BufferedReader(new InputStreamReader(System.in));
2. 字符流转换成字节流: OutputStreamWriter 是字符流通向字节流的桥梁,每次调用 write() 方法都会针对给定的字符(或字符集)调用编码转换器。在写入基础输出流之前,得到的这些字节会在缓冲区累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递到此 write() 方法的字符是未缓冲的。 为了达到最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中以避免频繁调用转换器。
例如:
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
四. 文件续写
1. Fi
leWriter(
String name
, boolean append)
在给出文件名对象的情况下构造一个FileWriter 对象,如果第二个参数为 true
,则将字节写入文件末尾处,而不是写入文件开始处,也就是续写文件。
2. FileOutputStream(
Stringname
,boolean append)
如果第二个参数为true
,则将字节写入文件末尾处,而不是写入文件开始处,也就是续写文件。
五. BufferedReader /LineNumberReader中的readLine的原理和getLineNumber()原理
1. readLine()这个方法是字符流缓冲对象BufferedRead中的一个特有方法,以及它的子类LineNumberReader也有读取一行的效果。
2. readLine()方法的原理无论是第一行,或者读取多个字符,其实最终它底层都是在硬盘从硬盘上一个一个的读取,然后加入到缓冲区数组中,然后判断每一读到的字符是否为\r(13)和\n(10),在windows操作系统中,\r\n是换行符号,也会被看做是字符,区别在于它们是特殊的转义字符,所以当读取到\r的时候就不将该字符保存到缓冲数组中,当判断是\n的时候,就将缓冲数组中的这些数据转换成字符串返回给调用端,然后将数组中的数据清空,在继续读去下面的其他的字符。
3. 自定义一个BufferedReader实现readLine()方法。
package com.IO_Practice;
importjava.io.FileReader;
importjava.io.IOException;
importjava.io.Reader;
publicclass MyBufferedReaderDemo
{
public static void main(String[] args) throwsIOException
{
FileReader fr = new FileReader("C:\\hello.txt");
MyBufferedReader mbr = new MyBufferedReader(fr);
String s = null;
while((s=mbr.readLine()) != null)
{
System.out.println(s);
}
mbr.myClose();
}
}
classMyBufferedReader
{
//定义一个Reader类型的引用。
private Reader reader;
//定义一个构造方法,接收一个Reader类型的读取流的对象,并将这个读取流对象赋值//给reader引用,方便后面直接读取该(模拟BufferedReader)。
public MyBufferedReader(Reader reader)
{
this.reader = reader;
}
//定义一个MyReaderLine()方法。
public String myReadLine() throws IOException
{
//定一个StringBuilder对象用来串接读取到的字符,最后直接将
//其转换成字符串返回,达到返回一行的效果,提高了性能。
StringBuilder sb = new StringBuilder();
//用来接接收每一次读到的字符(要强制转换成字符)。
int c = 0;
//这个里面的会抛出IOException异常,这里只是为了做笔记,为了代码简单直观,//就不加try、catch语句了。
while( (c = reader.read())!= -1)
{
if((char)c == '\r')
continue;
if((char)c == '\n')
return sb.toString();
else
sb.append((char)c);
}
//这里加判断是为了可以防止最后一行没有换行转义字符。
if(sb.length() != 0 )
return sb.toString();
else
return null;
}
//声明一个关闭流的方法。
public void myClose() throws IOException
{
reader.close();
}
}
4. LineNumberReader继承BufferedReader
4.1 LineNumberReader的原理是在内部定一个计数器变量,没调用一次readLine()方法就让计数器进行自增一次,而且此类还继承了BufferedReader类。
4.2 自定义LineNumberReader
package com.IO_Practice;
importjava.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
publicclass MyLineNumberReaderDemo
{
publicstatic void main(String[] args)throws IOException
{
FileReader fr = new FileReader("C:\\hello.txt");
MyLineNumberReader mnr = new MyLineNumberReader(fr);
String s = null;
while((s=mnr.myReadLine()) !=null)
{
System.out.println(mnr.getLineNumber()+": "+s);
}
mnr.close();
}
}
class MyLineNumberReader
{
private Readerreader;
privateint lineNumber;
public MyLineNumberReader(Reader reader)
{
this.reader = reader;
}
public String myReadLine()throws IOException
{
lineNumber++;
StringBuilder sb = new StringBuilder();
int c = 0 ;
while((c=reader.read()) != -1)
{
if((char)c =='\r' )
continue;
if((char)c =='\n')
return sb.toString();
else
sb.append((char)c);
}
if(sb.length() != 0)
return sb.toString();
returnnull;
}
publicvoid setLineNumner(int lineNumber)
{
this.lineNumber = lineNumber;
}
publicint getLineNumber()
{
returnthis.lineNumber;
}
publicvoid close() throws IOException
{
reader.close();
}
}
六. 装饰设计以及和继承的区别
1. 当想要在对自己已经有的类功能基础上再进行功能扩展和性能增强时使用。
2. 可以定一个类将以有对象传入,基于已有的功能,并提供加强功能,装饰类和被装饰类一般都出自一个体系,或者说来继承自同一个类。
3. 装饰类通常会通过构造方法接收一个被装饰类的对象,并基于被装饰的对象的功能,提供更加强的功能。
4. 比方说我们现在有一个专门读取数据的类MyReader它的继承体系如下:
MyReader
|--MyTextReader 用于操作文本文件的类
|--MyBufferTextReader用来缓冲操作文本文件的类
|--MyMediaReader 用于操作媒体文件的类
|--MyBufferMediaReader用来缓冲操作媒体文件的类
|--MyDataReader 用于操作数据的类
|--MyBufferDataReader用来缓冲操作数据文件的类
从上面的一个体系看出,这样的继承关系,一个对数据进程读取类又衍生很多中对不同文件不同内容进行操作的子类,而每个子类又衍生出一个对它进行包装缓冲的缓冲类,那么这样看起来十分庞大,十分的繁琐,用起来也不方便,更重要的是它造成了继承体系的臃肿,可扩展性极差。那么是不是可以进行分析,上面的这些子类是不是都有个共同点,就是对文件的进行读取操作,而且也继承了MyReader类或者接口,那么是不是可以进行共点抽取形成一个专门的装饰类,用来封装同体系子类的读取操作,并且还可以增强功能,提升性能。最后上面的体系就发生这样的变法,这就叫做装饰类。
MyReader
|--MyTextReader 用于操作文本文件的类
|--MyMediaReader 用于操作媒体文件的类
|--MyDataReader 用于操作数据的类
|--MyBufferReader用来缓冲操作数据文件的类,只剩下这一个类了。
所以上面的MyBufferedReader 的myReadLine()方法的代码是不是实现了一个装饰类了呢?是的。为了符合完整的装饰类标准,我们将其加以优化和修改如下:
packagecom.IO_Practice;
importjava.io.FileReader;
importjava.io.IOException;
importjava.io.Reader;
publicclass MyBufferedReaderDemo
{
public static void main(String[] args)throws IOException
{
FileReader fr = newFileReader("C:\\hello.txt");
MyBufferedReader mbr = newMyBufferedReader(fr);
String s = null;
while((s=mbr.readLine()) != null)
{
System.out.println(s);
}
mbr.close();
}
}
classMyBufferedReader extends Reader
{
private Reader reader;
public MyBufferedReader(Reader reader)
{
this.reader = reader;
}
public String readLine() throws IOException
{
StringBuilder sb = new StringBuilder();
int c = 0;
while( (c = reader.read())!= -1)
{
if((char)c == '\r')
continue;
if((char)c == '\n')
return sb.toString();
else
sb.append((char)c);
}
if(sb.length() != 0 )
return sb.toString();
else
return null;
}
//装饰类和被装饰类都是继承自一个类,或说是一个体系,
//这里继承了Reader,那么就要实现它的两个抽象方法。
public void close() throws IOException
{
reader.close();
}
public int read(char[] cbuf, int off, int len) throws IOException
{
return reader.read(cbuf,off,len);
}
}
七. 读取键盘录入,标准输入、
示例1:
package com.IODemo;
import java.io.IOException;
import java.io.InputStream;
publicclass KeyReaderDemo
{
publicstatic void main(String[]args)throws IOException
{
//我们知道System中的in字段是java中标准的键盘输入,但是这个字段是一个//InputStream字节流的对象。
//所以我们可以按照如下的方式进行定义个键盘录入流,从键盘读入字节信息。
InputStreamis = System.in;
int c = 0;
//InputStream接口子类的对象都有一个read的方法,每次读取一个字节,
//当我们敲过回车之后,它会再一个字节一个字节的读取.
//所以当我们输入一行数据之后,下面的语句是每次打印了一个字节,
//而且每次输入任何数据,打印的时候会多出一个13和10
//所以这里的13和10代表的是windows中的换行符号'\r和'\n',它们的int表现形
//式就是13和10。
while((c=is.read())!= -1)
{
System.out.println(c);
}
is.close();
}
}
执行结果:
输入:ABC 回车
打印输出:
65
66
67
13
10
示例2:
package com.IODemo;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
publicclass KeyInDemo
{
publicstatic void main(String[]args)throws IOException
{
//我们知道System中的in字段是java中标准的键盘输入,但是这个字段是一个//InputStream字节流的对象。
//所以我们可以按照如下的方式进行定义个键盘录入流,从键盘读入字节信息。
InputStream is = System.in;
//既然标准键盘输入流是InputStream的字节对象,
//那么也可以作为字节缓冲读取流的参数使之与字节缓冲读取流关联。
BufferedInputStream bis = new BufferedInputStream(is);
//因为缓冲数据流的read方法是每一次都读取一个字节,所以要定义一个//StringBuilder对象对其
//进行串接缓冲,等到出现换行'\n'符出现之后,直接将一行打印出来。
StringBuilder sb = new StringBuilder();
//因为如果不读到末尾,每一次读取都会返回一个字节码的int表现形
//式回来,所以定一个int变量进行接受。
int ch = 0;
while((ch =bis.read()) !=-1)
{
if(ch =='\r')
{
continue;
}
if(ch =='\n')
{
//当用户输入为over时候,就结束循环读取。
if("over".equals(sb.toString()))
{
break;
}
//如果不是over,接收到换行符号就将sb中存放大字符串打印
System.out.print(sb.toString().toUpperCase());
//当每一次打印出之后,代表着又要重新读取新的字节进行存储,
//但又不能重新new一个StringBuilder对象,所以,最好是使用它的delete
//方法将里面的数据都删除
sb.delete(0,sb.length());
}
else
{
sb.append((char)ch);
}
}
bis.close();
}
}
执行结果:
输入:abc 回车
输出打印: ABC
示例3:
通过上面的例子,我们知道了读取一行readLine的原理,那么我们能不能使用readLine()方法?可是readLine()方法不是BufferedReader的方法吗?通过查看API
我们发现,在Reder的子类中有一个InputStreamReader类,这个类可以实现字节流向字符流的转换,而且这个类必须要接收一个InputStream字节流对象,既然有这么一个类,这么一个关联关系,那我们是不是可以用BufferedReader进行封装装饰呢?
package com.IODemo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
publicclass KeyInDemo2
{
publicstatic void main(String[] args)throws IOException
{
//建立一个标准键盘输入流。
InputStream is = System.in;
//创建一个InputStreamReader类对象,这个类可以实现字节流向字符流的转换,//而且这个类必须要接收一个InputStream字节流对象
InputStreamReader isr = new InputStreamReader(is);
//使用字符缓冲流对象对其进行装饰。
BufferedReader br = new BufferedReader(isr);
String s = null;
while( (s = br.readLine() )!=null)
{
if("over".equals(s))
{
break;
}
System.out.println(s);
}
br.close();
}
}
以上同样实现了输入一行打印一行的效果
标准输出
示例1:
package com.IODemo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
publicclass PrintStreamDemo
{
publicstatic void main(String[] args)throws IOException
{
InputStream is = System.in;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bfr = new BufferedReader(isr);
//以上是标准的输入流操作,从键盘读入数据。
//创建一个标准的控制台输出对象。
PrintStream ps = System.out;
String s = null;
while( (s = bfr.readLine()) !=null)
{
//使用标准输出对象中的write输出一个byte数组的0下标到length小标个数的字//节,并以字符方式显示。
byte[] bt = s.getBytes();
ps.write(bt, 0 , bt.length);
}
isr.close();
bfr.close();
}
}
打印结果:
输入:abcderf
输出:abcderf
输入:12345
打印:12345
示例2:
以上最终是从键盘读入的是整行的字符串,然后再将字符串转换成字节数组使用write(byte[] b , 0 ,b.length)的方式向控制台打印了数据,这样操作看起来不高效率,还不方便,那么有没有其他的方式呢?通过查API,我们在找到一个OutputStreamWriter的这样一个类,这个是Writer的子类,它字符流通向字节流的桥梁,它接收一个OutputStream接口实现类的对象参数,既然是Writer的子类,那我们就可以直接使用BufferedWriter字符缓冲对象进行封装装饰,使用更加简便和高效的操作。
package com.IODemo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
publicclass PrintStreamDemo2
{
publicstatic void main(String[] args)throws IOException
{
InputStream is = System.in;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bfr = new BufferedReader(isr);
PrintStream ps = System.out;
OutputStreamWriter os = new OutputStreamWriter(ps);
BufferedWriter bw = new BufferedWriter(os);
String s = null;
while((s = bfr.readLine()) !=null)
{
bw.write(s);
bw.flush();
}
bw.close();
bfr.close();
}
}
八. 改变标准输入输出设备
例如:
System.in 这个使用来标准的输入,从键盘读取数据,所以它只能用来从键盘读取数据,那么能不能有个方法可以修改呢?通过看API,发现在System这个类中有一个静态的方法 setIn(
InputStream in)
,它可以重新分配“标准”输入流,也就是指定新的输入流对对象,不过这个输入流对象是一个输入字节流,要作为参数传递。另一个是setOut(
PrintStream out)
重新分配“标准”输出流,就是指定一个新的PrintStream流对象作为参数传递。
setIn()示例代码:
package com.IODemo;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
publicclass KeyInDemo2
{
publicstatic void main(String[] args)throws IOException
{
//如下重新设置了一个InputStream类型的对象,将输入源指向了硬盘上的一个
//文件,而不再是键盘输入。
System.setIn(new FileInputStream("c:\\M2.txt"));
InputStream is = System.in;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String s = null;
while( (s = br.readLine() )!=null)
{
if("over".equals(s))
{
break;
}
System.out.println(s);
}
br.close();
}
}
SetOut()示例代码:
package com.IODemo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
publicclass PrintStreamDemo2
{
publicstatic void main(String[] args)throws IOException
{
InputStream is = System.in;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bfr = new BufferedReader(isr);
//如下代码重新将输出流对象进行了修改,将输出从标准控制台改为了
//字节文件流对象。
System.setOut(new PrintStream(new FileOutputStream("c:\\testlog.log")));
PrintStream ps = System.out;
OutputStreamWriter os = new OutputStreamWriter(ps);
BufferedWriter bw = new BufferedWriter(os);
String s = null;
while((s = bfr.readLine()) !=null)
{
bw.write(s);
bw.newLine();
bw.flush();
}
bw.close();
bfr.close();
}
}
十二. 异常日志信息的保存
类Exception extends Throwable的printStackTrace(
PrintStream s)
方法,该方法
会将此Exception及其追踪输出到指定的输出流。
示例代码:
try
{
……..
}
catch(Exception e)
{
e.PrintStackTrace(newPrintStream(new FileOutputStream(“C:\\TEST.log”));
}
十三. Properties类与配置信息的读取与保存
1. Properties类是HashTable类的子类,是Map体系的成员,这就说明它具备了Map集合的特点和操作方式。
2. Properties prop = System.getProperties(); 确定当前的系统属性。我们可以使用Map集合的方法去遍历里面的内容,Properties里面存放的键值内容都是String类型的。
3. 类表示了一个持久的属性集。可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
4. 定义一个Properties集合对象,Propertiespro = new Properties();
5. 向Properties集合对象中添加或修改元素(键值对):pro.setProperty(Stringkey,String value);
6. 从集合中获取单个key的value:Properties:pro.getProperty(Stringkey);
7. 遍历集合获取所有或多个键值对:
先将集合转成Set<String>集合,里面存放的全是Properties集合中的key,使用如下方法:pro. stringPropertyNames(),该方法返回一个Set集合,里面
存放的都是Properties集合中所有的key元素,那么就可以利用Set集合的方法去遍历每一个元素的键值对了。
8. 从一个输入流中读取加载属性列表信息load(
InputStream inStream)
,参数一定是一个字节流输入对象。
package com.IODemo;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
publicclass PropertiesDemo1
{
publicstatic void main(String[] args)throws IOException
{
Properties p = new Properties();
FileInputStreamfis = new FileInputStream("e:\\Test\\Test.ini");
BufferedInputStream bis = new BufferedInputStream(fis);
p.load(bis);
Set<String> set = p.stringPropertyNames();
for(String s : set)
{
System.out.println(s +"=" + p.getProperty(s));
}
}
}
一般我们在从一个直接输入流中(磁盘中的属性配置档案)读取属性配置信息到Properties结合中之后,然后我们使用setProperty(Stringkey,Stirng value)方法修改某个元素和添加某个元素后,其实都只是在程序运行时起到作用,因为这些变更和这些元素都只是保存在内存中,而但当程序退出和,将内存释放,这些信息就不存在了,那么怎么才能实现持久化呢?所以可以使用如
下的方式将变更或将更新后的Properties集合保存到原来磁盘中的配置档案中:pro. store(
OutputStream out,
String comments);
这里的参数
1
是一个字节输出流,也就是要将最新的
Properties
中的元素信息保存到哪一个文件中,要用字节输出流包装,第二个参数是一个自定义的
String
类型的备注信息。
以下步骤就可以将一个磁盘文件中的键值对信息读取添加到pro集合中,然后将修改后的数据更新到流中,随后我们的磁盘上的文件也得到了更新。
package com.IODemo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
publicclass PropertiesDemo1
{
publicstatic void main(String[] args)throws IOException
{
Properties p = new Properties();
FileInputStream fis = new FileInputStream("e:\\Test\\Test.ini");
BufferedInputStream bis = new BufferedInputStream(fis);
p.load(bis);
Set<String> set = p.stringPropertyNames();
for(String s : set)
{
System.out.println(s +"=" + p.getProperty(s));
}
//从字节输入流中读取到属性配置之后,我们又作了修改。
p.setProperty("Ansne","631150873");
//修改之后,使用store(OutputStreamout,String command)方法
//将最新的属性列表更新到字节输出流中。
FileOutputStreamfos = new FileOutputStream("e:\\Test\\Test.ini");
BufferedOutputStream bos = new BufferedOutputStream(fos);
p.store(bos,"Modify by :Zhang sir");
bis.close();
bos.close();
//以上的修改,会发现已经保存到我们的Test.ini中了
}
}
写一个小应用,就是模仿其他应用程序,如果试用次数到达限定值,就提示用户购买注册码,然后退出程序。
package com.IODemo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
publicclass PropertiesDemo2
{
publicstatic void main(String[] args)throws IOException
{
//当程序每次运行的时候,读取配置档案,既然要读取配置档案,是不
//是说要与读取流相关联?
//可是程式首次运行目录上没有这个文件这么办?
//最好是将这个文件封装成File对象
File f = new File("e:\\Test\\Used.ini");
if(!f.exists())
f.createNewFile();
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis);
Properties p = new Properties();
p.load(bis);
String value = p.getProperty("Time");
int count = 0;
if(!(value ==null))
{
count = Integer.parseInt(value);
if (count >= 5)
{
System.out.println("对不起,你的试用次数已经用完,请购买永久性激活码。");
return;
}
}
p.setProperty("Time",(++count)+"");
FileOutputStream fos = new FileOutputStream(f);
BufferedOutputStream bos = new BufferedOutputStream(fos);
p.store(bos,"Modify by : Zhang sir");
bis.close();
bos.close();
}
}