java 自学日志【十三】---IO流

本文详细介绍了Java中的IO流操作,包括字符流和字节流的基本概念、常用类及其使用方法。探讨了文件读写、复制及缓冲区的应用,并介绍了装饰设计模式在自定义缓冲区中的运用。
IO流
Java中的IO操作API封装在java.io包中,用来处理设备间的数据传输。按流向可分为输入流和输入流,按操作的数据可分为字节流和字符流。
字符流操作的是字符,字节流操作的是一个个的字节。
最早先有有字节流,后来为了方便人们操作,将字节编码制成了编码表,由此在字节流的基础上利用编码表产生了字符流。
Java的IO流中主要由四个基类:
Writer,Reader,它俩是所有字符流的基类;
InputStream,OutputStream,它俩是所有字节流的基类。

字符流体系结构:
Writer 字符输出流
 |----OutputStreamWriter:字符流通向字节流的桥梁,可以指定编码。
 |----FileWriter:文件写入流,会创建一个文件,通过构造函数定义是续写还是覆盖。
 |----BufferedWriter:字符写入流,定义了缓冲区,可以提高效率;缓冲区为字符数组,可定义缓冲区大小,默认缓冲区大小为8k。
代码:
/* 
演示对已有文件写入的基本操作、异常处理机续写方式。 
*/  
  
import java.io.*;  
  
class FileWriterDemo  
  
{  
     public static void main(String[] args)  
     {  
        FileWriter fw = null;//这句要定义在异常处理外部,提升其作用域,否则在finally中读不到fw。  
        try  
        {  
            //文件续写:传递一个true参数,代表不覆盖一个已有的文件,并在已有文件的末尾处进行数据的续写。  
  
            fw= new FileWriter("demo.txt",true);  
  
            /* 
            fw= new FileWriter("demo.txt"); 
 
            这句和注释外那句的区别: 
 
            创建一个FileWriter对象,该对象已被初始化就必须要明确要被操作的文件。 
 
            而且该文件会被创建到指定目录下。如果该目录下已有同名文件,则同名文件将被覆盖,。 
 
            其实该步就是在明确数据要存放的目的地。 
	    write可以写入字符、字符数组(或字符数组的一部分)、字符串(或字符串的一部分)。 
 
            注意:windows中,换行用"\r\n"两个字符表示,而Lunix中用"\n"一个字符表示。 
 
            所以,若只用“\n”,使用windows自带的记事本不能看到换行效果。 
 
            */          
  
            fw.write("nihao\r\nxiexie");             
  
            //刷新流对象中的缓冲中的数据。将数据从内存中刷到目的地中。  
  
            fw.flush();           
        }  
        catch (IOException e)  
        {  
            System.out.println(e.toString());  
        }  
        finally  
        {  
            try  
            {  
                //这里必须判断一下,因为如果fw的初始化没有成功,会抛出空指针异常。这里的fw操作是针对fw.write()方法抛出异常后进行的处理。  
  
                if(fw!=null)  
  
                    //IO底层调用了系统的资源,IO流建立成功使用结束时,一定要释放资源。fw.close()之前,会先调用flush方法,  
  
                    //这两个方法的区别是::flush刷新后,流可以继续使用;close刷新后,会将流关闭,若再次使用流会抛出异常。  
  
                    //注意:如果没有调用flush也没用调用close方法,那么磁盘文件中看不到写入效果。                  
  
                    fw.close();  
            }  
            catch (IOException e)  
            {  
                System.out.println(e.toString());  
            }  
        }         
      }  
 }  
Reader 字符读取流
|----InputStreamReader:字节流通向字符流的桥梁,可以指定编码。
|----FileReader:文件读取流,文件需存在,否则抛出异常。      
|----BufferedReader:字符读取流,定义了缓冲区,可以提高效率;缓冲区为字符数组,可定义缓冲区大小,默认缓冲区大小为8k。
Reader
文件读取的第一种方式:read()方法,一个字符一个字符的读取,返回值是字符的编码值,如果到流结束处,则返回-1,它能读取到换行符。

代码:
import java.io.*;  
  
import static java.lang.System.*;  
  
class FileReaderDemo  
{    
   public static void main(String[] args)throws IOException  
   {    
      //创建一个文件读取流对象,和指定名称的文件相关联。  
  
      //要保证该文件是已经存在的,如果不存在,会发生异常:FileNotFoundException。  
  
      FileReader fr = new FileReader("demo.txt");  

      int ch =0;  
  
      while((ch=fr.read())!=-1)  
      {   
         out.println((char)ch);    
      }   
      fr.close();    
   }  
  
} 

文件读取的第二种方式:read(char[]ch),把读取的字符放入字符数组中,返回的读取的字符个数,如果到流结束处,则返回-1;它能读取到换行符;
代码:
import java.io.*;  
import static java.lang.System.*;    
class FileReaderDemo2   
{    
   public static void main(String[] args)throws IOException    
   {    
      FileReaderfr = new FileReader("demo.txt");  
 
      //定义一个字符数组(也可称为缓冲区),用于存储读到的字符。  
  
      //该reader(char[])返回的值是读到的字符个数。  
  
      char [] buf =new char[1024];//通常情况下数组的长度会定义为1024的整数倍  
  
      int num =0;  
  
      while((num=fr.read(buf))!=-1)  
      {    
         out.println("num="+num+"...."+new String(buf,0,num));  
  
         //为什么不用new String(num),因为如果最后一次读取的个数如果不满足1024个,那么会把上次取出而本次没有覆盖掉的字符也打印出来。    
      }  
      fr.close();    
   }  
} 

练习:文件复制
将c盘的一个文本文件复制到D盘。 
复制原理:其实就是C盘下的文件数据存储到D盘的一个文件中。 

步骤:  

1.在D盘创建衣蛾文件,用于存储C盘文件中的数据。 
 
2.定义读取流和C盘文件关联。 
 
3.通过不断的读写完成数据存储。 
 
4.关闭资源。
代码:
import java.io.*;    
class CopyTest    
{  
   public static void main(String[] args)throws IOException    
   {    
      //myCopy("B:\\andy.txt","D:\\tony.txt");  
      //copy_1();  
      copy_2();   
   }  
   public static void myCopy(String sour,String dest)throws IOException  
   {  
      FileReader fr = new FileReader(sour);  
  
      FileWriter fw = new FileWriter(dest);  
  
      char[] buf =newchar[1024];  
  
      int num =0;  
  
      while((num =fr.read(buf))!=-1)    
      {  
         fw.write(buf,0,num);         
      }  
      fr.close();  
      fw.close();    
   }  
  
   public static void copy_2()  
   {  
      FileWriter fw = null;  
  
      FileReader fr = null;   
      try    
      {    
         fw= new FileWriter("D:\\tony.txt");  
          
         fr= new FileReader("B:\\andy.txt");  
  
         char [] buf =new char[1024];  
  
         int len =0;  
  
         while((len=fr.read(buf))!=-1)  
         {  
            fw.write(buf,0,len);  
  
            //其实fw里也有一个缓冲区,用于存放要写的内容,当缓冲区满了,自动调用底层方法,写入文件中。    
         }  
      }  
      catch (IOException e )  
      {  
         throw new RuntimeException("读写失败");  
      }  
      finally    
      {    
         if(fr!=null)    
	 try    
         {  
	    fr.close();    
         }  
	 catch (IOException e )  
         {  
         }  
         if(fw!=null) 
         try
         {  
            fw.close();
         }  
         catch (IOException e )  
         {  
         }  
      }    
/* 
      finally  
      {         
         try 
         { 
            if(fr!=null) 
 
            fr.close(); 
         } 
         catch(IOException e ) 
         { 
 
         } 
         finally 
         {  
            try 
            { 
                if(fw!=null) 
 
                fw.close();              
            }  
            catch(IOException e ) 
            { 
 
            } 
         }
      }  
*/     
   }   
   public static void copy_1()throws IOException  
   {  
      //创建目的地  
      FileWriter fw = new FileWriter("c:\\tony.txt");    
      //与已有的文件关联  
      FileReader fr = new FileReader("b:\\andy.txt");  
      int ch =0;  
      while((ch = fr.read())!=-1)  
      {    
         fw.write(ch);  
      }  
      fw.close();  
  
      fr.close();    
   }  
}  

字符流缓冲区
缓冲区的出现提高了对数据的读写效率,它要结合流才可以使用,它在流的基础上对流的功能进行了加强。对应的类:BufferedWriter和BufferedReader。
缓冲区的出现是为了提高流的操作效率而出现的。 
所以在创建缓冲区之前,必须要先有流对象。 
该缓冲区中提供了一个跨平台的换行符。 
newLine(); 
BufferedWriter代码:
import java.io.*;    
class BufferedWriterDemo    
{  
   public static void main(String[] args)throws IOException  
   {  
      //创建一个字符续写入流对象。  
      FileWriter fw = new FileWriter("buf.txt",true); 
   
      //为了提高字符写入效率,加入了缓冲技术。  
  
      //只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。  
      BufferedWriter bufw = new BufferedWriter(fw);//原理是这个对象里边封装了数组。  
      for(int x = 1 ;x < 5; x++)    
      {  
         bufw.write("abcde"+x);  

         bufw.newLine();  
  
         bufw.flush();  
      } 
      //记住,只要用到缓冲器,就要记得 刷新。  
  
      //bufw.flush();  
  
      //其实关闭缓冲区,就是在关闭缓冲区中的流对象。  
  
      bufw.close(); 
   }  
} 

BufferedReader代码:
import java.io.*;    
class BufferedReaderDemo    
{  
   public static void main(String[] args)throws IOException    
   {  
      //创建一个读取流对象和文件想关联。  
      FileReader fr = new FileReader("buf.txt");    
      //为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲对象的构造函数。  
  
      //默认的缓冲区char[]数组大小为8k。  
      BufferedReader bufr = new BufferedReader(fr);    
      String line = null;  
      while((line=bufr.readLine())!=null)  
      {  
         System.out.println(line);  
      }    
      bufr.close();     
   }  
} 

利用缓冲区拷贝文件
import java.io.*;  
  
class CopyTextByBuf    
{  
   public static void main(String[] args)//throws IOException  
   {    
      BufferedReader bufr = null;//如果不先定义为空,finally中就无法访问读写缓冲区的引用  
  
      BufferedWriter bufw = null;    
      try  
      {  
         bufr= new BufferedReader(new FileReader("BufferedReaderDemo.java"));  
  
         bufw= new BufferedWriter(new FileWriter("B:\\BufferedReaderDemo.java"));  
  
         String line = null;//line此时即是中转站                    

	 while((line =bufr.readLine())!=null)  
         {//readLine只返回该行的有效内容,并不返回换行符,所以写入的试试,要加换行符或调用newLine方法。    
            bufw.write(line);  
  
            bufw.newLine();  
  
            bufw.flush();    
         }            
      }  
      catch (IOException e)  
      {  
         throw new RuntimeException("读写失败");  
      }  
      finally  
      {          
         try    
         {  
            if(bufr!=null) 
	    bufr.close();  
         }  
         catch (IOException e)  
         {  
            throw new RuntimeException("读取关闭失败");    
         }       
         try    
         {  
            if(bufw!=null) 
	    bufw.close();  
         }  
         catch (IOException e )  
         {    
            throw new RuntimeException("写入关闭失败");    
         }  
      }  
   }    
}

readLine方法原理:
读一行,获取读取的多个字符,最终都是在硬盘上一个一个读取,所以最终使用的还是read()方法一次读取一个的方法
(因为只有一个一个读取,它才能筛选判断出换行符,判断一行是否到结尾)。
只不过它不是读一个就写一个,而是读一行就放入缓冲区,读完一行再,再刷新缓冲区,把缓冲区的数据写入硬盘文件中。
readLine返回的是字符串,如果读到末尾,返回值为null;这是与read不同的地方。

自定义读取流缓冲区与装饰设计模式
自定义缓冲区,模拟BufferedReader,而BufferedReader用到了装饰设计模式,所以要先明白装饰设计模式。

装饰设计模式
装饰设计模式:当想要对已有的对象进行功能增强时,可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
装饰类的特点:装饰类通常会通过构造方法接受被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
装饰和继承的区别:
一开始我们定义一个读取数据的类MyReader,其基本体系如下:
MyReader    
         |---MyTextReader  
  
         |---MyMediaReader  
  
         |---MyDataReader  
过一段时间,出现了新的技术(缓冲区),如果是使用继承,那么其他体系结构如下:
MyReader   
         |---MyTextReader  
  
                   |---MyBufferedTextReader  
  
         |---MyMediaReader  
  
                   |---MyBufferedMediaReader  
  
         |---MyDataReader  
  
                   |---MyBufferedDataReade  
发现其体系臃肿,而且每定义一个新的子类都要再定义一个新的基于它的MyBuffered子类。
那么可以考虑在设计体系时,将实现新技术的类与之前类体系的关系由继承关系转为组合,使用装饰模式。
classMyBufferedReader    
{  
    MyBufferedReader(MyBufferedTextReader text)  
    {}  
    MyBufferedReader(MyBufferedMediaReader media)    
    {}    
}  

上边这个类扩展性很差,因为每增加一个子类,都要修改构造函数,那么找到其参数的共同类型,通过多态的形式,提高扩展性。这个类设计如下:
classMyBufferedReader extends MyReader    
{    
    MyBufferedReader(MyReader r ) 
    {   
    }    
}  
最终体系如下: 
MyReader    
    |---MyTextReader              
  
    |---MyMediaReader          
  
    |---MyDataReader  
  
    |---MyBufferedReader  

装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。
装饰类因为是增强已有对象,具备的功能和已有对象的功能是相同的,只不过是提供了更强的功能所以装饰类和被装饰类通常是都属于一个体系中。
设计时,可以写继承,但如果过于臃肿,可以考虑采用装饰设计模式。
明白了BufferedReader类中特有方法readLine的原理后就可以自定义一个类中包含一个功能和readLine一致的方法,来模拟下BufferedReader .
import java.io.*;  
import java.util.*;  
class MyBufferedReader    
{    
    private FileReader fr = null;  
  
    MyBufferedReader(FileReader fr)    
    {  
       this.fr =fr;  
    }  
    //这里方法处理异常时为什么是抛而不是try?因为这个问题,你没法解决,是产生在调用你功能的代码中,所以要抛而不能try。  
    public String myReadLine() throws IOException  
    {    
       //定义一个临时容器,原BufferedReader封装的是字符数组。  
  
       //为了演示方便,定义一个StringBuilder容器,因为最终还是要将数组变成字符串。  
  
       StringBuilder sb = new StringBuilder();  
  
       int ch =0;  
  
       while((ch =fr.read())!=-1)    
       {  
          if(ch=='\r')  
	     continue ;           
          if(ch=='\n')    
             return sb.toString();  
          else    
             sb.append((char)ch);    
       }  
       //这里为什么要加一个判断?因为如果文件内容结尾处没有回车换行,没有这句判断,会丢失最后一行。  
       if(sb.length()!=0)  
          return sb.toString();    
       return null;    
       //return(sb.length!=0)?sb.toString():null;上边两句可以这样简写。  
    }  
    public void myClose()throws IOException  
    {  
       fr.close();  
    }  
} 
LineNumberReader也是一个包装类,它在BufferedReader的基础上增加了一个记录行号的功能,而记录行号是在readLine方法中操作的,所以它继承了BufferedReader并复写了readLine方法,同时增加了getLineNumber和setLineNumber方法。
代码:
import java.io.*;    
class LineNumberReaderDemo  
{  
    public static void main(String[] args)throws IOException  
    {  
       FileReader fr = new FileReader("PersonDemo.java");  
       LineNumberReader lnr = new LineNumberReader(fr);  
       String line = null;  
       lnr.setLineNumber(100);  
       while((line=lnr.readLine())!=null)  
       {  
          System.out.println(lnr.getLineNumber()+line);  
       }        
       lnr.close();  
    }  
} 
自定义带行号的缓冲区对象
因为之前自定义过缓冲区对象,可以通过继承这个缓冲区对象来实现。
import java.io.*;    
class MyLineNumberReader extends MyBufferReader    
{   
    Reader r = null;  
    intlineNumber = 0;  
    MyLineNumberReader(Reader r)  
    {  
       super(r);  
    }  
    public String myReadLine()throws IOException  
    {          
       lineNumber++;         
       return super.myReadLine();    
    }     
    public void setLineNumber(int lineNumber)  
    {  
       this.lineNumber = lineNumber;    
    }  
    public int getLineNumber()  
    {  
       return lineNumber;  
    }  
}
字节流
字节流的两个基类是InputStream和OutputStream,相应的缓冲区是BufferedInputStream和BufferedOutputStream。
它的操作与字符流类似,可以参与字符流的定义、读取、写入、处理异常的格式。
只不过是处理的数据不同,因为对于非字符的数据,比如图片、视频、音频文件(例如mp3)等,这些文件只能用字节流对之进行操作。
字节流的写操作,不用刷新动作,也能写入目的地,这是为什么?
字符流底层用也的字节流,因为涉及编码,比如写一个汉字,它有两个字节,你不能读一个字节就写入,而要等到两个字节都读取完后,才写入,
所以必须要有flush机制。而字节流,不涉及编码,可以读一个就写一个而不出现问题,所以不用刷新也能写入。
字节流特有的读取方式:available()方法可以知道文件的所有字节数,以此可以定义一个刚
刚好的字节数组,只用读取一次,然后写入来完成文件复制。但注意:如果字节说过大,会造成内存溢出。
字节流读写示例:
import java.io.*;    
class  FileStreamDemo    
{    
    public static void main(String[]args) throws IOException    
    {  
//     writeFile();  
       readFile_3();    
    }    
    //第三种d读取方式:字节流特有的读取方式,使用available()方法。    
    public static void readFile_3() throws IOException    
    {    
       FileInputStream fis = new FileInputStream("fos.txt") ;  
  
       //int num = fis.available();  
  
       byte [] buf =new byte[fis.available()];//定义一个刚刚好的缓冲区,不用再循环了。  
  
      //局限:但是这个方法慎用,如果读取的资源过大,如2G,而电脑内存只有1G,则会出现内存溢出。  
       fis.read(buf);  
  
       System.out.println(new String(buf));  
  
       fis.close();    
    }  
  
    //第二种读取方式:利用字符数组作为缓冲区,读取。  
    public static void readFile_2() throws IOException  
    {  
       FileInputStream fis = new FileInputStream("fos.txt") ;  
  
       byte[] buf = new byte[1024];  
  
       int len = 0;  
  
       while((len =fis.read(buf))!=- 1)  
       {  
           System.out.println(newString(buf,0,len));  
       }    
       fis.close();  
    }  
    //第一种读取方式:一个字节一个字节的读取。  
    public static void readFile_1() throws IOException  
    {  
       FileInputStream fis = new FileInputStream("fos.txt") ;  
  
       int ch = 0;  
  
       while((ch = fis.read())!=- 1)  
       {  
           System.out.println((char)ch);  
       }    
       fis.close();  
    }  
    public static void writeFile() throws IOException  
    {  
       FileOutputStream fos =new FileOutputStream("fos.txt");  
  
       fos.write("你好吗".getBytes());  
  
       //与字符流Writer相比,没有flush  
  
       fos.close();    
    }    
}
练习:复制图片(字节文件输入输出流)
注意:字符流也可以复制图片,但是它读取一段字节后就会查表进行编码,
如果表中有对应的编码值那么没有问题,如果没有对应的编码,则会在编码表的未知区域找一个类似的编码进行替代,
这时就改变了原来的字节,导致图片格式不对,复制的图片无法打开。

需求:复制一个图片。
思路:
1.定义一个字节读取流对象和图片关联
2.定义一个字节写入流对象,用于创建和存储图片。
3.通过循环读写,完成数据的存储。
4.关闭资源。
import java.io.*;    
class CopyPicDemo   
{    
    public static void main(String[]args)  
    {    
       copyPic("pic.jpg","D:\\pic.jpg");  
    }  
    public static void copyPic(Stringsour,String des)   
    {  
       FileInputStream fis = null;  
  
       FileOutputStream fos = null;     
  
       try  
       {  
           fis = new FileInputStream(sour);  
  
           fos = new FileOutputStream(des);   
  
           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("读取关闭失败");  
           }  
           finally  
           {  
              try  
              {  
                  if(fos!=null)  
  
                     fos.close();  
              }  
              catch (IOException e)  
              {    
                  throw new RuntimeException("写入关闭失败");  
              }  
           }            
       }    
    }  
}

复制Mp3:
import java.io.*;
class CopyMp3 
{
	public static void main(String[] args) throws IOException 
	{
		long s=System.currentTimeMillis();

		copy_2();
		
		long e=System.currentTimeMillis();
		
		System.out.println((e-s)+"毫秒");

	}
	public static void copymp3() throws IOException
	{
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("三国杀.mp3"));
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("三国杀copy.mp3"));

		int i= 0;
		while ((i=bis.read())!=-1)
		{
			bos.write(i);
		}
		bis.close();
		bos.close();
	}
	//通过自定义的字节流缓冲区完成复制。   
     public static void copy_2() throws IOException  
     {  
        MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("三国杀.mp3"));  
        BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("三国杀copy2.mp3"));  
                
        int by = 0;  
        //System.out.println("第一个字节是:"+bufis.myRead());//结果为-1。   
        
        int len = 0;  
        while((len = bufis.myRead())!= -1)  
        {  
           bufos.write(len);//write其实在做一个强转动作   
           //bufos.flush();//这里不要写flush,否则复制速度会大幅降低。   
           //System.out.println(len);    
        }  
        bufis.myClose();  
        bufos.close();  
     }  
}
/*
两种方法,一种是java提升的缓冲区,一种是自定义的缓冲区。

复制媒体文件要注意的问题:

1.复制MP3因为有可能读取的字节8个1,值为-1,,返回值是int类型,类型提升后结果仍为-1,
这会导致数据并未读完,但myRead()结果却为-1,从而使得复制mp3文件失败。
为避免这种情况,可以采取这种方法,读取的字节类型提升后前24位补的是1,我们让它补0。
怎么补零?类型提升的过程我们是没法控制的,那么可以将提升后的结果&255,
结果就转化成最后一个字节不变,而前三个字节位都变成了0,&255后的值作为read方法的返回值。
这其实也是为什么read()方法的返回值是int而不是byte的原因。

2.字节写入流的write(intb)方法,在写入时,有个截取动作,即把最低8位为保留,而把高24位舍弃。
*/
 
读取键盘录入。 
 
System.out:对应的是标准输出设备,控制台。 
 
System.in:对应的是标准的输入设备,键盘。 

通过键盘录入数据。 
 
当录入一行数据后,就将改行数据打印。 
 
如果录入的数据是over,那么停止录入。
import java.io.*;
class ReadIn 
{
	public static void main(String[] args) throws IOException
	{
		InputStream in = System.in;
		StringBuilder sb = new StringBuilder();

		while (true)
		{
			int i = in.read();
			if (i=='\r')
				continue;
			if (i=='\n')
			{
				String s = sb.toString();
				if ("over".equals(s))
					break;
				System.out.println(s);
				sb.delete(0,sb.length());

              //这里sb缓冲区必须清空,否则会导致两个结果:
	      //1,会被上次敲入的字符又打印一遍;2、导致结束标记失效。
			}
			else
				sb.append((char)i);
		}
	}
}

read()是一个阻塞式的方法,它每次读取一个字节,如果没有数据,它会一直等待。 
1.为什么它读硬盘上的文件文件时,没有阻塞?因为硬盘上的文件就算是空,为0字节,
 
因为文件有结束标记(windows系统给每个磁盘文件都加有结束标记),会返回-1,而不阻塞。 
而在dos命令行窗口下,启动后,既没有数据,也没有结束标记,所以一直阻塞,而“ctrl+c”命令,其实就是输入一个结束标记。 
 
2.为什么dos命令窗口下,只用我敲回车键后,控制台才打印,而不是我录入一个字符它就打印? 
因为,录入的字符,只有在敲回车键后才写入in关联的流里边,这时流里边才有数据,从而被read()方法读取到。 
 
3.为什么我敲入一个字符回车后,却打印出来3个数字? 
因为,windows中,敲回车键,就是加入换行符,而windows中的换行符是用\r\n两个字符表示的,所以会有3个。 
转换流
上边读取键盘录入的代码,与BufferedReader的readLine()方法很相似,那么能不能用readLine()来代替?
因为System.in是字节流,它要使用字符流缓冲区的方法,这就需要使用到两个转换流:InputStreamReader和OutputStreamWriter。
import java.io.*;
class  TransStream
{
	public static void main(String[] args) throws IOException
	{
		//获取键盘录入对象;
		//InputStream in = System.in;

		//将字节流对象 转换成字符流,使用 读取转换流
		//InputStreamReader isr = new InputStreamReader(in);
		
		//创建缓冲区,以便使用缓冲区的readLine方法
		//BufferedReader br = new BufferedReader(isr);
		//以上三步可以简写成:
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		
		//我们还可以利用使用 写入转换流 来将字符流转换成字节流
		BufferedWriter bw= new BufferedWriter(new OutputStreamWriter(System.out));

		String s = null;
		while ((s=br.readLine())!=null)
		{
			if ("over".equals(s)||"OVER".equals(s))//自定义退出语句,也可以使用ctrl+c;
				break;
			//System.out.println(s);
			bw.write(s);
			bw.newLine();//这个换行操作 跨平台;
			bw.flush();//因为用到了缓冲区所以要刷新;
		}
		br.close();
		bw.close();
	}
}

/*
流的操作:
1.将录入信息展示在控制台上,详见 TransStream.java
源:键盘录入,System.in;
目的地:控制台。System.out;

2.将录入信息存储到一个文件中,如将“come on!”存储在“加油.txt”中。
源:键盘录入,System.in;
目的地:加油.txt;

3.需要将一个文件中的数据,打印在控制台上;
源:文件;
目的地:控制台。System.out;
*/
import java.io.*;
class TransStream2 
{
	public static void main(String[] args) throws IOException
	{
		demo2();//先录入内容,存储到文件中
		demo3();//再让文件在控制台上显示
	}
	public static void demo2() throws IOException
	{
		BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(new FileOutputStream("加油.txt")));
		String s = null;
		while ((s=br.readLine())!=null)
		{
			if("over".equals(s)||"OVER".equals(s))
				break;
			bw.write(s);
			bw.newLine();
			bw.flush();
		}
		br.close();
		bw.close();
	}
	public static void demo3() throws IOException
	{
		BufferedReader br =new BufferedReader(new InputStreamReader(new FileInputStream("加油.txt")));
		BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(System.out));
		String s = null;
		while ((s=br.readLine())!=null)
		{
			if ("over".equals(s)||"OVER".equals(s))
				break;
			bw.write(s);
			bw.newLine();
			bw.flush();
		}
		br.close();
		bw.close();
	}
}

流操作的基本规律
IO操作最头痛的是如何其体系中选择需要的类,这个过程可以通过三个步骤来完成:
1.      明确源和目的。
源:输入流,InputStream和Reader;
目的:输出流,OutputStream和Writer
2.      明确操作的数据是否是纯文本。
是:字符流
不是:字节流。
3.      当体系明确后,再明确要使用哪个具体的对象。
通过设备来区分:
源设备:内存、硬盘、键盘
目的设备:内存、硬盘、控制台。
此外:
如果需要提高效率,则换用对应的缓冲区。
如果需要指定编码,则换用对应的转换流。
小知识点:
System类中的setIn()、setOut()、setError()方法,可以改变标准输入流、输出流和错误输出流。
异常信息序列化
import java.io.*;  
import  java.util.*;    
import java.text.*;    
class ExceptionInfo   
{    
    public static void main(String[]args) throws IOException   
    {  
       try  
       {  
           int [] arr = newint[2];  
  
           System.out.println(arr[3]);  
       }  
       catch (Exception e)  
       {  
           try  
           {    
              Date d= new Date();  
  
              SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-DDHH:mm:ss");  
  
              Strings =sdf.format(d);    
  
              PrintStreamps = new PrintStream(new FileOutputStream("exception.log",true));  
  
              ps.println(s);  
  
              System.setOut(ps);  
           }  
           catch (IOException ex)    
           {  
              throw new RuntimeException("日志文件创建失败");  
           }  
           e.printStackTrace(System.out);  
       }  
    }  
}  
log4j :网络上专门用于存储java日志个工具包,里边有很多对象可用。
 
系统信息序列化
import java.util.*;    
import java.io.*;    
class SystemInfo     
{    
    public static void main(String[]args) throws IOException    
    {    
       Properties prop = System.getProperties();    
       //System.out.println(prop);    
       //prop.list(System.out);//不用像在集合时一样,用遍历集合    
        //将属性列表输出到指定的输出流   
       prop.list(new PrintStream("sysinfo.txt"));  
    }  
}

File类
文件是一类事物,它有自己的名字、属性、大小、位置、后缀名等属性信息,那么根据面向对象的思想,就可以把它封装描述为一个类,
这个类就java.io包中的File类。File类是文件和目录路径名的抽象表示形式,它可以方便的对文件与文件夹的属性信息进行操作,也可以作为参数传递给流对象。
它弥补了了流的不足,比如流只能操作数据,而不能操作文件的属性与文件夹。
 
File类的常见操作
1.创建。
  boolean    creatNewFile():重点掌握
  在指定位置创建文件,如果该文件已经存在,在不创建,放回false。和输出流不一样,输出流对象一建立就创建文件,如果
文件已经存在,则会覆盖。
  createTempFile(Stringprefix, String suffix) :创建临时文件 
  boolean   mkdir():创建文件夹。
  bollean   mkdirs():创建多级文件夹。
 
2.删除。
  boolean  delete():删除失败,返回false;
  void   deleteOnExit():
在程序退出时,删除指定文件。比如使用的临时文件,如果出现异常,这句如果放在finally中,
可能出现删除不了的情况(文件正在被其他程序使用),这时就可以用这个方法。    
 
3.判断。
  boolean   exists() :重点掌握 文件是否存在。        
  boolean   isFile():
  boolean  isDirectory();
  boolean   isHidden();
  boolean    isAbsolute();
 
4.获取信息。
  getName();
  getPath();
  getParent();
 
  String  getAbsolutePath() 
  long    lastModified() 
  long    length() 
代码:
import java.io.*;
class FileDemo 
{
	public static void main(String[] args) throws IOException  
	{
			 demo4();	
	}
	
	public static void demo4() throws IOException//获取信息
	{
		File f = new File("day20\\andy1.txt");
		File f1 = new File("G:\\java\\code\\day20\\tony.txt");
		f1.createNewFile();
		//f.renameTo(f1);
		sop(f.getName());
		sop("相对路径:"+f.getPath());
		sop("绝对路径:"+f.getAbsolutePath());
		sop("父目录:"+f.getParent());
		sop("修改时间:"+f1.lastModified());
		sop("长度:"+f1.length());

	
		
	}
	public static void demo3() throws IOException //判断
	{
		File f = new File("andy.txt");
		f.createNewFile();
		
		sop("文件是否存在:"+f.exists());
		sop("是否是文件夹:"+f.isDirectory());
		sop("是否是文件:"+f.isFile());
		sop("是否隐藏:"+f.isHidden());
		sop("是否是绝对路径"+f.isAbsolute());

	}
	public static void demo2() throws IOException  //创建和删除
	{
		File f5 = new File("f5.txt");
		//f5.deleteOnExit(); 在程序退出时,删除指定文件;
		//sop("删除:"+f5.delete());
		sop("创建:"+f5.createNewFile());
		File f = new File("abc");
		sop(f.mkdir());//创建一级目录文件夹
		File d = new File("ab\\d\\ff\\cc");//创建多级目录文件夹
		sop(d.mkdirs());
	}
	
	
	public static void demo1()//创建File对象
	{
		//将f1.txt封装成File对象,可以对已有或出现的文件,文件夹进行封装;
		File f1 = new File("f1.txt");
		File f2 = new File("G:\\java\\code\\day20","f2.txt");
		File f3 = new File("G:\\java\\code\\day20");
		File f4 = new File(f3,"f4.txt");
		sop(f1);
	}
	
	public static void sop(Object o)
	{System.out.println(o);}
}
//File.separator 是系统默认的名称分隔符,跨平台使用
//如 File f3 = new File("G:"+File.separator+"java"+File.separator+"code"+File.separator+"day20");

File的list方法:
import java.io.*;
class FileDemo2 
{
	public static void main(String[] args) 
	{
		//listFilesDemo();
		listDemo();
		listRootsDemo();
	}
	//listFiles(filter)方法返回的是File数组,返回的是文件对象,使用它操作更方便。
	public static void listFilesDemo() {
		File dir = new File("c:\\");
		File[] files = dir.listFiles(new FilenameFilter()
		{
			public boolean accept(File dir,String name)
			{							
				return name.endsWith(".sys");
			}
		});
		
		for(File f:files)
		{
			System.out.println(f.getName()+"::"+f.length());
		}
	}
	//过滤出符合某种条件的文件或文件夹。list方法只返回文件名,是String[]数组
	public static void listDemo_2()
	{
		File dir = new File("c:\\");
		String [] arr = dir.list(new FilenameFilter()
		{
			public boolean accept(File dir,String name)
			{
				//System.out.println("dir:"+dir+".....name:"+name);				
				return name.endsWith(".sys");
			}
		});
		System.out.println("len:"+arr.length);
		for(String name :arr)
		{
			System.out.println(name);
		}

	}
	//打印指定目录下的文件及文件夹的名称,包括隐藏文件及文件夹。
	public static void listDemo()
	{
		File f = new File("c:\\windows");
		if(f.isDirectory()){
			String [] names = f.list();//调用list方法的file对象必须是封装了一个目录,该目录还必须存在
			for(String name :names)
			{
				System.out.println(name);
			}
		}
	}
	//列出机器上的有效盘符
	public static void listRootsDemo()
	{
		File[] files = File.listRoots();
		for(File f: files)
		{
			System.out.println(f+"--------------------------");			
		}
	}
}

递归获取文件目录
递归:
因为目录中还有目录,只要使用同一个列出目录功能更的函数完成即可。
在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身。这种表现形式,或者变成手法,称为递归。
递归要注意:
1.限定条件。
2.要注意递归的次数,尽量避免内存溢出。

import java.io.*; 
class  FileDemo3
{
	public static void main(String[] args) 
	{
		File dir = new File("G:\\java\\code");
		show(dir);
	}
	public static void show(File dir)
	{
		File[] files = dir.listFiles();
		for (int x =0; x<files.length; x++)
		{
			if (files[x].isDirectory())
				show(files[x]);
			else
				System.out.println(files[x]);
		}
	}
}
删除一个带内容的目录。 
删除原理: 
在window中,删除目录从里边往外面删除的。 
 
既然是从里边往外删除,就需要用到递归。 

 
import java.io.*;  
class FileDemo4 
{
	public static void main(String[] args) 
	{
		File f= new File("G:\\java\\code\\day20\\ab");
		removeDir(f);
	}
	public static void removeDir(File f)
	{
		File[] files = f.listFiles();
		for (int x=0 ;x<files.length ;x++ )
		{
			if (!files[x].isHidden()&&files[x].isDirectory())
				removeDir(files[x]);
			else
				System.out.println(files[x]+"----"+files[x].delete());
		}
		System.out.println(f+"delete--"+f.delete());
	}

}
练习
将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。
建立一个java文件列表文件。
思路:
1,对指定的目录进行递归。
2,获取递归过程所以的java文件的路径。
3,将这些路径存储到集合中。
4,将集合中的数据写入到一个文件中。

import java.io.*;
import java.util.*;
class FileDemo5
{
	public static void main(String[] args) throws IOException
	{
		File f = new File("g:\\java\\code");
		
		List<File> list = new ArrayList<File>();
		
		fileToList(f,list);
		
		File file = new File(f,"javalist.txt");
		
		writeToFile(list,file.toString());
	}
	public static void fileToList(File f,List<File> list)
	{
		File[] files = f.listFiles();
		
		for(File file:files)
			if(file.isDirectory())
				fileToList(file,list);
			else 
				if (file.getName().endsWith(".java"))
				{
					list.add(file);
				}
	}
	public static void writeToFile(List<File>list,String filename) throws IOException
	{
		BufferedWriter bfw= new BufferedWriter(new FileWriter(filename));
		for(File f:list)
		{
			String path = f.getAbsolutePath();
			bfw.write(path);
			bfw.newLine();
			bfw.flush();
		}
		bfw.close();	
	}
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值