1.基本概念和基本情况:
a.流:一组有序的数据序列。
b.字节流:数据流中最小的数据单元是字节。
c.字符流:数据流中最小的数据单元是字符。
d.java.io.InputStream和java.io.OutputStream分别表示字节输入流和字节输出流。都是抽象类,不能被实例化。所有的字节操作流都是这两个类的直接或间接子类。read()读取的是一个8位字节,write()写入的是一个8位字节。
e.java.io.Reader和java.io.Writer分别表示字符输入流和字符输出流。都是抽象类,不能被实例化。所有的字符操作流都是这两个类的直接或间接子类。
2.字节输入流:
1)字节数组输入流:ByteArrayInputStream类
从内存中的字节数组中读取数据,其数据源是一个字节数组。该类本身采用了适配器设计模式,将字节数组类型转化为输入流,完成对数组的读操作,读到的每一个字节类型的元素都会自动转换为int。
使用如下:
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args){
byte buff[]= new byte[]{2,15,67,-1,-9,9};
ByteArrayInputStream inputStream = new ByteArrayInputStream(buff,1,4);
int data = inputStream.read();
while (data!=-1) {
System.out.println(data + " ");
data = inputStream.read();
}
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这里的值会在前边补0而导致变化,但是请注意在byte类型赋值给int类型时,取值不会变。
2)文件输入流:FileInputStream类
从文件中读取数据流。注意系统的默认编码格式。
3)字符串输入流:StringBufferInputStream类。(已被StringReader类代替而废弃)
从一个字符串中读取。本身采用了适配器设计模式。
4)管道输入流PipedInputStream类
通常用于线程间通信,A执行管道输入流的read方法,暂时没有数据的时候这个线程就会被阻塞,只有当线程B向管道输入流写了新的数据后A才会执行。
5)顺序输入流:SequenceInputStream类
用于将几个输入流串联在一起,合并为一个输入流。当类通过这个类来读取数据时,它会依次(以输入流加入为序) 从所有被串联的输入流中读取数据。关闭输入流不需要先依次关闭各个子输入流。
6)过滤输入流:FilterInputStream类
这是装饰器,它本身继承了InputStream类,还可以用来修饰其他的输入流类。其构造方法为protected访问级别,因此外部程序不能创建这个类本身的实例。
关于装饰器模式,在这摘录一个网友(江苏 无锡 缪小东 )的文字,我把一些调侃的话删掉了,留其本质内容:
“三明治”的例子:三明治必不可少的是两块面包片,然后可以在夹层里加上蔬菜、沙拉、咸肉等等,外面可以涂上奶油之类的。假如现在你要为一个三明治小店构造一个程序,其中要设计各种三明治的对象。可能你已经创建了一个简单的Sandwich对象,现在要产生带蔬菜的就是继承原有的Sandwich添加一个蔬菜的成员变量,看起来很“正点”的做法,以后我还要带咸肉的、带奶油的、带蔬菜的又分为带青菜的、带芹菜的、生菜的……还是一个一个继承是吧!假如我们还需要即带蔬菜又带其它肉类,设置我们还要求这些添加成分的任意组合,那你就慢慢继承吧!下面我们就使用装饰器模式来设计这个库吧!下图是我们的设计图:
下面是以上各个类的意义:
1. Ingredient(成分):所有类的父类,包括它们共有的方法,一般为抽象类且方法都有默认的实现,也可以为接口。它有Bread和Decorator两个子类。这种实际不存在的,系统需要的抽象类仅仅表示一个概念,图中用红色表示。
2. Bread(面包):就是我们三明治中必须的两片面包。它是系统中最基本的元素,也是被装饰的元素,和IO中的媒质流(原始流)一个意义。在装饰器模式中属于一类角色,所以其颜色为紫色。
3. Decorator(装饰器):所有其它成分的父类,这些成分可以是猪肉、羊肉、青菜、芹菜。这也是一个实际不存在的类,仅仅表示一个概念,即具有装饰功能的所有对象的父类。图中用蓝色表示。
4. Pork(猪肉):具体的一个成分,不过它作为装饰成分和面包搭配。
5. Mutton(羊肉):同上。
6. Celery(芹菜):同上。
7. Greengrocery(青菜):同上。
总结一下装饰器模式中的四种角色:1.被装饰对象(Bread);2.装饰对象(四种);3.装饰器(Decorator);4.公共接口或抽象类(Ingredient)。其中1和2是系统或者实际存在的,3和4是实现装饰功能需要的抽象类。
写段代码体会其威力吧!(程序很简单,但是实现的方法中可以假如如何你需要的方法,意境慢慢体会吧!)
//Ingredient.java
public abstract class Ingredient {
public abstract String getDescription();
public abstract double getCost();
public void printDescription(){
System.out.println(" Name "+ this.getDescription());
System.out.println(" Price RMB "+ this.getCost());
}
}
所有成分的父类,抽象类有一个描述自己的方法和一个得到价格的方法,以及一个打印自身描述和价格的方法(该方法与上面两个方法构成模板方法哦!)
//Bread.java
public class Bread extends Ingredient {
private String description ;
public Bread(String desc){
this.description=desc ;
}
public String getDescription(){
return description ;
}
public double getCost(){
return 2.48 ;
}
}
面包类,因为它是一个具体的成分,因此实现父类的所有的抽象方法。描述可以通过构造器传入,也可以通过set方法传入。同样价格也是一样的,我就很简单地返回了。
//Decorator.java
public abstract class Decorator extends Ingredient {
Ingredient ingredient ;
public Decorator(Ingredient igd){
this.ingredient = igd;
}
public abstract String getDescription();
public abstract double getCost();
}
装饰器对象,所有具体装饰器对象父类。它最经典的特征就是:1.必须有一个它自己的父类为自己的成员变量;2.必须继承公共父类。 这是因为装饰器也是一种成分,只不过是那些具体具有装饰功能的成分的公共抽象罢了。在我们的例子中就是有一个Ingredient作为其成员变量。Decorator继承了Ingredient类。
//Pork.java
public class Pork extends Decorator{
public Pork(Ingredient igd){
super(igd);
}
public String getDescription(){
String base = ingredient.getDescription();
return base +"\n"+"Decrocated with Pork !";
}
public double getCost(){
double basePrice = ingredient.getCost();
double porkPrice = 1.8;
return basePrice + porkPrice ;
}
}
具体的猪肉成分,同时也是一个具体的装饰器,因此它继承了Decorator类 。猪肉装饰器装饰可以所有的其他对象,因此通过构造器传入一个Ingredient的实例,程序中调用了父类的构造方法,主要父类实现了这样的逻辑关系。同样因为方法是具体的成分,所以getDescription得到了实现,不过由于它是具有装饰功能的成分,因此它的描述包含了被装饰成分的描述和自身的描述。价格也是一样的。价格放回的格式被装饰成分与猪肉成分的种价格哦!
从上面两个方法中我们可以看出,猪肉装饰器的功能得到了增强,它不仅仅有自己的描述和价格,还包含被装饰成分的描述和价格。主要是因为被装饰成分是它的成员变量,因此可以任意调用它们的方法,同时可以增加自己的额外的共同,这样就增强了原来成分的功能。
//Mutton.java
public class Mutton extends Decorator{
public Mutton(Ingredient igd){
super(igd);
}
public String getDescription(){
String base = ingredient.getDescription();
return base +"\n"+"Decrocated with Mutton !";
}
public double getCost(){
double basePrice = ingredient.getCost();
double muttonPrice = 2.3;
return basePrice + muttonPrice ;
}
}
羊肉的包装器。
//Celery.java
public class Celery extends Decorator{
public Celery(Ingredient igd){
super(igd);
}
public String getDescription(){
String base = ingredient.getDescription();
return base +"\n"+"Decrocated with Celery !";
}
public double getCost(){
double basePrice = ingredient.getCost();
double celeryPrice =0.6;
return basePrice + celeryPrice ;
}
}
芹菜的包装器。
//GreenGrocery.java
public class GreenGrocery extends Decorator{
public GreenGrocery (Ingredient igd){
super(igd);
}
public String getDescription(){
String base = ingredient.getDescription();
return base +"\n"+"Decrocated with GreenGrocery !";
}
public double getCost(){
double basePrice = ingredient.getCost();
double greenGroceryPrice = 0.4;
return basePrice + greenGroceryPrice ;
}
}
青菜的包装器。
下面我们就领略装饰器模式的神奇了!我们有一个测试类,其中建立夹羊肉的三明治、全蔬菜的三明治、全荤的三明治。
public class DecoratorTest{public static void main(String[] args){
Ingredient compound = new Mutton(new Celery(new Bread("Master24's Bread")));
compound.printDescription();
compound = new Celery(new GreenGrocery(new Bread("Bread with milk")));
compound.printDescription();
compound = new Mutton(new Pork(new Bread("Bread with cheese")));
compound.printDescription();
}
}
以上就是一个简单的装饰器类。
a.DataInputStream类
实现了DataInput接口,用于读取基本类型数据。如int,long,float,double,boolean。其中的readUTF()方法还能读取UTF-8编码的字符串。其应该与DataOutputStream配套使用。也就是说,用DataInputStream读取由DataOutputStream写出的数据,才能保证获得正确的数据。
b.LineNumberInputStream类:已被废弃
c.BufferedInputStream类
覆盖了被装饰的输入流的读数据行为,利用buffer来提供效率。一般在读取数据文件或者键盘输入时都使用其进行装饰。
d.PushbackInputStream类
很少用到。
3.字节输出流
1)字节数组输出流:ByteArrayOutputStream类
向内存中的字节数组写数据,使用toByteArray方法获得字节数组。
2)文件输出流:FileOutputStream类
向文件中写数据。注意默认下使用则是覆盖文件,必须使用FileOutputStream(String name,boolean append),在后边设置为true则是在追加。
3)过滤输出流:FilterOutputStream类
和过滤输入流的特性一样。
a)DataOutputStream类:
用于向输出流写基本数据类型。
b)BufferedOutputStream类:
缓冲区提高效率。在超过缓冲区时会将缓冲区的内的数据写入输出流,若没有超过则不会自动写入,否则使用flush()方法刷新,或者关闭输出流。
c)PrintStream类:
采用本地OS默认的字符编码输出格式化数据。每一个Print()方法都与一个println()方法对应。使用checkError()方法判断写数据是否成功。它也带有缓冲区,不过用户可以自己设置刷新规则(见其构造函数的一个重载)。
4.字符输入流
注意BufferedReader不是FilterReader的子类。
1)字符数组输入流:CharArrayReader类
从内存中的字符数组中读取字符。
2)字符串输入流:StringReader类
数据源是字符串。
3)InputStreamReader类:
主要是解决Unicode字符编码和用户指定编码的变换问题。为了提高效率则可以使用BufferedReader来修饰。它有个子类是FileReader类,但是其只能按照本地平台的字符编码来读取数据,用户不能指定编码类型。
4)BufferedReader类:
带有缓冲区的字符输入流。
5.字符输出流
1)字符数组输出流:CharArrayWriter类
将内存中的字符中写入字符数组。
2)BufferedWriter类:带有缓冲。
3)OutputStreamWriter类:
为了提高效率则可以使用BufferedWriter来修饰。它有个子类是FileWriter类,但是其只能按照本地平台的字符编码来写入数据,用户不能指定编码类型。
4)PrintWriter类:能输出格式化的数据。在输出字符数据的场合,应该优先考虑用PrintWriter。
6.标准IO
三个标准输入输出流:
System.in:InputStream类,代表标准输入流,默认为键盘。常用套路是InputStreamReader适配器把System.in转换为Reader类型,再用BufferedReader装饰它。
System.out:PrintStream类,代表标准输出流,默认为控制台。通常转化为PrintWriter类型。
System.err:PrintStream类,代表标准错误输出流,默认为控制台。
重定向使用System类中的,setIN(),setOut(),setErr()方法。
7.随机访问文件类:RandomAccessFile
可以从任意位置对文件进行读写。实现了DataInput和DataOutput接口,可以读写格式化的数据。
8.新IO库的部分介绍
java.nio包中,目的在于提高IO操作效率。缓冲器Buffer类:减少了实际物理读写次数,减少动态分配和回收内存区域的次数。这个类可以使得程序直接控制和运用缓冲区。抽象类,有8个具体的缓冲区类,都有能返回自身实例的静态工厂方法。
缓冲区的几个属性(也提供了改变这三个属性的方法):
容量:缓冲区的大小。
极限:缓冲区的当前终点。这个点即为实际的数据区的终点。
位置:缓冲区的下一个读写单元。
Channel是用来连接缓冲区与数据源的。通道在创建时被打开,一旦关闭就不能再打开了。
有两个常用方法:一个是flip(),确保后续操作只针对在缓冲区中的当前数据,而clear()方法则把缓冲区的极限设为容量值,为后续向缓冲区填入更多的数据做准备。
2)字符编码Charset类
其中的每个实例都代表特定的字符编码类型。
3)FileChannel读写文件
放一个例子吧,从书上摘的:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;public class Test{
public static void main(String[] args) throws IOException{
final int SIZE = 1024;
/*向文件中写数据*/
FileChannel fcChannel = new FileOutputStream("c:\\test.txt").getChannel();
fcChannel.write(ByteBuffer.wrap("你好,".getBytes()));//静态方法wrap为包装为一个ByteBuffer对象
fcChannel.close();
/*向文件末尾添加数据*/
fcChannel = new RandomAccessFile("c:\\test.txt","rw").getChannel();
fcChannel.position(fcChannel.size());//定位到文件尾
fcChannel.write(ByteBuffer.wrap("世界!".getBytes()));
fcChannel.close();
/*读数据*/
fcChannel = new FileInputStream("c:\\test.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(SIZE);//创建ByteBuffer对象
fcChannel.read(buffer);//将文件中的数据读入缓冲区中
buffer.flip();//将极限设为当前位置值,再把position设为0,这使得接下来的cs.decode(buff)方法仅仅操作刚刚写入缓冲区的数据。
Charset cs = Charset.defaultCharset();//获得本地OS的字符编码
System.out.println(cs.decode(buffer));//转换为Unicode字符编码
fcChannel.close();
}
}