java输入与输出I/O(提升篇)


上接“黑马程序员-->输入与输出I/O(基础篇)”

 

十、装饰设计模式
1、概述:
什么叫“装饰设计模式”?见名知意,就是将一个“设计好的类装饰了一下而已,增强其功能”。当想要对已有的对象进行功能的增强的时候,可以定义一个装饰类,将已有的类创建的对象传入,基于已有的功能,并且提供加强功能。那么这个自定义的类就是“装饰类”,这个设计模式就叫做“装饰设计模式”。

2、格式:
“装饰设计模式”通常会使用“装饰类”的“构造方法”接受被装饰的类创建的对象,并基于被装饰的对象的功能,提供更强的功能。

 

3、例子:
比方说我定义了一个Dog类,里面创建的一个“Cry”方法就是输出一句话“汪汪”但是我觉得这个功能太少了,因为Dog类还有很多的功能,比方说可以“睡觉”“尿尿”等,所以我可以定义一个SuperDog装饰类,这个类用来加强Dog类中没有的功能,可以使用SuperDog类中的构造方法将前面定义的Dog类创建的对象传入,然后再定义一个SuperCry方法,在里面定义一些其他的功能,然后将Dog类中cry方法进行调用即可,这样相当的不错,然后再主函数中只要创建两个类的对象,然后将Dog类创建的对象传入到SuperDog后面作为参数中即可。

class Dog{
 public void cry(){
  System.out.println("汪汪");
 }
}
class SuperDog{
 private Dog d;
 SuperDog(Dog d){
  this.d=d;
 }
 public void superCry(){
  d.cry();
  System.out.println("drink");
  System.out.println("pee");
  System.out.println("shit");
 }
}
class Introduce{
 Introduce(){
  System.out.println("this is a introduce below:");
 }
}
public class ZhuangShiSheJiMoShi {
 public static void main(String[] args) {
  Dog d=new Dog();
  new Introduce();
  new SuperDog(d).superCry(); //所有功能都能实现!
 }
}


 

 

4、“装饰类”和“继承类”的区别
两者的确很相似,都可以实现相同的功能。但是,“装饰设计模式”比“继承”要灵活,避免了“继承体系”的臃肿,降低了类与类之间的从属关系,装饰类因为增强了对象,具备的功能和已有的是相同的,只不过提供了更强的功能。所以“装饰类”和“被装饰类”通常都属于一个体系中。
可以想象到,如果使用的是继承的方法的话,那么整个体系将会比较臃肿,如果使用的是“装饰模式”的话,如果自己写的一个超级类中出现了问题需要被删掉的话,将会随时可以将自己写的扩展类“撤掉”,这是相当的方便的。

十一、“合并流”SequenceInputStream
1、概述:
首先明白什么是“流的合并”?就是将几个“读取流”合并成一个流,然后对这个合成的流进行操作。比方说有2个文件的“读取流”,先不管是用“字符流”还是“字节流”,先进行读取操作,自己可以使用SequenceInputStream类创建的“流对象”对这3个流进行“合并”,也可以说成是进行了封装操作。然后将这个“合并流”写入到第3个文件中去,相当的叼咋天吧!?

2、注意:
没有SequenceOutputStream这个类,因为几个“流对象”进行“合并”之后,成为了一个新的“流对象”,这个流对象就是SequenceInputStream类创建的对象,只不过是用来读取的,里面只有简单的几个方法,然后用这些方法进行操作就可以啦。如果有SequenceOutputStream这个类的话,意思将会是:将一个“流对象”进行拆分,但是没有这样的类!

 

3、举例说明问题:
程式要求:将两个文本中的内容写入到第三个文本中。

public class SequenceInputStreamDemo {
 public static void f() throws IOException{
  //创建合并流
  SequenceInputStream sis=null;
  //创建输出流
  FileOutputStream out=null;
  //创建输入流
  FileInputStream f1=null;
  FileInputStream f2=null;
  try{
   f1=new FileInputStream("d:\\file1.txt");
   f2=new FileInputStream("d:\\file2.txt"); 
   sis=new SequenceInputStream(f1,f2);
   out=new FileOutputStream("d:\\file3.txt");
   byte[] buf=new byte[1024];
   int len=0;
   while((len=sis.read(buf))!=-1){
    out.write(buf,0,len);
    out.flush();
   }
  }
  catch(FileNotFoundException e1){
   e1.printStackTrace();
  }
  finally{
   try{
    if(sis !=null)
     sis.close();
   }
   catch(IOException e){
    e.printStackTrace();
   }
   try{
    if(out!=null)
     out.close();
   }
   catch(IOException e){
    e.printStackTrace();
   }
  }
 }
 public static void main(String[] args) throws IOException{  
  f();
 }
}


 十二、文件的切割

前面学过了文件的合并,下面这个是文件的切割,文件的切割没有什么技术含量,用已经学过的知识点就可以实现这个功能,也就是将“字符”或者是“字节”一段一段的进行读取然后另存就可以了,下面是小代码示例:

FileInputStream fis=new FileInputStream("d:\\Total.txt");		//关联Total.txt文件
		FileOutputStream fos=null;			//输出文件不能确定,初始化为空
		byte[] buf=new byte[4];			//一次分割4个字节
		int length=0;
		int counter=1;
		while((length=fis.read(buf))!=-1){
			fos=new FileOutputStream("d:\\"+(counter++)+".txt");//创建了“流对象”,但是没有进行操作呢,下面就是写入操作,将上面定义的流对象进行写入操作。
			fos.write(buf, 0, length);
			fos.close();
		}
		try{
			if(fis!=null)
				fis.close();
		}
		catch(IOException e){
			throw e;
		}


 

十二、File文件类
1、概述:
前面学过了很多的“流”,但是“流”归根到底是对“数据”的一种操作而已,你能对文件进行一定的操作,但是不能设置他的路径什么的。所以引入“File”这个类实现对“文件”和“文件夹”的详细操作。

2、转义字符:
自己知道java中有转义字符,所以在windows平台中,如果使用的是一个目录的话,比方说使用是:“c:\\dd\\asd\\谷歌”,使用的是“双斜杠”,但是到了linux平台中,就可能出现错误,那么怎样解决这样的问题的呢?在File类中有一个方法是separator(),可以使用File类创建的对象直接使用这个方法产生一个分隔符,从而实现跨平台的使用。

3、File的创建和删除:
(1)、File创建:创建就基本不用说了,File类创建对象的时候,后面的String类型的字符串就是文件的路径和文件名,然后调用createNewFile()方法进行文件的创建了。创建文件夹的时候,使用mkdir()方法,可以创建简单的目录结构。使用mkdirs()方法,可以创建多级目录结构。
(2)、FIle删除:删除就是使用的是delete()方法,实际上“删除”有两种方法,一种是delete(),一种是deleteOnExite()方法,这两种删除方法有什么不同呢?delete()方法直接删除文件,返回的是boolean。deleteOnExite()方法退出虚拟机的时候删除文件,有点像“临时文件的删除一样”。
下面是一个删除文件和文件夹的小程式

public class FilesOrFolderDeleteDemo {
 /**
  * 本例子用来演示怎样删除多级目录,使用的是“递归调用”的方法。
  */
 public static void delete(File dir){
  File[] files=dir.listFiles(); //返回的是一个数组
  for(int i=0;i<files.length;i++){
   if(files[i].isDirectory()){ //判断是否是目录
    delete(files[i]); //调用自身的编程技巧
   }
   else{
    System.out.println(files[i].toString()+":  删除成功!");//删除连带输出文件内容
    files[i].delete(); //这个是删除的文件夹中的文件
   }
  }
  System.out.println(dir+"::dir::"+dir.delete());//这个是将整个文件夹删除
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  File f=new File("E:\\test");
  delete(f);
 }

}


4、File获取文件信息:
所谓获取,就是将文件的属性获取,然后进行相应的操作。获取file文件类创建的对象的方法如下:
getName();  //获取文件名称
getPath();   //获取抽象路径名的字符串形式
getParent();  //获取文件的父目录
getAbsolutePath();  //获取文件的绝对路径
long lastModified();  //获取文件最后一次
long length();  //获取文件的大小长度

5、File判断文件内容:
boolean exists():文件是否存在。
boolean isFile():判断是否是文件。
boolean isDirectory():判断是否是一个目录。
boolean isHidden():判断文件是否是隐藏的。
boolean isAbsolutePath():判断文件的绝对路径。

十二、配置文件
1、小知识:
Properties是一个子类,是hashtable集合的子类,也就是说,它具备map集合的特点,而且它里面存储的键值对都是“字符串”!是“集合框架”中和io技术相结合的集合容器。该对象的特点是:可以用于“键值对”形势的配置文件。那么在加载数据的时候,需要对数据有固定的格式:键=值。

2、配置文件:
(1)、java常用的配置文件有xml和property,学过xml文件知道里面都是一些标记,property里面都是些键值对,就是“什么等于什么”。
(2)、从文件结构来说:xml文件会更加强大一些,因为里面可以存放比较复杂的内容,property里面只能存放的是一个一个的键值对,存放的内容是相当的有限的!
(3)、自己先要知道的是:现在先学会property配置文件的协作方式,也就是.ini后缀的配置文件,里面放满了键值对。
(4)、因为Properties类是Map类的子类,所以也是一个集合,所以往后,只要用到向“配置文件”中写入数据的程序,不用考虑,就用Properties类创建集合对象!!!!因为Properties类里面有很多比较“牛掰”的方法 ,可以参考使用!!

3、著名配置文件dom4j:
dom4j是一个java的XML API文档,类似于jdom,用来读取XML文档的。dom4j是一个非常优秀的java Xml  API文档,具有性能优异,功能强大,极端易用性的特点,同时是一款开放源代码的软件,可以在SourceForge上找到他。dom4j是相当的出色的软件,你可以看到现今越来越多的java软件都在使用dom4j来读写XML配置文件,特别值得一提的是连Sun公司的JAXM也在使用dom4j。所以dom4j成为了一款越来越受欢迎的jar包,也是必不可少的一个配置XML配置文件的jar包,Hibernate用它来读取配置文件。

4、获取web工程位置:

(1)、javaWeb的时候,api文档提供有方法,可以得到web工程在硬盘上的具体位置,这个方法就是getRealPath(),只有这一条路!

(2)、框架的配置文件都是放在calsspath指定的目录文件下面。里面就是用的类加载器加载的这些配置文件。

 

十三、对象序列化
1、概述:
刚开始的流对象中“流动”的都是一般的数据流,“对象的序列化”是将对象添加到“流”中,然后进行相应的操作。

2、格式:
ObjectInputSteam(对象的读入流)
ObjectOutputStream(对象的输出流)
上面这两个类创建的对象可以将“流”作为参数传进来,方法中有readObject(对象)、writeObject(对象),可以看到方法里面的参数是对象类型的,对象里面还可以有自己的参数,对象可以是某个外面的类创建的对象,相当具有扩展性。

3、注意的几件事情:
(1)、序列中一个比较重要的概念是UID(序列号),UID(序列号)不是随机产生的,而是根据程序自身的内容进行判定。
(2)、抛出异常的时候如果出现错误,记得将异常的范围变大,这样出现异常的情况就会减少(实际上是废话)。
(3)、UID序列号,如果关联的几个程序之间编译时间不相同的话,或者是出现使用private修饰的话,可以自己手动指定一个UID序列号,具体格式可以参考API文档,下面列举一个格式自己参考:public static final long serialVersionUID=42L;(可以将前面的public省去)
(4)、静态是不能被序列化的!因为静态是在方法区中,但是非静态的是在堆内存中!
(5)、最重要的一点!自己定义的需要添加到“流”中的对象,需要进行序列化,这个时候就应该使用implemens执行Serializable“序列化”接口!这是必须做的一步!

 

4、具体小程式:

class People implements Serializable{ //执行Serializable接口
 String name;
 int age;
 People(String name,int age){
  this.name=name;
  this.age=age;
 }
 public String toString(){ //转换成String类型
  return name+":"+age;
 }
}
public class ObjectInputStreamDemo {
 public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
  writeObj();
  readObj();
 }
 public static void readObj() throws FileNotFoundException, IOException, ClassNotFoundException{
  ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:\\我好.txt"));
  People p1=(People)ois.readObject();
  People p2=(People)ois.readObject();
  System.out.println(p1);
  System.out.println(p2);
 }
 public static void writeObj() throws FileNotFoundException, IOException{
  ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("d:\\我好.txt"));
  oos.writeObject(new People("妈妈",22)); //添加多少对象,输出就会多少对象
  oos.writeObject(new People("东东",22));
  oos.close();
 }
}


 

 

5、小总结:
说白了,这两个类创建的对象也就是“流对象”,但是就是因为里面有两个可以操作“对象”的方法writeObject()和readObject()方法让这两个类看着不是太寻常,因为这两个类中的方法可以让“对象作为参数”。

十四、管道流
1、概述:
“管道流”包含“管道字节流”和“管道字符流”,“管道字节流”中包含两个作用类“PipedInputStream”和“PipedOutputStream”,“管道字符流”中包含两个作用类“PipedReader”和“PipedWriter”。配对的“管道流”中的两个类,分别管理“读取”和“写入”。这两个“流”进行关联的时候,需要使用一个方法,connect()方法。比方说PipedInputStream创建的对象in想要和PipedOutputStream创建的对象out进行关联的话,需要使用的是:in.connect(out);进行关联。也就是说“管道流”将“读取”和“写入”的操作封装到了一个过程中,考虑到读取写入的同步性,而使用线程,“读取”和“写入”需要相互进行阻塞等待,如果没有“写入操作”就没有“读取操作”,就是说没有原因,就没有结果。相互进行等待。话说这两个类具有相当高的能力了,可以直接将两个不太相干的流对象进行关联,使之产生关系!

 

2、管道字节流示例:
作用类是PipedInputStream和PipedOutputStream(作用字节流)

class Read implements Runnable{ //如果是”线程类“,那么常用的是执行Runnable接口复写run方法。
 private PipedInputStream in;
 Read(PipedInputStream in){
  this.in=in;
 }
 public void run() {
  try{
   byte[] buf=new byte[1024];
   int len=in.read(buf);
   String s=new String(buf,0,len);
   System.out.println(s); //输出到控制台
   in.close();
  }catch(IOException e){
   throw new RuntimeException("管道读取流失败");
  }
 } 
}
class Write implements Runnable{
 private PipedOutputStream out;
 Write(PipedOutputStream out){
  this.out=out;
 }
 public void run() {
  try{
   out.write("你好,很高兴认识你".getBytes()); //向管道中写入一字符串,然后转换成了byte字节类型
  }catch(IOException e){
   throw new RuntimeException("管道输出流失败");
  }
 }
}
public class PipedStreamDemo {
 public static void main(String[] args) throws IOException {
  PipedInputStream in=new PipedInputStream(); //创建管道读取和写入流
  PipedOutputStream out=new PipedOutputStream();
  out.connect(in);  //管道流的读取和写入进行了关联connect
  Read read=new Read(in);
  Write write=new Write(out);
  Thread thread01=new Thread(read); //将方法加载到线程中
  Thread thread02=new Thread(write);
  thread01.start(); //线程启动
  thread02.start();
 }
}


 

 

 

3、管道字符流示例:
作用类是:PipedReader和PipedWriter(作用字符流)

class Writer implements Runnable{
 private PipedWriter out;
 Writer(PipedWriter out){
  this.out=out;
 }
 public void run() {
  try{
   out.write("你好,很高兴认识你");
  }
  catch(IOException e){
   throw new RuntimeException("管道输出流失败");
  }
 }
}
class Reader implements Runnable{
 private PipedReader in;
 Reader(PipedReader in){
  this.in=in;
 }
 public void run() {
  try{
   char[] length=new char[1024];
   int len=in.read(length);
   String s=new String(length,0,len);
   System.out.println(s);
   in.close();
  }
  catch(IOException e){
   throw new RuntimeException("管道读取流失败");
  }
 }
 
}
public class PipedStreamDemo1 {
 public static void main(String[] args) throws IOException {
  PipedReader in=new PipedReader();
  PipedWriter out=new PipedWriter();
  out.connect(in);
  Writer w=new Writer(out);
  Reader r=new Reader(in);
  Thread thread1=new Thread(w);
  Thread thread2=new Thread(r);
  thread1.start();
  thread2.start();
 }
}


 

 

4、”管道字节流“和”管道字符流“的区别:
可以看到,上面的两个小程式基本上一样,只不过在读取的时候”管道字节流“使用的是”固定长度的字节数组“。”管道字符流“使用的时”固定长度的字符数组“!!!剩下都是一样的。

十五、随机读取流文件RandomAccessFile
1、概述:
RandomAccessFile“随机读取流文件”。根据写作格式可以看出:该类不能算是IO体系中的子类,因为它直接继承的是Object类,所以“比较特殊”。但它又是IO包中的成员,因为它同时具备“读”和“写”的操作功能(这一点相当的重要!)内部封装了一个”数组“,而且可以通过指针对数组的元素进行操作。可以通过getFilePointer获取指针的位置。同时可以通过seek改变指针的位置。其实完成读写的原理就是内部封装了字节输入流和输出流。通过构造函数可以看出,该类只能操作文件(不足之处)而且操作文件还有模式:只读:r     读写:rw。
(1)、如果模式为只读 r,RandomAccessFile类创建对象的时候,不会创建“文件”。会去读取一个已经存的文件,如果该文件不存在,会出现异常。
(2)、如果模式为rw,RandomFile类创建对象的时候,如果操作的文件不存在,那么就会创建文件,并且文件如果存在的话,也不会覆盖。

2、RandomAccessFile常用方法:
seek()方法,可以调整对象中指针的位置。
skipBytes(int m)方法,跳过指定大小的字节数。

3、作用:
RandomAccessFile类创建的流对象十分的常用!自己知道“迅雷下载”的时候,就是用的多线程下载文件的,这个类可以实现多线程的流对象,所以十分的常用。可以模拟“迅雷下载”这个过程。

 

4、小示例:

public class RandomAccessFileDemo {
 //这个方法是一个写操作,向文件”我是好人.txt“文件中写入一些String类型的name,和int类型的数字,转换成文本的时候会被翻译成字母或者各种符号。
 public static void writeFile() throws IOException{
  RandomAccessFile raf=new RandomAccessFile("d:\\我是好人.txt","rw");
  raf.write("马济亨".getBytes()); //想要写入String类型内容,转换成byte类型
  raf.writeInt(97); //将int类型转换成字符类型
  raf.write("马济东".getBytes());
  raf.writeInt(67);
  raf.close();
 }
 //这个方法是一个读操作,读取文件”我是好人.txt“中的属性为String的name和以int类型的形式读取age,然后再控制台输出。
 public static void readFile() throws IOException{
  RandomAccessFile raf=new RandomAccessFile("d:\\我是好人.txt","r");
  byte[] buf=new byte[4];
  raf.read(buf);
  long n=raf.getFilePointer();
  String name=new String(buf);
  int age=raf.readInt();
  System.out.println("name"+name);
  System.out.println("age"+age);
  System.out.println(n);
  raf.close();
 }
 public static void main(String[] args) throws IOException {
  readFile();
 }
}


 

 

十六、基本数据类型流DataStream
1、概述:
上面的DataStream是自己编造出来的,实际上是没有的,意在用来概括其中的两个基本数据类型流对象DataInputStream和DataOutputStream。自己知道,Java中有很多的基本数据类型,比方说int、double、long、boolean、float之类的基本数据类型(所以这个类的使用频率还是挺高的)java定义了一个操作这些基本数据类型的“流对象”类创建的对象可以进行操作。

2、创建方法:
DataInputStream in=new DataInputStream (new FileInputStream("nihao.txt"));
DataOutputStream out=new DataOutputStream(new FileOutputStream("nihao.txt"));

 

3、小程式演示:

public class DataStreamDemo {
 public static void main(String[] args){
  DataInputStream in = null;
  try {
   in = new DataInputStream(new FileInputStream("d:\\学生信息.txt")); //使用“文件字节流”读取文件
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  }
  DataOutputStream out = null;
  try {
   out = new DataOutputStream(new FileOutputStream("d:\\学生信息2.txt")); //使用“文件字节流”写入文件中
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  } 
  int len=0;
  try {
   while((len=in.read())!=-1){
    out.write(len);
   }
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}


 

 

4、总结:
DataInputStream和DataOutputStream类中还封装了很多的方法,自己参看api文档可以详细了解其中的乐趣,自己一定要学会查看api文档,因为这是一个从“java编码员”迈向“java程序员”的重要的一步!!!

十七、字节数组流ByteArrayInputStream&ByteArrayOutputStream
1、概述:
见名知意,这是一个操作字节数组的类。自己知道,先开始学习的各种流对象里面都是封装了字节数组的,为什么要单独进行讲解呢?这个类有点儿特殊,类创建的对象操作的内容不是文件,不是键盘,而是内存中的“字节数组”!

2、注意:
因为这两个流对象都操作的是字节数组,因为没有使用”系统资源“,所以不用进行close关闭操作的。(只有使用了系统资源才必须要关闭资源的!)

3、”流“操作的规律:
(1)、源设备:键盘System.in  硬盘FileStream  内存ArrayStream
(2)、目的设备:控制台System.out  硬盘FileStream  内存ArrayStream

 

4、小程式演示:

public class ByteArrayStreamDemo {
 public static void f(){
  String s="我是一个好孩子"; 
  ByteArrayInputStream bis=new ByteArrayInputStream(s.getBytes());
  ByteArrayOutputStream bos=new ByteArrayOutputStream();
  int len=0;
  while((len=bis.read())!=-1){
   bos.write(len); //这里的write操作是将len内容写入到了系统内存当中
   String message=bos.toString();
   System.out.println(message);
  }
  int size=bos.size(); //返回字节数组的size大小
  System.out.println(size);
 }
 public static void main(String[] args) {
  f();
 }
}


 

 

5、小扩展:
(1)、既然有字节数组,那么相应的就有“字符数组”了 ,CharArrayReader    CharArrayWriter类分别创建的是字符数组的“读取"和"写入流"。
(2)、既然有字符数组,那么相应的就有”字符串“数组了,StringReader   StringWriter类分别负责创建的是字符串的”读取“和”写入流“。
上面两个类的使用方法和ByteArrayInputStream和ByteArrayOutputStream的使用方法基本一致!!!后面就不一一介绍。

6、注意:
其中的ByteArrayOutputStream操作中的write是将内容写入到系统内存当中,不是一个硬盘上的什么文件当中!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值