------------android培训、java培训、java学习型技术博客、期待与您交流! ------------
1.装饰设计模式
概述
装饰模式(Decorator)又名包装(Wrapper)模式,装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
装饰模式可以在不创造更多的子类的模式下,将对象的功能加以扩展。当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能,那么自定义的该类称为装饰类。
装饰类的特点:装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
与继承的区别
假如一开始我们定义一个读取数据的类MyReader,其基本体系如下:
MyReader
|---MyTextReader
|---MyMediaReader
|---MyDataReader
过一段时间,出现了新的技术(缓冲区),如果是使用继承,那么其他体系结构如下
MyReader
|---MyTextReader
|---MyBufferedTextReader
|---MyMediaReader
|---MyBufferedMediaReader
|---MyDataReader
|---MyBufferedDataReade
于是上面发现有缺陷,于是我们单独定义一个MyBufferReader类
class MyBufferReader
{
MyBufferReader(MyTextReader text)
{}
MyBufferReader(MyMediaReader media)
{}
}
但是发现其体系臃肿,而且每定义一个新的子类都要再定义一个新的基于它的MyBuffered子类。那么可以考虑在设计体系时,将实现新技术的类与之前类体系的关系由继承关系转为组合,使用装饰模式。
好处:装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。
装饰类因为是增强已有对象,具备的功能和已有对象的功能是相同的,只不过是提供了更强的功能,所以装饰类和被装饰类通常是都属于一个体系中。
设计时,可以写继承,但如果过于臃肿,可以考虑采用装饰设计模式。
最终体系如下:
MyReader
|---MyTextReader
|---MyMediaReader
|---MyDataReader
|---MyBufferReader
装饰设计模式示例:
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("来一根");
}
}
class PersonDemo
{
public static void main(String[] args)
{
Person p = new Person();//建立一个对象
p.chifan();//基本的吃饭功能
SuperPerson sp = new SuperPerson(p);//将对象作为参数传入,修饰基本功能
sp.superChifan();//调用增强之后的功能
}
}
2.LineNumberReader类
java.lang.Object
----java.io.Reader
----java.io.BufferedReader
-----java.io.LineNumberReader
LineNumberReader也是一个包装类,它在BufferedReader的基础上增加了一个记录行号的功能,而记录行号是在readLine方法中操作的,所以它继承了BufferedReader并复写了readLine方法,同时定义了方法setLineNumber(int) 和getLineNumber(),它们可分别用于设置和获取当前行号。
用法示例:
import java.io.*;
public class LineNumberReaderDemo
{
public static void main(String[] args)throws IOException
{
FileReader fr = new FileReader("D:\\123.txt");//建立读取流对象
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;//定义用作临时存储的字符串变量
lnr.setLineNumber(100);//设置起始行号,设置为100,从101开始
while((line=lnr.readLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);//连同行号一起打印
}
//打印结果为前面带着行号的数据
lnr.close();//关键资源
}
}
3.字节流
字节流的抽象基类:
|---->InputStream:字节输入流
|---->OutputStream:字节输出流
它的操作与字符流类似,可以参与字符流的定义、读取、写入、处理异常的格式,只不过是处理的数据不同,因为对于非字符的数据,比如图片、视频、音频文件(例如mp3)等,这些文件只能用字节流对之进行操作。
FileInputStream
FileInputStream是InputStream的一个子类,用于读取诸如图像数据之类的原始字节流
构造方法:
|--->FileInputStream(File file)
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
|--->FileInputStream(FileDescriptor fdObj)
通过使用文件描述符 fdObj 创建一个 FileInputStream,该文件描述符表示到文件系统中某个实际文件的现有连接。
|--->FileInputStream(String name)
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
常用方法:
|--->int available()返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
|--->void close()关闭此文件输入流并释放与此流有关的所有系统资源。
|--->int read()从此输入流中读取一个数据字节。
|--->int read(byte[] b)从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
|--->int read(byte[] b, int off, int len)从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
字节读取流代码示例:
import java.io.*;
public class InputStreamDemo {
public static void main(String[] args)
{
read1();
}
//第一种读取方式:按字节来读取
public static void read1()
{
FileInputStream fis = null;
try {
fis = new FileInputStream("D:\\Demo\\demo.java");//新建字节读取的对象,明确源文件
int x = 0;
while((x = fis.read())!=-1)
{
System.out.print((char)x);
}
} catch (IOException e) {
e.printStackTrace();
}
finally//执行关闭资源的操作
{
if(fis!=null)
{
try{
fis.close();
}catch(IOException e2){
e2.printStackTrace();
}
}
}
}
//第二种读取方式:按字节数组读取
public static void read2()
{
FileInputStream fis = null;
try {
fis = new FileInputStream("D:\\Demo\\demo.java");//新建字节读取的对象,明确源文件
int len = 0;
byte[] buff = new byte[1024];//定义一个字节数组,用于存储字节
while((len=fis.read(buff))!=-1)//每次将读取到的字节存储进buff数组
{
System.out.println(new String(buff,0,len));//将字节数组转换成字符串输出
}
} catch (IOException e) {
e.printStackTrace();
}
finally//执行关闭资源的操作
{
if(fis!=null)
{
try {
fis.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
}
}
注意available()这个方法的用法,取文件字节数,然后定义一个刚好大小的字节数组。代码如下:
fis =new FileInputStream(file);
byte [] ch =newbyte[fis.available()];
fis.read(ch);
System.out.println(new String(ch));
但是如果文件过大,会造成内存溢出。
FileOutputStream
FileOutputStream是OutputStream的一个子类,用于写入诸如图像数据之类的原始字节的流。
构造方法:
|--->FileOutputStream(File file)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
|--->FileOutputStream(File file,boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
|--->FileOutputStream(FileDescriptor fdObj)
创建一个向指定文件描述符处写入数据的输出文件流,该文件描述符表示一个到文件系统中的某个实际文件的现有连接。
|--->FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的输出文件流。
|--->FileOutputStream(String name, boolean append)
创建一个向具有指定 name 的文件中写入数据的输出文件流。
常用方法:
|--->close():关闭此文件输出流并释放与此流有关的所有系统资源。
|--->write(byte[] b):将 b.length 个字节从指定byte数组写入此文件输出流中。
|--->write(byte[] b, int off, int len):将指定byte数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
|--->write(int b):将指定字节写入此文件输出流。
字节写入流代码示例:
import java.io.*;
public class OutputStreamDemo {
public static void main(String[] args)
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream("D:\\Demo\\fos.txt");
//定义一个字符串,因为字节流只能以字节或字节数组的形式读取
String str = "努力努力";
byte [] by =str.getBytes();//转成字节数组形式
fos.write(by);//不用刷新
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
if(fos!=null)
{
try
{
fos.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
练习:通过字节流对图片文件的复制操作
/*
思路:
1,用字节读取流对象和图片关联。
2,用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。
3,通过循环读写,完成数据的存储。
4,关闭资源。
*/
import java.io.*;
class CopyPic
{
public static void main(String[] args)
{
FileOutputStream fos = null;
FileInputStream fis = null;
try
{
fos = new FileOutputStream("c:\\2.bmp");//明确复制目的地
fis = new FileInputStream("c:\\1.bmp");//关联一个源图片文件
byte[] buf = new byte[1024];//用于临时存储的字节数组
int len = 0;//定义变量用于判断是否是结尾处
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
}
catch (IOException e)//捕获文件复制中的异常
{
throw new RuntimeException("复制文件失败");
}
finally//执行关闭流的操作
{
try
{
if(fis!=null)
fis.close();//关闭读取流
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(fos!=null)
fos.close();//关闭写入流
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}
4.字节流缓冲区
字节流缓冲区同样是提高了字节流的读写效率。
对应类:
BufferedOutputStream (extends OutputStream)
BufferedInputStream (extends InputStream)
练习:利用缓冲区的技术,拷贝Mp3文件
import java.io.*;
public class copyMp3 {
public static void main(String[] args)
{
BufferedInputStream buis=null;
BufferedOutputStream buos=null;
try
{
buis=new BufferedInputStream(new FileInputStream("D:\\play.mp3"));
buos=new BufferedOutputStream(new FileOutputStream("D:\\play_copy2.mp3"));
//第一种复制方式
int num=0;
while((num=buis.read())!=-1)
{
buos.write(num);
}
//第二种复制方式
byte[] buff = new byte[1024];
int len = 0;
while((len=buis.read(buff))!=-1)
{
buos.write(buff,0,len);
}
}
catch(IOException e)
{
throw new RuntimeException("复制失败");
}
finally
{
if(buis!=null)
{
try {
buis.close();
} catch (Exception e2) {
throw new RuntimeException("关闭读取流失败");
}
}
if(buos!=null)
{
try {
buos.close();
} catch (Exception e2) {
throw new RuntimeException("关闭写入流失败");
}
}
}
}
}
5.转换流
转换流包括:
InputStreamReader:字子流通向字符流的桥梁
OutputStreamWriter:字符流通向字节流的桥梁
转换流的由来:是字节流和字符流的桥梁;方便了字节流和字符流之间的操作
转换流的应用:字节流中的数据都是字符时,转成字符流操作更高效
InputStreamReader
是字节流通向字符流的桥梁。每次调用 InputStreamReader 中的一个 read()方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。
构造方法:
InputStreamReader(InputStream in)
InputStreamReader(InputStream in, String charsetName)
示例:BufferedReader in = new BufferedReader(new InputStreamReader(System.in));(必须记住)
OutputStreamWriter
是字符流通向字节流的桥梁,将要写入流中的字符编码成字节。 每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。
构造方法:
OutputStreamWriter(OutputStream out)
OutputStreamWriter(OutputStream out, String charsetName)
示例:
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
System.in 默认设备为键盘
System.setIn(InputStream in)\\ 重新分配“标准”输入流。
System.out 默认设备为控制台
System. setOut(PrintStream out) \\重新分配“标准”输出流。
6.标准输入与输出
System类中的字段:in,out,它们各代表了系统标准的输入和输出设备,默认输入设备是键盘,输出设备是显示器。
System.in的类型是InputStream.
System.out的类型是PrintStream
java.lang.Object
|--java.io.OutputStream
|--java.io.FilterOutputStream
|---java.io.PrintStream
示例:
例:获取键盘录入数据,然后将数据流向显示器,那么显示器就是目的地。
通过System类的setIn,setOut方法对默认设备进行改变:
System.setIn(new FileInputStream(“1.txt”));//将源改成文件1.txt。
System.setOut(new FileOutputStream(“2.txt”));//将目的改成文件2.txt
因为是字节流处理的是文本数据,可以转换成字符流,操作更方便。
BfferedReader bufr =new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw =new BufferedWriter(new OutputStreamWriter(System.out));
个人总结
本节又学习了一个java的设计模式:装饰设计模式,个人理解是定义一个新的类,将原有类对象作为参数传入,然后提供比原来对象的更强大的功能。避免了继承体系的臃肿,强化了类与类之间的关系。字节流是专门用于操作各种媒体文件的流对象,字符流是专门用于操作字符类文件的流对象,二者尽量避免交叉使用,有可能会出现操作失败等错误(比如用字符流去操作mp3文件的话,就有可能mp3里面的字节发生改变,从而文件使用出错)。