Java输入输出流(IO流)(从装饰者模式理解java.io)
概述
- 程序运行时,将数据从硬盘或网络输入内存进行处理,再将数据从内存输出到硬盘或网络。Java对数据的输入输出依靠IO流实现。
- Java的IO流分为两类:
字节流:传输数据的最小单位为byte(字节)
字符流:传输数据的最小单位char(字符) - 对字节流的输入输出使用InputStream和OutputStream。
- 对字符流的输入输出使用Reader和Writer,其实字符也是由字节组成,使用Reader和Writer传输数据时,也是对字节数据的传输,可以将Reader和Writer看为带有编解码功能的InputStream和OutputStream,可以将字节数据解释为字符。(借鉴了廖雪峰老师对Reader和Writer的理解)
-------------------------------------------分割线(6月16日更新)------------------------------------------------------ - java.io包与装饰者设计模式
inputStream/OutputStream(字节流)
- InputStream/OutputStream都是抽象类.
- InputStream抽象类提供的重要函数:
int read()
int read(byte[] b)
int read(byte[] b, int off, int len)
int read():一次读取一个字节,返回值为字节的数值,当返回值为-1时,表示没有内容可以被读取.
int read(byte[] b):一次读取多个字节到数组b中,返回值为实际读取到字节的个数,当返回值为-1时,表示没有内容可以被读取.
int read(byte[] b, int off, int len):off为偏移量,len为一次读取长度,返回值为实际读取到字节的个数,当返回值为-1时,表示没有内容可以被读取.
将InputStream的一个实现类FileInoutStream做为例子,如下:
import java.io.*;
import java.util.*;
public class IOPractice{
public static void main(String[] args) {
InputStream input = null;
try {
input = new FileInputStream("test");
Byte n = 0;
List<Byte> b = new ArrayList<>();
while((n = (byte)input.read()) != -1) {
b.add(n);
}
//将读取到的字节数组转化为字符串
byte[] array = new byte[b.size()];
for(int i = 0;i < b.size();i++) {
array[i] = b.get(i);
}
String s = new String(array);
System.out.println(s);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(input != null) {
try {
input.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
上述代码运行结果:
- OutputStream类提供的重要函数
void wirte(int b); //向输出流写入一个字节(b的低8位)
void write(byte[] b); //向输出流写入一个字节数组
void write(byte[] b, int off, int len); //向输出流写入一个字节数组
void flush(); //强制将缓冲区中的内容输出 - OutputStream的使用例子如下(使用了OutputStream的一个实现类FileOutputStream)
import java.io.*;
public class IOExceptionPractice {
public static void main(String[] args) {
try {
OutputStream output = new FileOutputStream("test.txt");
output.write("hellow java".getBytes("UTF-8"));
output.close();
}catch (IOException e) {
System.out.println(e);
}
finally {
System.out.println("finally");
}
}
}
Reader/Writer(字符流)
- Reader是一个抽象类,Reader提供常用函数如下:
int read() //返回读取到的字符,当没有数据可读取时,返回-1
int read(char[] arg) //一次读取多个字符,返回读取到的字符数
int read(char[] arg, int off, int len) //off为开始存放读取到的字符的位置,len为个数,返回值为读取到的字符数 - java中的字符:java中的char占两个字节,采用Unicode编码。如果读取的字符为UTF-8编码,该编码一个中文字符占3个字节,如果直接用Char来方法,会导致乱码,故在读取时需要主要编码。
- Reader的使用例子如下(使用了Reader的一个实现类FileReader)
import java.io.*;
import java.nio.charset.StandardCharsets;
public class CharStream{
public static void main(String[] args) {
try {
Reader reader = new FileReader("test.txt"); //注意要将该文件的编码方式改为ANSI(windows下)否则会出现乱码
char[] buf = new char[10];
reader.read(buf);
System.out.println(buf);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- Writer提供的常用函数如下:
void write(int c) //写入一个字节
void write(char[] arg) //一次写入多个字节
void write(String s) //写入一个字符串 - writer的使用例子如下(使用了Writer的一个实现类FileWriter)
import java.io.*;
import java.nio.charset.StandardCharsets;
public class CharStream{
public static void main(String[] args) {
try {
Writer myWriter = new FileWriter("test.txt");
String s = new String("你好java");
myWriter.write(s);
myWriter.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
--------------------------分割线(6月16日更新)--------------------------------------------
java.io与装饰者设计模式
- 装饰者模式(如果没有接触过,可能刚开始有点晕,但是我会举例尽量讲得明白)。
装饰者模式可以实现:动态地将功能附加到对象上。
一般来说,装饰者模式中有四种角色,分别是抽象组件,具体组件,抽象装饰者,具体装饰者(可能具体实现会有一些出入)。这四种角色的关系如下图所示。
现在举一个具体的例子:对于饮料店的饮料,有不同种类,如黑咖啡,拿铁等。顾客在点完一杯饮料后,还可以往饮料中加入一些配料,如抹茶,奶盖等。那么在这个场景中,饮料就是一个抽象组件;黑咖啡、拿铁就是具体组件;配料就是抽象装饰者;抹茶、奶盖就是具体装饰者。他们之间的关系如下图所示。(这个例子取自于Head First 设计模式)
针对饮料店的例子,我们实现的代码如下(为了简单,没有实现Latte类):
//总共有6个源文件,主函数在StarBucks.java中
/*
* Beverage.java
* 饮料类-抽象组件
*/
public abstract class Beverage{
String description = "unknown Beverage";
public String getDescription() {
return this.description;
}
public abstract double cost();
}
/*
* DarkRoast.java
* 黑咖啡类-具体组件
* 继承Beverage
*/
public class DarkRoast extends Beverage{
Beverage beverage;
public String getDescription() {
return "DarkRoast coffee";
}
public double cost() {
return 0.88;
}
}
/*
* CondimentDecorator.java
* 配料类-抽象装饰者
* 继承Beverage
*/
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
/*
* Mocha.java
* 抹茶类-具体装饰者
* 继承CondimentDecorator
*/
public class Mocha extends CondimentDecorator{
Beverage beverage;
Mocha(Beverage beverage){
this.beverage = beverage;
}
public double cost() {
return 0.2f + this.beverage.cost();
}
@Override
public String getDescription() {
return this.beverage.getDescription() + ", mocha";
}
}
/*
* Naigai.java
* 奶盖类-具体装饰者
* 继承CondimentDecorator
*/
public class Naigai extends CondimentDecorator{
Beverage beverage;
Naigai(Beverage beverage){
this.beverage = beverage;
}
public double cost() {
return 0.5f + this.beverage.cost();
}
@Override
public String getDescription() {
return this.beverage.getDescription() + ", naigai";
}
}
/*
* StartBucks.java
* 主类
*/
public class Starbucks{
public static void main(String[] args) {
Beverage coffee1 = new DarkRoast();
System.out.println(coffee1.getDescription() + " . cost = " + coffee1.cost());
//coffee2:黑咖啡 + 抹茶、奶盖
Beverage coffee2 = new Naigai(new Mocha(new DarkRoast()));
System.out.println(coffee2.getDescription() + " . cost = " + coffee2.cost());
}
}
- 装饰者模式和java.io的关系
我们使用java.io读取文件时,经常会用到BufferedInputStream类,该类使用缓冲区来增强读文件的性能,并且支持readline()函数来一次读取一行的数据。一般定义BufferedInputStream对象的代码如下:
File filename = new File(fllePath);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(filename));
这里定义BufferedInputStream对象的代码和上面例子中定义coffee2(黑咖啡+抹茶+奶盖)的代码有木有很相似啊。
其实:FileInputStream就是一个具体组件;BufferedInputStream是一个具体装饰者。
java.io中类的关系如下图所示:
总结:在使用java.io库时,面对库中的类,只要明白它们大多数都是具体装饰者而已,这样一切都会变得简单很多。
最后,通过实现一个自己的java.io装饰者来加深印象。
/*
* LowerCaseInputStream.java
* LowerCaseInputStream-具体装饰者
* 继承FilterInputStream
* 将读取到的内容中的大写字母转化为小写字母
*/
import java.io.*;
public class LowerCaseInputStream extends FilterInputStream{
public LowerCaseInputStream(InputStream in){
super(in);
}
public int read() throws IOException {
int c = super.read();
if(c == -1) {
return c;
}else {
return Character.toLowerCase((char)c);
}
}
public int read(byte[] b,int offset,int len) throws IOException {
int result = super.read(b,offset,len);
for(int i = offset;i < offset + result;i++) {
b[i] = (byte) Character.toLowerCase((char)b[i]);
}
return result;
}
}
/*
* MyInputStream.java
* 主类
*/
import java.io.*;
public class MyInputStream{
public static void main(String[] args) {
InputStream in;
try {
in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
int c;
while((c = in.read()) != -1) {
System.out.print((char)c);
}
}catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}
}
最近在通过看设计模式学java,《Head First 设计模式》真的是一本好书啊