java---IO流


12、IO流

12.1、IO(Input Output)流

   ·IO流用来处理设备之间的数据传输

   ·java对数据的操作是通过流的方式

   ·java用于操作流的对象都是在IO包中

   ·流按操作数据分为两种:字节流和字符流

   ·流按流向分为:输入流和输出流

12.2、IO流常用基类

   字节流的抽象基类:

      InputStream,OutputStream

   字符流的抽象基类:

      Reader,Writer

   注意:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。

      如:InputStream的子类FileInputStream

      如:Reader的子类FileReader

12.3、IO程序的书写

12.3.1基本步骤

导入IO包中的类

   进行IO异常处理

   在finally中对流进行关闭

思考:

      有了垃圾回收机制为什么还要调用close方法进行关闭

      为什么IO异常一定要处理

12.3.2IO异常的标准处理方式代码示例

只要与系统相关的设备有关的操作都会抛出IO异常
import java.io.*;
class  FileWriterDemo2
{
        public static void main(String[] args) 
        {
//在外边建立引用,在try内进行初始化,作用域为整个函数(关闭流也会使用)
                FileWriter fw = null; 
               try
               {
//try内部初始化,且创建和写入都会发生异常,且他们有关联,所以一起处理
                       fw = new FileWriter("demo.txt"); 
                       fw.write("abcdefg");
               }
               catch (IOException e)
               {
                       System.out.println("catch:"+e.toString());
               }
               finally//在此代码块中,必须进行关闭流的操作
               {
                       try//关闭流时会发生IO异常,所以必须try处理
                       {
      //当有多个流时,应当分别关闭和进行非空判断
      //此句话可以写在try外面,当创建文件发生异常时,会发生空指针异常
                                      if(fw!=null) 
                                         fw.close();                        
                       }
                       catch (IOException e)
                       {
                               System.out.println(e.toString());
                       }
               }              
        }
}

12.4、字符流——创建文件

12.4.1字符流创建文件的步骤    

·创建流对象,建立数据存放文件

      FileWriter fw = new FileWriter(“Test.txt”);

   ·调用流对象的写入方法,将数据写入流

      fw.write(“test”);

   ·关闭流资源,并将流中的数据清空到文件中

      fw.close();

12.4.2FileWriter子类的代码演示

找到一个专门用于操作文件的Writer子类对象。FileWriter。  
后缀名是父类名。 前缀名是该流对象的功能。

 

import java.io.*;

class FileWriterDemo

{

        publicstatic void main(String[] args) throws IOException

        {

               //1、创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。其实该步就是在明确数据要存放的目的地。

               FileWriterfw = new FileWriter("demo.txt");

 

               //2、调用write方法,将字符串写入到流中。

               fw.write("abcde");

 

               //3、刷新流对象中的缓冲中的数据。将数据刷到目的地中。

               //fw.flush();//可以多次使用,但是必须最后关闭

 

               //4、关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。将数据刷到目的地中。和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。

               fw.close();

        }

}

12.4.3对已有文件的数据续写

import java.io.*;
class  FileWriterDemo3
{
        public static void main(String[] args) throws IOException
        {
//传递一个true参数,若没有文件会创建文件,若有文件则不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
               FileWriter fw = new FileWriter("demo.txt",true);
//在window(包括XP和win7)系统中的记事本程序中,换行是识别符是“\r\n”。
               fw.write("nihao\r\nxiexie");
               fw.close();
        }
}

12.5、字符流——读取文件

12.5.1字符流读取文件的步骤    

·建立一个流对象,将已存在的一个文件加载进流

      FileReader fr =new FileReader(“Test.txt”);

   ·创建一个临时存放数据的数组

      char[] ch = new char[1024];

   ·调用流对象的读取方法将流中的数据读入到数组中

      fr.read(ch);

思考:

   在加载文件时候是否将文件全部加载进流

   为什么定义数组,要定义多大呢?

12.5.2注意

   定义文件路径时,可以用“/”或者“\\”

   在创建一个文件时,如果目录下有同名文件将被覆盖

   在读取文件时,必须保证该文件已存在,否则出异常

   会有一个默认的编码方式就是系统的默认编码

12.5.3文本文件读取方式一:一次读一个字符

import java.io.*;
 
class  FileReaderDemo
{
        public static void main(String[] args) throws IOException
        {
//1、创建一个文件读取流对象和指定名称的文件相关联。要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
               FileReader fr = new FileReader("demo.txt");
 
               //2、调用读取流对象的read方法。
        //read():一次读一个字符。而且会自动往下读。独到末尾处返回-1
               //while循环读取文件内容,书写比较简便
               int ch = 0;
               while((ch=fr.read())!=-1)
               {
                       System.out.println(
               }
               /*
               while(true)
               {
                       int ch = fr.read();
                       if(ch==-1)
                               break;
                       System.out.println("ch="+(char)ch);
               }
               */
               fr.close();//关闭该流并释放与之关联的所有资源,不刷新
        }
}图示
 
  
 
  

12.5.4文本文件读取方式二:通过字符数组进行读取

第二种方式:通过字符数组进行读取。
import java.io.*;
class FileReaderDemo2 
{
        public static void main(String[] args) throws IOException
        {
               FileReader fr = new FileReader("demo.txt");
               //定义一个字符数组。用于存储读到字符。
               //该read(char[])返回的是读到的字符个数。
               //字符是2个字节,定义1024的整数倍,这里是2K。
               char[] buf = new char[1024];
        //int num=fr.read(buf);//把流关联的数据读取num个存入数组中。
               int num = 0;
               while((num=fr.read(buf))!=-1)
               {
                       System.out.println(new String(buf,0,num));
               }
               fr.close();
        }
}图示
 
  
 
  

12.5.5练习:读取一个.java文件,并打印在控制台上

文本文件使用字符流
最好打印语句不加换行,因为当打印的数据长度大于1024时,会出现一次换行。
import java.io.*;
class FileReaderTest 
{
        public static void main(String[] args) throws IOException
        {
               FileReader  fr = new FileReader("DateDemo.java");
               char[] buf = new  char[1024];
               int num = 0;
               while((num=fr.read(buf))!=-1)
               {
                       System.out.print(new String(buf,0,num));
               }
               fr.close();
        }
}

12.5.6练习:文本文件的复制

将C盘一个文本文件复制到D盘。
复制的原理:其实就是将C盘下的文件数据存储到D盘的一个文件中。
步骤:
1,在D盘创建一个文件。用于存储C盘文件中的数据。
2,定义读取流和C盘文件关联。
3,通过不断的读写完成数据存储。
4,关闭资源。
 
import java.io.*;
 
class CopyText 
{
        public static void main(String[] args) throws IOException
        {
               copy_2();
        }
        public static void copy_2()//建议掌握
        {
               FileWriter fw = null;
               FileReader fr = null;
               try
               {
                       fw = new FileWriter("SystemDemo_copy.txt");
                       fr = new FileReader("SystemDemo.java");
                       char[] buf = new char[1024];
                       int len = 0;
                       while((len=fr.read(buf))!=-1)
                       {
                               fw.write(buf,0,len);
                       }
               }
               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)
                               {
                               }
               }
        }
//第一种方式:从C盘读一个字符,就往D盘写一个字符。
        public static void copy_1()throws IOException
        {
               //1、创建目的地。
               FileWriter fw = new FileWriter("RuntimeDemo_copy.txt");
               //2、与已有文件关联。
               FileReader fr = new FileReader("RuntimeDemo.java");
               //3、读取一个字符,存一个字符
               int ch = 0;
               while((ch=fr.read())!=-1)
               {
                       fw.write(ch);
               }
               fw.close();
               fr.close();
        }
}复制图例
 
  
 
  

12.6、字符流的缓冲区

12.6.1缓冲区的作用及注意事项

缓冲区的出现是为了提高流的操作效率而出现的。所以在创建缓冲区之前,必须要先有流对象。

缓冲区的出现提高了对数据的读写效率

   对应类

      BufferedWriter

      BufferedReader

   缓冲区要结合流才可以使用

   在流的基础上对流的功能进行增强

 

12.6.2字符写入缓冲区代码示例

该缓冲区中提供了一个跨平台的换行符。
newLine();写入一个行分隔符(在window和linux系统中都是换行,只有缓冲区对象具有此方法)
\r\n:window系统中是换行,\n:在linux系统中是换行
import java.io.*;
class  BufferedWriterDemo
{
        public static void main(String[] args) throws IOException
        {
               //1、创建一个字符写入流对象。
               FileWriter fw = new FileWriter("buf.txt");
               //2、为了提高字符写入流效率。加入了缓冲技术。只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
               BufferedWriter bufw = new BufferedWriter(fw);
               for(int x=1; x<5; x++)
               {
                       bufw.write("abcd"+x);
                       bufw.newLine();//跨平台的通用换行方法
                       bufw.flush();//刷新缓冲区
               }
               //记住,只要用到缓冲区,就要记得刷新
               //bufw.flush();
 
               //其实关闭缓冲区,就是在关闭缓冲区中的流对象。
               bufw.close();
        }
}

12.6.3字符读取缓冲区代码示例

字符读取流缓冲区:字符、数组、行
该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取。当返回null时,表示读到文件末尾。
readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。
import java.io.*;
class  BufferedReaderDemo
{
        public static void main(String[] args) throws IOException
        {
               //1、创建一个读取流对象和文件相关联。
               FileReader fr = new FileReader("buf.txt");
               //2、为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。
               BufferedReader bufr = new BufferedReader(fr);
               String line = null;
               while((line=bufr.readLine())!=null)
               {
                       System.out.println(line);
               }
               bufr.close();
        }
}

12.6.4通过缓冲区复制一个.java文件

import java.io.*;
class  CopyTextByBuf
{
        public static void main(String[] args) 
        {
               BufferedReader bufr = null;
               BufferedWriter bufw = null;
               try
               {
bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
bufw = new BufferedWriter(new FileWriter("bufWriter_Copy.txt"));
               String line = null;//资源的中转站
                       while((line=bufr.readLine())!=null)
                       {
                       bufw.write(line);
                       bufw.newLine();//补充readLine方法的缺陷
                       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方法图例

 
  

12.6.5模拟BufferedReader,自定义readLine方法

 
明白了BufferedReader类中特有方法readLine的原理后,
可以自定义一个类中包含一个功能和readLine一致的方法。
来模拟一下BufferedReader
此类是一个使得读取增强的装饰类
import java.io.*;
class MyBufferedReader extends Reader
{
        private Reader r;
        MyBufferedReader(Reader r)
        {
               this.r = r;
        }
        //可以一次读一行数据的方法。
        public String myReadLine()throws IOException
        {
               //定义一个临时容器。原BufferReader封装的是字符数组。
               //为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。
               StringBuilder sb = new StringBuilder();
               int ch = 0;
               while((ch=r.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;           
        }
        /*
        覆盖Reader类中的抽象方法。
        */
        public int read(char[] cbuf, int off, int len) throws IOException
        {
               return r.read(cbuf,off,len) ;
        }
 
        public void close()throws IOException 
        {
               r.close();
        }
        public void myClose()throws IOException
        {
               r.close();
        }
}
class  MyBufferedReaderDemo
{
        public static void main(String[] args) throws IOException
        {
               FileReader fr = new FileReader("buf.txt");
               MyBufferedReader myBuf = new MyBufferedReader(fr);
               String line = null;
               while((line=myBuf.myReadLine())!=null)
               {
                       System.out.println(line);
               }
               myBuf.myClose();
        }
}

12.7、装饰设计模式

   对原有类进行了功能的改变,增强

   装饰设计模式的基本格式

   它与继承有什么不同?

   了解BufferedReader的原理

12.7.1装饰类

当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
 
装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
class Person
{
        public void chifan()
        {
               System.out.println("吃饭");
        }
}
class SuperPerson //与Person同属于一个接口或者一个类
{
        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();
        }
}

12.7.2装饰和继承的区别

例子:MyReader//专门用于读取数据的类。
        |--MyTextReader
               |--MyBufferTextReader
        |--MyMediaReader
               |--MyBufferMediaReader
        |--MyDataReader
               |--MyBufferDataReader
class MyBufferReader
{
        MyBufferReader(MyTextReader text)
        {}
        MyBufferReader(MyMediaReader media)
        {}
}
上面这个类扩展性很差。找到其参数的共同类型。通过多态的形式。可以提高扩展性。
//组合结构
class MyBufferReader extends MyReader
{
        private MyReader r;
        MyBufferReader(MyReader r)
        {}
}       
MyReader//专门用于读取数据的类。,优化后的体系,扩展性更强
        |--MyTextReader
        |--MyMediaReader
        |--MyDataReader
        |--MyBufferReader//基于前类增加的类
以前是通过继承将每一个子类都具备缓冲功能。
那么继承体系会复杂,并不利于扩展。
现在优化思想。单独描述一下缓冲内容。将需要被缓冲的对象。传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。这样继承体系就变得很简单。优化了体系结构。

12.7.3装饰设计模式的特点

装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类于类之间的关系。
 
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的
 

12.7.4装饰类之LineNumberReader使用行号

import java.io.*;
class LineNumberReaderDemo 
{
        public static void main(String[] args)throws IOException 
        {
               FileReader fr = new FileReader("PersonDemo.java");
//获取行号的装饰类LineNumberReader,内部定义了一个私有属性LineNumber
               LineNumberReader lnr = new LineNumberReader(fr);
               String line = null;
//默认为从0开始,这里设置其从100开始
               lnr.setLineNumber(100);
               while((line=lnr.readLine())!=null)
               {
System.out.println(lnr.getLineNumber()+":"+line);
               }
               lnr.close();
        }
}

12.7.5装饰类练习:模拟一个带行号的缓冲区对象

import java.io.*;
//优化后,继承了MyBufferedReader
class MyLineNumberReader extends MyBufferedReader
{
        private int lineNumber;
        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;
        }
}
/*优化前
class MyLineNumberReader 
{
        private Reader r;//包装一个Reader
        private int lineNumber;
        MyLineNumberReader(Reader r)
        {
               this.r = r;
        }
        public String myReadLine()throws IOException
        {
               lineNumber++;
               StringBuilder sb = new StringBuilder();
               int ch = 0;
               while((ch=r.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;
        }
        public void setLineNumber(int lineNumber)
        {
               this.lineNumber = lineNumber;
        }
        public int getLineNumber()
        {
               return lineNumber;
        }
        public void myClose()throws IOException
        {
               r.close();
        }
}
*/
class  MyLineNumberReaderDemo
{
        public static void main(String[] args) throws IOException
        {
               FileReader fr = new FileReader("copyTextByBuf.java");
 
               MyLineNumberReader mylnr = new MyLineNumberReader(fr);
               String line = null;
               mylnr.setLineNumber(100);
               while((line=mylnr.myReadLine())!=null)
               {
               System.out.println(mylnr.getLineNumber()+"::"+line);
               }
               mylnr.myClose();
        }
}

12.8、字节流

12.8.1字节流作用

基本操作与字符流类相同

   但它不仅仅操作字符,还可以操作其他媒体文件

字符流:编码使用的是系统默认的编码
字符流的操作类:
        FileReader
        FileWriter。
        BufferedReader
        BufferedWriter
字节流:
FileInputStream
FileOutputStream
 
BufferedInputStream
BufferedOutputStream
字节流的操作类:Stream(都是操作的字节流)
        抽象类:InputStream读  OutputStream写(针对电脑而言)
需求,想要操作图片数据。这时就要用到字节流。

12.8.2字节流对文本文件的写入和读取方式

 
代码示例
import java.io.*;
class  FileStream
{
        public static void main(String[] args) throws IOException
        {
               readFile_3();
        }
//第三种读方式:字节特有的available方法读取
        public static void readFile_3()throws IOException
        {
               FileInputStream fis = new FileInputStream("fos.txt");
//             int num = fis.available();//(字符数)
               //定义一个刚刚好的缓冲区。不用在循环了。
//有可能超出虚拟机的最大容量,默认是64M。会发生内存溢出。数据不太大的可以
        byte[] buf = new byte[fis.available()];
               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(new String(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)
               {
                       //读过之后是一个数字,需要ASCII码表的转换
               System.out.println((char)ch); 
               }
               fis.close();
        }
//字节流对文本文件的写入方式
        public static void writeFile()throws IOException
        {
//写数据的目的
               FileOutputStream fos = new FileOutputStream("fos.txt");
               //把字符串变成字节数组
               fos.write("abcde".getBytes());
               //必须关闭资源,但是不具备刷新功能
               fos.close();
        }
}

12.8.3字节流的练习——复制一个图片

复制一个图片(标准写法包含异常处理)
思路:
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("写入关闭失败");
                       }
               }
        }
}

12.8.4字节流的练习——MP3复制(通过缓冲区)

演示mp3的复制。通过缓冲区。初始化必须有流关联
BufferedOutputStream
BufferedInputStream
 
import java.io.*;
class  CopyMp3
{
        public static void main(String[] args) throws IOException
        {
               long start = System.currentTimeMillis();//开始时间
               copy_2();
               long end = System.currentTimeMillis();//结束时间
        System.out.println((end-start)+"毫秒");//时间差,为拷贝时间
        }
//第二种方式:自定义字节流的缓冲区拷贝mp3文件
        public static void copy_2()throws IOException
        {
               MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\9.mp3"));
               BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\3.mp3"));
               
               int by = 0;
 
               //System.out.println("第一个字节:"+bufis.myRead());
 
               while((by=bufis.myRead())!=-1)
               {
                       bufos.write(by);
               }
 
               bufos.close();
               bufis.myClose();
        }
//第一种方式:通过字节流的缓冲区完成复制。
        public static void copy_1()throws IOException
        {
               BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));//源文件
               BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));//输出文件
//双反都有数组,所以就不必再定义数组
               int by = 0;
               while((by=bufis.read())!=-1)
               {
                       bufos.write(by);
               }
               bufos.close();
               bufis.close();
        }
}

12.9、字节流的缓冲区

12.9.1字符流的缓冲区 

同样是提高了字节流的读写效率

      MP3文件的拷贝比较效率

   模拟一个BufferedInputStream

 

12.9.2复制Mp3文件使用——自定义字节流的缓冲区

import java.io.*;
 
class MyBufferedInputStream
{
        private InputStream in;
        private byte[] buf = new byte[1024*4];//定义字节数组
        private int pos = 0,count = 0;//指针和计数器
        MyBufferedInputStream(InputStream in)//包装类,装饰类
        {
               this.in = in;
        }
        //一次读一个字节,从缓冲区(字节数组)获取。
        public int myRead()throws IOException
        {
               //通过in对象读取硬盘上数据,并存储buf中。
               if(count==0)//确保字符数组中的数据都被取完
               {
                       count = in.read(buf);//记录读取字符数组的字节数
                       if(count<0)
                               return -1;
                       pos = 0;//设置指针归零,方便下次继续存储
                       byte b = buf[pos];//取得字节数组元素
                       count--;//取过之后的计数器减一
                       pos++;//指针向后加一,继续取
                       return b&255;//原因在下面
               }
               else if(count>0)//取元素
               {
                       byte b = buf[pos];
                       count--;
                       pos++;
                       return b&0xff;//255的八进制表示形式
               }
               return -1;
        }
        public void myClose()throws IOException//关流
        {
               in.close();
        }
}
原因:读取数据时有可能读到8个1的字节,结果为-1,程序在未完成时就结束。
 
读取的字节byte: -1  会被提升为  int : -1;
字节型:1111-1111 int型:高位补1:1111-1111 1111-1111 1111-1111 1111-1111
为了保证数据的原样为:00000000 00000000 00000000 11111111  255
那么我只要在前面补0,即可以保留原字节数据不变,又可以避免-1的出现。
怎么补0呢?
用&运算
 11111111 11111111 11111111 11111111 -1
&00000000 00000000 00000000 11111111 255
------------------------------------
 00000000 00000000 00000000 11111111 255
求-1的过程:              
0000-0001 正1
1111-1110 取反
000000001 加1
1111-1111 得-1
结论:
字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.
那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
所以,为了避免这种情况将读到的字节进行int类型的提升。
并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。
而在写入数据时,只写该int类型数据的最低8位。
read方法在读取的时候进行了提升,但是write方法在写的时候进行了强转。

12.10、转换流

12.10.1转换流

   InputStreamReader,OutputStreamWriter

   转换流的由来

      字符流与字节流之间的桥梁

      方便了字符流与字节流之间的操作

   转换流的应用

      字节流中的数据都是字符,转成字符流操作更高效

12.10.2转换流使用的情况

字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。

 

12.10.3字节流转成字符流使用字符流缓冲readLine方法

通过下面的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理。也就是readLine方法。
能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?
 
readLine方法是字符流BufferedReader类中的方法。
而键盘录入的read方法是字节流InputStream的方法。
 
那么能不能将字节流转成字符流在使用字符流缓冲去的readLine方法呢?
 
 
import java.io.*;
 
class  TransStreamDemo
{
        public static void main(String[] args) throws IOException
        {

12.10.4写入转换流示例代码

//1、创建显示器输出流
               OutputStream out = System.out; 
//2、将字符流对象转成字节流对象,使用转换流。OutputStreamWriter
               OutputStreamWriter osw = new OutputStreamWriter(out);
//3、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedWriter
               BufferedWriter bufw = new BufferedWriter(osw);
优化上面的程序代码:将上述的三句话简写为下面的一句话(嵌套进去)
               BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
               String line = null;
               while((line=bufr.readLine())!=null)
               {
                       if("over".equals(line))//定义键盘录入结束标记
                               break;
//包装了System.out就可以使用此将对象打印出去。
                       bufw.write(line.toUpperCase());
                       bufw.newLine();//newline()是缓冲区的特有方法
                       bufw.flush();//刷新缓冲区
               }

12.10.5读取转换流示例代码:

//1、获取键盘录入对象。
               InputStream in = System.in;
//2、将字节流对象转成字符流对象,使用转换流。InputStreamReader
               InputStreamReader isr = new InputStreamReader(in);
//3、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
               BufferedReader bufr = new BufferedReader(isr);
优化上面的程序代码:将上述的三句话简写为下面的一句话(嵌套进去)
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘录入,一读一行,最常见写法,必须会
 
               String line = null;
               while((line=bufr.readLine())!=null)
               {
                       if("over".equals(line))//定义键盘录入结束标记
                              break;
                       bufw.write(line.toUpperCase());
               }
               bufr.close();//关不关都可以,因为只有键盘录入操作对象
        }
}

12.11、标准输入输出流

   System类中的字段:in,out

   它们各代表了系统标准的输入和输出设备

   默认输入设备是键盘,输出设备是显示器

   System.in的类型是InputStream

   System.out的类型是PrintStream是OutputStream的子类FileOutStream的子类

   键盘录入的结束标记:除了Ctrl+C就是定义录入结束标记。

12.11.1练习——读取键盘录入(必须亲自写一遍)

读取键盘录入。
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 ch = in.read();//没有读到数据是就会等待输入
                       if(ch=='\r')//ASCII码中的编码是13
                               continue;
                       if(ch=='\n') //ASCII码中的编码是10
                       {
                       String s = sb.toString();//缓冲区变成字符串
                               if("over".equals(s))
                                      break;
               System.out.println(s.toUpperCase());//编程大写打印
               sb.delete(0,sb.length());//清空缓冲区
                       }
                       else
                               sb.append((char)ch);//添加到缓冲区
               }
        }
}

//优化代码见上面的转换流里的代码示例

12.12、流的基本应用——操作规律

12.12.1流操作

   ·流是用来处理数据的

   ·处理数据时,一定要先明确数据源,与数据目的地(数据汇)

   ·数据源可以是文件,可以是键盘

   ·数据目的地可以是文件、显示器或者其它设备

   ·而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理、转换处理等。

12.12.2流操作规律总结

12.12.2.1、在控制台下及时的输入和输出

数据源:键盘录入。     数据目的:控制台。
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘录入,数据的源
        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//控制台,数据的目的

12.12.2.2,把键盘录入的数据存储到一个文件中

数据源:键盘。         数据目的:文件。
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘录入
        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(“out.txt”)));//文件输出

12.12.2.3,将一个文件的数据打印在控制台上。

数据源:文件。         数据目的:控制台。
        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream(“copy.txt”)));//文件
        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//控制台

12.12.3流操作的基本规律总结

病症所在:最痛苦的就是流对象有很多,不知道该用哪一个。
解决办法:通过三个明确来完成。

12.12.3.1,明确源和目的。

        数据源:输入流。InputStream(字节输入)Reader(字符输入)
        数据目的:输出流。OutputStream(字节输出)Writer(字符输入)

12.12.3.2,操作的数据是否是纯文本。

        是:字符流。
        否:字节流。

12.12.3.3,当体系明确后,再通过设备明确要使用哪个具体的对象。

        数据源设备:内存,硬盘。键盘
        数据目的设备:内存,硬盘,控制台。

12.12.4举例说明流操作的基本规律

(有时候操作数据,只有数据源或者数据目的,不一定都有)

12.12.4.1、将一个文本文件中数据存储到另一个文件中(复制文件)

数据源:因为是源,所以使用读取流。InputStream Reader 
        是不是操作文本文件。
        是!这时就可以选择Reader
        这样体系就明确了。
 
        接下来明确要使用该体系中的哪个对象。
        明确设备:硬盘上一个文件。
        Reader体系中可以操作文件的对象是 FileReader
 
        是否需要提高效率:是!加入Reader体系中缓冲区 BufferedReader.
 
        FileReader fr = new FileReader("a.txt");
        BufferedReader bufr = new BufferedReader(fr);
目的:OutputStream和Writer
        是否是纯文本。
        是!Writer。
        设备:硬盘上的一个文件。
        Writer体系中可以操作文件的对象FileWriter。
        是否需要提高效率:是!。加入Writer体系中缓冲区 BufferedWriter
        
        FileWriter fw = new FileWriter("b.txt");
        BufferedWriter bufw = new BufferedWriter(fw);
---------------------------------------------------------------------

12.12.4.2、将键盘录入的数据保存到一个文件中。

        这个需求中有源和目的都存在。那么分别分析
 
        数据源:InputStream和Reader
        是不是纯文本?是!Reader
        设备:键盘。对应的对象是System.in.
        疑问:不是选择Reader吗?System.in对应的不是字节流吗?
        解释:为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。所以既然明确了Reader,那么就将System.in转换成Reader。用了Reader体系中转换流,InputStreamReader
        InputStreamReader isr = new InputStreamReader(System.in);
        需要提高效率吗?需要!BufferedReader
        BufferedReader bufr = new BufferedReader(isr);
 
        目的:OutputStream和Writer
        是否是存文本?是!Writer。
        设备:硬盘。一个文件。使用 FileWriter。
        FileWriter fw = new FileWriter("c.txt");
        需要提高效率吗?需要。
        BufferedWriter bufw = new BufferedWriter(fw);
---------------------------------------------------------------------

12.12.4.3、把录入的数据按照指定的编码表(utf-8),将数据存到文件中。

        
        数据目的:OutputStream和Writer
        是否是存文本?是!Writer。
        设备:硬盘。一个文件。使用 FileWriter。
        但是FileWriter是使用的默认编码表(系统默认的编码表)GBK.
        
        但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。所以要使用的对象是OutputStreamWriter。而该转换流对象要接收一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
        需要高效吗?需要。
        BufferedWriter bufw = new BufferedWriter(osw);

12.12.4.4、转换流使用的情况

字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。

12.12.4.5、代码示例(改变输入输出流)

import java.io.*;
class  TransStreamDemo2
{
        public static void main(String[] args) throws IOException
        {
//改变标准输入输出流设备使用setXxx方法
               System.setIn(new FileInputStream("PersonDemo.java"));
               System.setOut(new PrintStream("zzz.txt"));
//若不改变,则默认的为键盘和控制台,改变之后则使用改变的输入输出流设备
               BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//数据的源
               BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//数据的目的
               String line = null;
               while((line=bufr.readLine())!=null)
               {
                       if("over".equals(line))
                               break;
                       bufw.write(line.toUpperCase());
                       bufw.newLine();
                       bufw.flush();
               }
               bufr.close();
        }
}

12.12.5利用IO流操作并记录异常的日志信息

import java.io.*;
import java.util.*;
import java.text.*;//日期格式化
class  ExceptionInfo
{
        public static void main(String[] args)throws IOException 
        {
               try
               {
                       int[] arr = new int[2];//创建一个数组
               System.out.println(arr[3]);//使其发生脚标越界异常
               }
               catch (Exception e)
               {
                       try
                       {
//创建一个日期
                               Date d = new Date();
//装换日期的格式,使其模式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//把日期格式化为一个字符串形式
                               String s = sdf.format(d);
//创建一个新的打印输出流:形成一个日志文件
               PrintStream ps = new PrintStream("exeception.log");
//打印异常发生的日期
                               ps.println(s);
//改变默认输出流,设置为新的打印流
                               System.setOut(ps);
                       }
                       catch (IOException ex)
                       {
//抛出运行异常,使得程序停止
                       throw new RuntimeException("日志文件创建失败");
                       }
//使得异常信息成变成输出流
               e.printStackTrace(System.out);        }
        }
}
//log4j:工具下载下来,存储日志文件

12.12.6获取系统信息

12.12.6.1获取系统信息代码示例

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(new PrintStream("sysinfo.txt"));
        }
}

12.12.6.2我的系统文件信息

sysinfo.txt文件内容

--listing properties --

java.runtime.name=Java(TM)SE Runtime Environment

sun.boot.library.path=C:\ProgramFiles\Java\jre6\bin

java.vm.version=20.6-b01

java.vm.vendor=SunMicrosystems Inc.

java.vendor.url=http://java.sun.com/

path.separator=;

java.vm.name=JavaHotSpot(TM) 64-Bit Server VM

file.encoding.pkg=sun.io

user.country=CN

sun.java.launcher=SUN_STANDARD

sun.os.patch.level=ServicePack 1

java.vm.specification.name=JavaVirtual Machine Specification

user.dir=D:\demo//获得用户当前目录

java.runtime.version=1.6.0_31-b05

java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment

java.endorsed.dirs=C:\ProgramFiles\Java\jre6\lib\endorsed

os.arch=amd64

java.io.tmpdir=C:\Users\abc\AppData\Local\Temp\

line.separator=

java.vm.specification.vendor=SunMicrosystems Inc.

user.variant=

os.name=Windows7//操作系统名称

sun.jnu.encoding=GBK

java.library.path=C:\WINDOWS\SYSTEM32;C:\windows\Sun\Ja...

java.specification.name=JavaPlatform API Specification

java.class.version=50.0

sun.management.compiler=HotSpot64-Bit Tiered Compilers

os.version=6.1

user.home=C:\Users\abc

user.timezone=

java.awt.printerjob=sun.awt.windows.WPrinterJob

file.encoding=GBK//系统默认编码表(简体中文版的操作系统)

java.specification.version=1.6

user.name=abc

java.class.path=.;C:\ProgramFiles\Java\jdk1.6.0_31\l...

java.vm.specification.version=1.0

sun.arch.data.model=64

java.home=C:\ProgramFiles\Java\jre6

sun.java.command=demo

java.specification.vendor=SunMicrosystems Inc.

user.language=zh

awt.toolkit=sun.awt.windows.WToolkit

java.vm.info=mixedmode

java.version=1.6.0_31//JDK版本

java.ext.dirs=C:\ProgramFiles\Java\jre6\lib\ext;C:...

sun.boot.class.path=C:\ProgramFiles\Java\jre6\lib\resour...

java.vendor=SunMicrosystems Inc.

file.separator=\

java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport..sun.cpu.endian=little

sun.io.unicode.encoding=UnicodeLittle

sun.desktop=windows

sun.cpu.isalist=amd64

12.13、字符流继承体系简图

12.14、字节流继承体系简图

12.15、File类

12.15.1File类

   ·用来将文件或者文件夹封装成对象

   ·方便对文件与文件夹的属性信息进行操作

   ·File对象可以作为参数传递给流的构造函数

   ·了解File类中的常用方法

   ·是文件和目录路径名的抽象表示形式

12.15.2File类常见方法

在操作文件的时候会使用底层资源,所以会发生异常。

12.15.2.1、创建

        boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。创建成功返回true。注意和输出流不一样,输出流对象一建立就会创建文件。而且若文件已经存在,则会覆盖。
        可以创建临时文件,在程序运行使其使用,在程序结束后成为垃圾文件,一些程序的配置文件,此临时文件也可以存储在System32系统级目录下面。

static File

createTempFile(String prefix,String suffix)
在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。

static File

createTempFile(String prefix,String suffix,File directory)
在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。

 
        boolean mkdir():创建文件夹。只能创建一级目录(”abc\\kkk”)
        boolean mkdirs():创建多级文件夹。mkdirs可以创建多级目录。

12.15.2.2、删除

        boolean delete():删除失败返回false。如果文件正在被使用,则删除不了返回false。
        void deleteOnExit();在程序退出时删除指定文件,直接写在创建文件的后面。

12.15.2.3、判断

 

boolean

canExecute()
测试应用程序是否可以执行此抽象路径名表示的文件。

boolean

canRead()
测试应用程序是否可以读取此抽象路径名表示的文件。

boolean

canWrite()
测试应用程序是否可以修改此抽象路径名表示的文件。

        
        boolean exists():文件是否存在.
        boolean isFile():是否是文件
        boolean isDirectory();是否是目录
        boolean isHidden();是否隐藏(java用流对系统盘下的(System开头的系统卷标目录)文件是访问不了的,对隐藏文件尽量不要访问)
        boolean isAbsolute();是否是绝对路径,是相对路径返回为false,是绝对路径返回为true,不管文件是否存在

12.15.2.4、获取信息。

        

File

getAbsoluteFile()
返回此抽象路径名的绝对路径名形式。

String

getAbsolutePath()
返回此抽象路径名的绝对路径名字符串。

File

getCanonicalFile()
返回此抽象路径名的规范形式。

String

getCanonicalPath()
返回此抽象路径名的规范路径名字符串。

long

getFreeSpace()
返回此抽象路径名指定的分区中未分配的字节数。

String

getName()
返回由此抽象路径名表示的文件或目录的名称。

String

getParent()
返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null

File

getParentFile()
返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null

String

getPath()
将此抽象路径名转换为一个路径名字符串。

long

getTotalSpace()
返回此抽象路径名指定的分区大小。

long

getUsableSpace()
返回此抽象路径名指定的分区上可用于此虚拟机的字节数。

 

long

lastModified()
返回此抽象路径名表示的文件最后一次被修改的时间。

long

length()
返回由此抽象路径名表示的文件的长度。

 
        getAbsolutePath() 
        long lastModified() //操作后的文件是否被修改,返回时间是毫秒值
        long length() //数据的大小

12.15.2.5代码示例

import java.io.*;
class FileDemo 
{
        public static void main(String[] args) throws IOException
        {
               method_5();
        }
public static void method_5()
        {
               File f1 = new File("c:\\Test.java");
               File f2 = new File("d:\\hahah.java");
               sop("rename:"+f2.renameTo(f1));//类似于移动,不变内容
        }
public static void method_4()
        {
               File f = new File("file.txt");
               sop("path:"+f.getPath());//获取封装的路径
        sop("abspath:"+f.getAbsolutePath());//获取绝对路径(虽然不存在)
        sop("parent:"+f.getParent());//该方法返回的是绝对路径中的父目录。如果获取的是相对路径,返回null。如果相对路径中有上一层目录那么该目录就是返回结果。
        }
public static void method_3()throws IOException
        {
               File f = new File("d:\\java1223\\day20\\file2.txt");
               //f.createNewFile();
               //f.mkdir();
       //记住在判断文件对象是否是文件或者目录时,必须要先判断该文件对象封装的内容是否存在。通过exists判断。
               sop("dir:"+f.isDirectory());
               sop("file:"+f.isFile());
               sop(f.isAbsolute());//相对路径为false,绝对路径为true,不管文件是否存在
        }
public static void method_2()
        {
               File f = new File("file.txt");
//判断文件是否存在,返回值为boolean型(使用的频率比较高,流操作)
               //sop("exists:"+f.exists());
                //判断文件是否可执行
               //sop("execute:"+f.canExecute());
               //创建文件夹,返回值为boolean型,说明目录是否创建成功,mkdir方法只能创建一级目录(”abc\\kkk”),mkdirs可以创建多级目录。
               File dir = new File("abc\\kkk\\a\\a\\dd\\ee\\qq\\aaa");
               sop("mkdir:"+dir.mkdirs());
}
public static void method_1()throws IOException
        {
               File f = new File("file.txt");
        //创建文件,返回值为boolean型的,说明创建文件是否成功。
               sop("create:"+f.createNewFile());
        //删除文件,返回值为boolean型的,说明文件是否删除成功
               sop("delete:"+f.delete());
        }
//创建File对象
        public static void consMethod()
        {
//File类的构造方法,创建File类
//第一种方式:将a.txt封装成file对象。可以将已有的和没有出现的文件或者文件夹封装成对象。
               File f1 = new File("a.txt");
//第二种方式:将文件和目录换成两个变量传递(父目录和文件中的文件名)等同于File f2 = new File("c:\\abc\\b.txt");
               File f2 = new File("c:\\abc","b.txt");
//第三种方式:此种方式等同于第二种方式,一个是字符串,另一个是File对象
               File d = new File("c:\\abc");
               File f3 = new File(d,"c.txt");
//打印结果为路径:封装相对路径打印相对路径(f1),分装绝对路径打印绝对路径(f2,f3)。
               sop("f1:"+f1); //结果为:a.txt
               sop("f2:"+f2); //结果为:c:\abc\b.txt
               sop("f3:"+f3); //结果为:c:\abc\c.txt
//打印路径中会有目录分隔符,反斜杠“\”后的为转义字符,所以会有两个。也可以写作为“/”(在window下)。在Linux下会变化,所以不利于跨平台。解决的办法就是用File的一个属性写法为:File.separator。
               File f4 = new File("c:"+File.separator+"abc"+File.separator+"zzz"+File.separator+"a.txt");
}
public static void sop(Object obj)
        {
               System.out.println(obj);
        }
}

12.15.3列出目录中的所有内容

列出目录中的内容
import java.io.*;
class  FileDemo2
{
        public static void main(String[] args) 
        {
               File dir = new File("c:\\");
               File[] files = dir.listFiles();
               for(File f : files)
               {
System.out.println(f.getName()+"::"+f.length());//文件的名字和大小
               }
        }
        public static void listDemo_2()
        {
               File dir = new File("d:\\java1223\\day18");
        String[] arr = dir.list(new FilenameFilter()//匿名内部类
               {
                       public boolean accept(File dir,String name)
                       {
               System.out.println("dir:"+dir+"....name::"+name);
                               /*优化前
                               if(name.endsWith(".bmp"))
                                      return true;
                               else
                               return false;
                               */
                               return name.endsWith(".bmp");//优化后
                       }
               });
               System.out.println("len:"+arr.length);
               for(String name : arr)
               {
                       System.out.println(name);
               }
        }
public static void listDemo()
        {
               File f = new File("c:\\abc.txt");
               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);
               }
        }
}

12.16、递归

   函数自己调用自己

   注意:递归时一定要明确结束条件

   应用场景:当某一功能要重复使用时

   练习:列出一个文件夹下所有的子文件夹以及子文件

   思考:删除一个目录的过程是如何进行的?

12.16.1列出目录下的所有目录包括子目录

/*
列出指定目录下文件或者文件夹,包含子目录中的内容。
也就是列出指定目录下所有内容。
 
因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身。
这种表现形式,或者编程手法,称为递归。
 

12.16.2递归要注意

1,限定条件。
 
2,要注意递归的次数。尽量避免内存溢出。
 
import java.io.*;
 
class FileDemo3 
{
        public static void main(String[] args) 
        {
               File dir = new File("d:\\testdir");
               //showDir(dir,0);
               //toBin(6);
               //int n = getSum(8000);
               //System.out.println("n="+n);
               System.out.println(dir.delete());
        }
        public static String getLevel(int level)
        {
               StringBuilder sb = new StringBuilder();
               sb.append("|--");//调用一次方法加一个标记
               for(int x=0; x 
  
               {
                       //sb.append("|--");
                       sb.insert(0,"|  ");//插入
               }
               return sb.toString();
        }
public static void showDir(File dir,int level)//调用时传入目录和标记
        {
        System.out.println(getLevel(level)+dir.getName());
               level++;
               File[] files = dir.listFiles();
               for(int x=0; x 
  
               {
                       if(files[x].isDirectory())
                               showDir(files[x],level);
                       else
                               System.out.println(getLevel(level)+files[x]);
               }
        }
public static int getSum(int n)//演示递归通过加法运算
        {
               if(n==1)
                       return 1;
               return n+getSum(n-1);
        }
public static void toBin(int num)//演示递归通过十进制向二进制转换
        {
               if(num>0)
               {
                       toBin(num/2);
                       System.out.println(num%2);
               }
        }
public static void method()//演示递归,必须有控制条件
        {
               method();
        }
}

12.16.3递归应用——删除带内容的目录

删除一个带内容的目录。java删除文件时不走回收站的。
删除原理:在window中,删除目录从里面往外删除的。
既然是从里往外删除。就需要用到递归。
import java.io.*;
class  RemoveDir
{
        public static void main(String[] args) 
        {
               File dir = new File("d:\\testdir");
               removeDir(dir);
        }
public static void removeDir(File dir)
        {
               File[] files = dir.listFiles();
               for(int x=0; x 
  
               {       
//最好避免文件为隐藏的,因为隐藏的无法访问files[x].isHidden()       
                       if(files[x].isDirectory())
                               removeDir(files[x]);
                       else
//删除文件
System.out.println(files[x].toString()+":-file-:"+files[x].delete());
               }
        System.out.println(dir+"::dir::"+dir.delete());//删除文件夹
        }
}

12.16.4递归应用——创建java文件列表

将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。建立一个java文件列表文件。
思路:
1,对指定的目录进行递归。
2,获取递归过程所以的java文件的路径。
3,将这些路径存储到集合中。
4,将集合中的数据写入到一个文件中。
import java.io.*;
import java.util.*;集合类
class  JavaFileList
{
        public static void main(String[] args) throws IOException
        {
               File dir = new File("d:\\java1223");
               List list = new ArrayList();
               fileToList(dir,list);
               //System.out.println(list.size());
               File file = new File(dir,"javalist.txt");
               writeToFile(list,file.toString());
        }
public static void fileToList(File dir,List list)//路径和集合
        {
               File[] files = dir.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 list,String javaListFile)throws IOException
        {
               BufferedWriter bufw =  null;
               try
               {
        bufw = new BufferedWriter(new FileWriter(javaListFile));
                       for(File f : list)
                       {
                               String path = f.getAbsolutePath();
                               bufw.write(path);
                               bufw.newLine();
                               bufw.flush();
                       }
               }
               catch (IOException e)
               {
                       throw e;
               }
               finally
               {
                       try
                       {
                               if(bufw!=null)
                                      bufw.close();
                       }
                       catch (IOException e)
                       {
                               throw e;
                       }
               }
        }
}

12.17Properties

12.17.1Properties类声明

java.util 
类 Properties
java.lang.Object
  java.util.Dictionary
      java.util.Hashtable
          java.util.Properties

所有已实现的接口:

Serializable, Cloneable, Map

直接已知子类:

Provider

12.17.2Properties类的常用方法

Properties对象是hashtable的子类。
也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串。是集合中和IO技术相结合的集合容器。
Properties对象的特点:可以用于键值对形式的配置文件。
那么在加载数据时,需要数据有固定格式:键=值。
import java.io.*;
import java.util.*;
 
class PropertiesDemo 
{
        public static void main(String[] args) throws IOException
        {
               //method_1();
               loadDemo();
        }
public static void loadDemo()throws IOException
        {
               Properties prop = new Properties();
               FileInputStream fis = new FileInputStream("info.txt");
               //将流中的数据加载进集合。JDK1.6版本以后出现的
               prop.load(fis);
//拥有集合以后就可以操作集合,修改文件,改变的是内存的结果。
               prop.setProperty("wangwu","39");
 
        FileOutputStream fos = new FileOutputStream("info.txt");
//store方法是将内存的结果存到流中,并存到一个文件中。
               prop.store(fos,"haha");//输出流和注释信息(不要写中文),自动回添加一个系统当前时间,注释信息前面带有“#”号,不会被Properties信息所加载,被加载的信息必须是键值对。不是键值对加载的没有意义。
        //      System.out.println(prop);//流加载进集合之后就可以打印
               prop.list(System.out);//列出集合目录
 
               fos.close();
               fis.close();
 
        }
演示,如何将流中的数据存储到集合中。
想要将info.txt中键值数据存到集合中进行操作。
思路:
        1,用一个流和info.txt文件关联。
        2,读取一行数据,将该行数据用"="进行切割。
        3,等号左边作为键,右边作为值。存入到Properties集合中即可。
public static void method_1()throws IOException
        {
BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));
               String line = null;
               Properties prop = new Properties();
        while((line=bufr.readLine())!=null)
               {
               String[] arr = line.split("=");//读到一行进行切
        ///System.out.println(arr[0]+"...."+arr[1]);//读取的键值对
        prop.setProperty(arr[0],arr[1]);//将键值对放到Properties中
               }
               bufr.close();//关流
               System.out.println(prop);打印Properties对象
        }
//      设置和获取元素。
        public static void setAndGet()
        {
               Properties prop = new Properties();//创建对象
               prop.setProperty("zhangsan","30");
               prop.setProperty("lisi","39");
//             System.out.println(prop);//默认等号连接,大括号内
               String value = prop.getProperty("lisi");//通过键获取值
               //System.out.println(value);
               prop.setProperty("lisi",89+"");//修改,字符串
//JDK1.6以后的版本才有的stringPropertyNames()方法,返回的是键集
               Set names = prop.stringPropertyNames();
               for(String s : names)
               {
        System.out.println(s+":"+prop.getProperty(s));//返回键和值
               }
        }
}

12.17.3Properties类的应用——记录程序运行的次数

/*
用于记录应用程序运行次数。如果使用次数已到,那么给出注册提示。
 
很容易想到的是:计数器。
可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。可是随着该应用程序的退出,该计数器也在内存中消失了。下一次再启动该程序,又重新开始从0计数。这样不是我们想要的。
 
程序即使结束,该计数器的值也存在。下次程序启动后会先加载该计数器的值并加1后在重新存储起来。所以要建立一个配置文件。用于记录该软件的使用次数。
该配置文件使用键值对的形式。这样便于阅读数据,并操作数据。
键值对数据是map集合。数据是以文件形式存储,使用io技术。
那么map+io -->properties.

12.17.4配置文件可以实现应用程序数据的共享

java中常用的配置文件**.properties扩展名就是这集合名,其中的配置信息就是键值对。
常用的配置文件有:键值对和XML文件(这两种最常见),框架也是这样的,配置文件(字符串)
可以把配置文件写到系统级目录下(在非常绝密的情况下)
import java.io.*;
import java.util.*;
class  RunCount
{
        public static void main(String[] args) throws IOException
        {
               Properties prop = new Properties();
        File file = new File("count.ini");//把文件分装成文件对象
//对文件的存在性判断,下面的流文件就不会报异常。否则路径异常。
               if(!file.exists())
                       file.createNewFile();
               //在此处可以写一个否则
               FileInputStream fis = new FileInputStream(file);
               prop.load(fis);//将流中的数据加载到集合
               int count = 0;
               String value = prop.getProperty("time");
               
               if(value!=null)
               {
                       count = Integer.parseInt(value);
                       if(count>=5)
                       {
                       System.out.println("您好,使用次数已到,拿钱!");
                               return ;
                       }
               }
               count++;
               prop.setProperty("time",count+"");
        FileOutputStream fos = new FileOutputStream(file);//写回源文件
               prop.store(fos,"");
               fos.close();
               fis.close();
        }
}
 
/*

12.17.5配置文件描述

配置文件.ini文件
name=zhangsan
age=20
XML文件配置信息(取此文件的信息的工具在org.w3c.dom包中的Document接口建立对象那这个数据,但是java获取这个数据是非常的麻烦,要用好多对象和方法完成。)
但是有更好的办法去获取这个信息就是dom4j:dom for java(four和for谐音)
      //标记
        
               zhagnsan
               30
               
bj
        
        
                
  
        
 
  
*/

12.17、IO包中的其他类

12.17.1打印流

      PrintWriter与PrintStream

        可以直接操作输入流和文件

打印流:
该流提供了打印方法,可以将各种数据类型的数据都原样打印。
 

12.17.1.1字节打印流

        打印int型的字节打印是最低八位
PrintStream
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
 

12.17.1.2字符打印流

PrintWriter
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
4,字符输出流,Writer。

12.17.1.3代码示例

import java.io.*;
class  PrintStreamDemo
{
        public static void main(String[] args) throws IOException
        {
        BufferedReader bufr =  new BufferedReader(new InputStreamReader(System.in));//键盘输入,数据源
//通用性极强的输出流,控制台,//自动刷新必须有刷新标记,文件对象可以装到缓冲区中,new BufferedWriter(new FileWriter("a.txt"))。
PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true); 
               String line = null;
               while((line=bufr.readLine())!=null)
               {
                       if("over".equals(line))//结束标记
                               break;
                       out.println(line.toUpperCase());
                       //out.flush();//刷新
               }
               out.close();//关闭打印流
               bufr.close();//关闭键盘录入
        }       
}

12.17.2序列流

      SequenceInputStream对多个流进行合并

12.17.2.1SequenceInputStream类声明

java.io (没有对应的OutputStream)
类 SequenceInputStream

java.lang.Object
  java.io.InputStream
      java.io.SequenceInputStream

所有已实现的接口:

Closeable


public class SequenceInputStream
extends InputStream

SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

从以下版本开始:

JDK1.0

12.17.2.2示例代码

import java.io.*;
import java.util.*;
class SequenceDemo 
{
        public static void main(String[] args) throws IOException
        {
        //Vector集合中有Enumeration创建集合,存入流对象
        Vector v = new Vector();
               v.add(new FileInputStream("c:\\1.txt"));
               v.add(new FileInputStream("c:\\2.txt"));
               v.add(new FileInputStream("c:\\3.txt"));
        //集合中的Enumeration对象操作集合
               Enumeration en = v.elements();
        //创建序列流对象,并将集合传入
               SequenceInputStream sis = new SequenceInputStream(en);
        //由于不涉及编码可以使用字节流
        FileOutputStream fos = new FileOutputStream("c:\\4.txt");
               byte[] buf = new byte[1024];
               int len =0;
               while((len=sis.read(buf))!=-1)
               {
                       fos.write(buf,0,len);
               }
               fos.close();
               sis.close();//关闭所有流
        }
}

12.17.2.3文件切割合并的程序

此程序切割图片和Mp3没有问题,但是切割电影时可以使用切割也是用1M的空间,然后循环使用当100M时存入一个空间。

import java.io.*;
import java.util.*;
 
class SplitFile 
{
        public static void main(String[] args) throws IOException
        {
               //splitFile();
               merge();
        }
//合并文件
public static void merge()throws IOException
        {
//创建一个集合存储切割后的部分文件
ArrayList al = new ArrayList();
//循环将切割的部分文件添加到集合中去
               for(int x=1; x<=3; x++)
               {
//把切割的部分文件分装成读取流,添加到集合中去
al.add(new FileInputStream("c:\\splitfiles\\"+x+".part"));
               }
//迭代器中含有Enumeration对象,用迭代器遍历集合中的分割文件
               final Iterator it = al.iterator();
//使用的是匿名内部类
Enumeration en = new Enumeration()
               {
        //覆盖Enumeration中的方法
                       public boolean hasMoreElements()
                       {
                               return it.hasNext();
                       }
                       public FileInputStream nextElement()
                       {
                               return it.next();
                       }
               };
//创建一个序列流,关联Enumeration对象
               SequenceInputStream sis = new SequenceInputStream(en);
//创建一个输出流
FileOutputStream fos = new FileOutputStream("c:\\splitfiles\\0.bmp");
               byte[] buf = new byte[1024];
               int len = 0;
               while((len=sis.read(buf))!=-1)
               {
                       fos.write(buf,0,len);
               }
               fos.close();
               sis.close();
        }
        //切割文件
        public static void splitFile()throws IOException
        {
        //关联文件
        FileInputStream fis =  new FileInputStream("c:\\1.bmp");
               FileOutputStream fos = null;
               byte[] buf = new byte[1024*1024];//创建缓冲区为1M大

//此程序切割图片和Mp3没有问题,但是切割电影时可以使用切割也是用1M的空间,然后循环使用当100M时存入一个空间。

               int len = 0;
               int count = 1;//文件分割后的部分为1开始
               while((len=fis.read(buf))!=-1)
               {
//此时的文件路径最好不要写死,扩展名为部分文件
fos = new FileOutputStream("c:\\splitfiles\\"+(count++)+".part");
                       fos.write(buf,0,len);//写流
                       fos.close();//关流,循环一次创建一个流,并使用完毕后关闭该流
               }
               fis.close();
        }
}

12.17.3操作对象

12.17.3.1操作对象的流

      ObjectInputStream与ObjectOutputStream被操作的对象需要实现Serializable(标记接口);

对象本身存在于堆内存中,当我们的程序用完之后,内存被释放,堆内存的垃圾被回收,对象就会不存在。使用流将对象存放在硬盘上。

对象的持久化存储(对象的序列化,对象的可串行性),找一个介质能长期的存储保存数据。

12.17.3.2示例代码

import java.io.*;
class ObjectStreamDemo 
{
        public static void main(String[] args) throws Exception
        {
               //writeObj();
               readObj();
        }
//读取对象,读取第一个后,再次读的时候就是第二个对象(按顺序读就可以)
public static void readObj()throws Exception
        {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
//读取的是Object对象,然后强转       
        Person p = (Person)ois.readObject();
               System.out.println(p);
               ois.close();
        }
//写入对象,可以写多个对象(因为每个对象都有标记)
public static void writeObj()throws IOException
        {
//将一个对象写到文件中去,不是纯文本(看懂的字符),是字节文件。一般的对象文件名为:类名.object。如:person.object
               ObjectOutputStream oos = 
        new ObjectOutputStream(new FileOutputStream("person.object "));
//写对进硬盘象
               oos.writeObject(new Person("lisi0",399,"kr"));
               oos.close();
        }
}

12.17.3.3对象序列化和UID标识

import java.io.*;
//实现其接口可以启用其序列化功能,导包,没有方法的接口就是标记接口
class Person implements Serializable
{
//有一个UID标识,编译使用,Long型值。UID是根据类中的成员算出来的,所以要一一对应。若自己写个UID,则对应的使用该类,若该类的成员有所改变,但是使用该类的的文件也不会改变
//自定义的UID号42L可以变,给类定义个固定标识,方便序列化。
public static final long serialVersionUID = 42L; 
        private String name;
//对非静态成员不想序列化,可以加上一个关键字transient,保证其值在堆内存中存在,而不在文本文件中存在。
        transient int age; 
//静态是不能被序列化的,因为在方法区中,只能序列化堆内存中的对象。读取的时候就是初始值,不可在写入的方法中再次传入静态的变量值
        static String country = "cn"; 
        Person(String name,int age,String country)
        {
               this.name = name;
               this.age = age;
               this.country = country;
        }
        public String toString()
        {
               return name+":"+age+":"+country;
        }
}

12.17.4管道流

12.17.4.1管道流概述

PipedInputStream和PipedOutputStream输入输出可以直接进行连接,通过结合线程使用

12.17.4.2类声明

java.io
类 PipedInputStream

java.lang.Object
  java.io.InputStream
      java.io.PipedInputStream

所有已实现的接口:

Closeable


public class PipedInputStream
extends InputStream

管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏

从以下版本开始:

JDK1.0

另请参见:

PipedOutputStream

12.17.4.3示例代码

读和写不一定谁先谁后
import java.io.*;
//读的多线程
class Read implements Runnable
{
        private PipedInputStream in;
        Read(PipedInputStream in)//读的一个流
        {
               this.in = in;
        }
        public void run()
        {
               try
               {
                       byte[] buf = new byte[1024];
                       System.out.println("读取前。。没有数据阻塞");
                       int len = in.read(buf);
                       System.out.println("读到数据。。阻塞结束");
                       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
               {
               System.out.println("开始写入数据,等待6秒后。");
                       Thread.sleep(6000);
                       out.write("piped lai la".getBytes());//写数据
                       out.close();
               }
               catch (Exception e)
               {
                       throw new RuntimeException("管道输出流失败");
               }
        }
}
class  PipedStreamDemo
{
        public static void main(String[] args) throws IOException
        {
               PipedInputStream in = new PipedInputStream();//管道流
               PipedOutputStream out = new PipedOutputStream();
               in.connect(out);//两个流接上
               Read r = new Read(in);
                Write w = new Write(out);
               new Thread(r).start();
               new Thread(w).start();
        }
}

 

 

12.17.5RandomAccessFile

12.17.5.1 RandomAccessFile概述

RandomAccessFile随机访问文件,自身具备读写的方法。   通过skipBytes(intx),seek(int x)来到达随机访问

12.17.5.2RandomAccessFile类声明

java.io
类 RandomAccessFile

java.lang.Object
  java.io.RandomAccessFile

所有已实现的接口:

Closeable, DataInput, DataOutput


public class RandomAccessFile
extends Object
implements DataOutput, DataInput, Closeable

12.17.5.3RandomAccessFile类说明

此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过getFilePointer 方法读取,并通过seek 方法设置。

通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出 EOFException(是一种IOException)。如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾,则抛出IOException,而不是EOFException。需要特别指出的是,如果流已被关闭,则可能抛出IOException

从以下版本开始:

JDK1.0

12.17.5.4RandomAccessFile类概述

该类不是算是IO体系中子类。而是直接继承自Object。
但是它是IO包中成员。因为它具备读和写功能。内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流
 
通过构造函数可以看出,该类只能操作文件
而且操作文件还有模式:只读r,,读写rw等。
 
如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
如果模式rw。操作的文件不存在,会自动创建。如果存在则不会覆盖。

12.17.5.5RandomAccessFile功能是实现文件的分段写入

下载软件的原理,多线程的下载

12.17.5.6代码示例

import java.io.*;
class RandomAccessFileDemo 
{
        public static void main(String[] args) throws IOException
        {
               //writeFile_2();
               //readFile();
 
               //System.out.println(Integer.toBinaryString(258));
 
        }
public static void readFile()throws IOException
        {
        //读取一个文件
        RandomAccessFile raf = new RandomAccessFile("ran.txt","r");
        //调整对象中指针。可以随机取得任意一个数据,但是保证其规律
                //raf.seek(8*1);//写姓名是可以按8个字16个字节处理,年龄4个字节int型,则20个字节一个人
               //跳过指定的字节数,但是这个跳过只能往下跳,不能往回跳
               raf.skipBytes(8);
               byte[] buf = new byte[4];//创建一个字符数组
               raf.read(buf);//用读取文件读字节数组
               String name = new String(buf);//把数组变成字符串输出
               int age = raf.readInt();//读一个32位二进制数
               System.out.println("name="+name);
               System.out.println("age="+age);
               raf.close();//关流
        }
public static void writeFile_2()throws IOException
        {
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
        raf.seek(8*0);//随机的添加和修改(若指定位置有数据则修改)
               raf.write("周期".getBytes());
               raf.writeInt(103);
               raf.close();
        }
public static void writeFile()throws IOException//抛出异常
        {
//创建写入的文件对象
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
               raf.write("李四".getBytes());//写入字节数组
               raf.writeInt(97);//输出为a(字符),因为write方法只写了最低的八位而此字符的二进制位超出了8位。所以要使用writeInt()方法将字符的4个字节写出去。(记事本的一个空格代表一个字节)
               raf.write("王五".getBytes());
               raf.writeInt(99);
               raf.close();
        }
}

12.17.6操作基本数据类型

      DataInputStream与DataOutputStream

可以用于操作基本数据类型的数据的流对象。

将基本数据类型和流相结合。它的功能里面可以操作基本数据类型。所以初始化之后就有一个字节流

对于“你好”:

GBK编码之后写入的是4个字节,

UTF-8编码之后写入的是6个字节。

UTF-8修改版编码之后写入的是8个字节

12.17.6.1代码示例

import java.io.*;
class DataStreamDemo 
{
public static void main(String[] args) throws IOException
        {
               //writeData();
               //readData();
               //writeUTFDemo();
//             OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");// 
//             osw.write("你好");
//             osw.close();
//             readUTFDemo();
        }
public static void readUTFDemo()throws IOException
        {
               //创建一个流对象
               DataInputStream dis = new DataInputStream(new FileInputStream("utfdata.txt"));
               String s = dis.readUTF();//读成字符串
               System.out.println(s);
               dis.close();
        }
public static void writeUTFDemo()throws IOException
        {
               DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdata.txt"));//写入的是8个字节,UTF-8是读不出来的,原因是修改版做的,此方式写的只能用此对应的方式读出来
        dos.writeUTF("你好");//必须使用指定的编码。转换流不可以
               dos.close();
       }
public static void readData()throws IOException
        {
               DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));//读取的时候是依照写入的顺序
               int num = dis.readInt();
               boolean b = dis.readBoolean();
               double d = dis.readDouble();
               System.out.println("num="+num);
               System.out.println("b="+b);
               System.out.println("d="+d);
               dis.close();//关流
        }
        public static void writeData()throws IOException
        {
               DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));//记事本中编码
               dos.writeInt(234);//写了4个字节
               dos.writeBoolean(true);//写了1个字节
               dos.writeDouble(9887.543);//写了8个字节
               dos.close();
               ObjectOutputStream oos = null;
               oos.writeObject(new O());
        }
}

12.17.7操作字节数组

      ByteArrayInputStream与ByteArrayOutputStream

   操作字符数组

      CharArrayReader与CharArrayWriter

   操作字符串

      StringReader与StringWriter

        用于操作字节数组中数据的流对象。

12.17.7.1 ByteArrayInputStream和ByteArrayOutputStream比较

ByteArrayInputStream :在构造的时候,需要接收数据源。而且数据源是一个字节数组。
ByteArrayOutputStream: 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。
 
因为这两个流对象都操作的数组,并没有使用系统资源。
所以,不用进行close关闭。

12.17.7.2在流操作规律讲解

数据源设备,
        键盘 System.in,硬盘 FileStream,内存 ArrayStream。
数据目的设备:
        控制台 System.out,硬盘FileStream,内存 ArrayStream。
用流的读写思想来操作数据。

12.17.7.3代码示例

import java.io.*;
class ByteArrayStream 
{
        public static void main(String[] args) 
        {
               //数据源,为字节数组
               ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFD".getBytes());//数据源可以改变
               //数据目的,是一个数组,可以不用指定,可以指定大小
ByteArrayOutputStream bos = new ByteArrayOutputStream();
               int by = 0; 
              while((by=bis.read())!=-1) //读取数据
              {
                    bos.write(by);//写数据
              }
               System.out.println(bos.size());//打印缓冲区大小
               System.out.println(bos.toString());//转换成字符串
        //      bos.writeTo(new FileOutputStream("a.txt"));只有这个方法涉及到异常。
        }
}

12.18、字符编码

12.18.1字符编码的出现

      字符流的出现为了方便操作字符,更重要的是加入了编码转换

通过子类转换流来完成

      InputStreamReader,OutputStreamWriter在进行构造的时候可以加入字符集。PrintStream,PrintWrite也可以加入字符集(编码表),但是只能去打印,不可以去读取。所以还是以转换流为主。

12.18.2编码表的由来

计算机只能识别二进制数据,早期由来是电信号

   为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,使用10的不同组合,并一一对应,形成一张表

12.18.3常见的编码表(不区分大小写)

12.18.3.1 ASCII:美国标准信息交换码

      用一个字节的7位可以表示

12.18.3.2 ISO8859-1:拉丁码表(欧洲码表)

   用一个字节的8位表示,高位表示1 。不识别中文。

12.18.3.3 GB2312:中国的中文编码表

   两个字节的高位都是1,避免和ASCII重复。中国的码表兼容ASCII码表。包括中文的符号六七千个字。

12.18.3.4 GBK:中国的中文编码表升级

融合了更多(两万多,但是还在扩容)的中文字符号。

12.18.3.5 Unicode:国际标准码(融合了多种文字)

   所有文字都用两个字节(6535)来表示,java语言使用是就是unicode(char类型的编码)

12.18.3.6 UTF-8:Unicode转换格式以最小8位为单位

最多用三个字节来表示一个字符,在每一个字节的开头都加了一个标识符。但是同一个文字在UTF-8中和GBK中对应的数字不是同一个。那么就涉及到编码转换问题。

12.18.4转换流的编码应用

·可以将字符以指定编码格式存储

·可以对文本数据指定编码格式来解读

·指定编码表的动作有构造函数完成

12.18.4.1字符集转换示例代码(应当编码解码一致)

import java.io.*;
class EncodeStream 
{
public static void main(String[] args) throws IOException 
        {
                       //writeText();
                       readText();
        }
public static void readText()throws IOException 
        {
               InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"utf-8");
               char[] buf = new char[10];
               int len = isr.read(buf);
               String str = new String(buf,0,len);
               System.out.println(str);
               isr.close();
        }
public static void writeText()throws IOException 
        {
//编码,需要转换流
               OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"));//未指定,默认为GBK编码表。4个字节
               OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");//UTF-8编码,6个字节
               osw.write("你好");
               osw.close();
        }
}

12.18.5字符编码

12.18.5.1编码和解码

编码:字符串(人类熟悉的数据)à字节数组(电脑识别的数据)

String-->byte[];语法:str.getBytes(charsetName);指定编码表

   解码:字节数组à字符串

byte[] -->String;语法:new String(byte[],charsetName);

12.18.5.2乱码问题

      当编码错误时,就不要再去解码(使用不识别中文的去编码);
      当编码正确时,解码错误时,有可能使用其他的字符对应或者使用“?”对应错误编码中的一个字符。产生的是乱码。
解决乱码问题办法是:“用(错码)编一次,再用(正码)解一次”
      原理如下:客户端(默认GBK)发送由GBK编码而成的数组到Tomcat服务器(默认编码是ISO8859-1)。
      如果是get提交的话,必须,将Tomcat服务器返回的乱码,进行用Tomcat默认的ISO8859-1编码表进行编码,得到的数组,再用客户端的GBK字符集进行解码。
图示如下:
 
  
 
  
      如果是post提交的话,可以通过,服务器当中的一个对象的函数叫做set^Encoding,设置字符编码,指定字符集,会自动转换。
      可以将Tomcat服务器的码表改成GBK,这样做是可以的。但是,一个服务器中可以装好多web应用程序(网站),新浪使用GBK编码,搜狐使用UTF-8,当指定GBK是,搜狐全是乱码。
      当使用GBK编码正确时,然后使用UTF-8解码,错误后,在使用UTF-8进行编码,最后使用GBK进行解码后,会出现问题。原因是GBK和UTF-8都识别中文造成的。所以以后要注意这个问题。而(GBK和ISO8859-1)或者(UTF-8和ISO8859-1)就不会出现这个问题,因为ISO8859-1不识别中文。

12.18.5.3示例代码

import java.util.*;
class  EncodeDemo
{
        public static void main(String[] args)       
        {
               String s = "哈哈";
//编码返回一个字节数组,若指定一个字符集(异常),必须抛出异常。
               byte[] b1 = s.getBytes("GBK"); 
//数组变成字符串,(GBK,默认编码)结果是四个负数组成的一个数组;(UTF-8)结果是六个数字组成的一个数组。
                System.out.println(Arrays.toString(b1));//打印数组
//解码成字符串,若出现使用错误解码,如iso8859-1
               String s1 = new String(b1,"iso8859-1");
               System.out.println("s1="+s1);//解码正确,否则出现乱码
//对s1进行iso8859-1编码。返回原数组
               byte[] b2 = s1.getBytes("iso8859-1");
               System.out.println(Arrays.toString(b2));
//重新对原数组进行解码,使用正确的字符集
               String s2 = new String(b2,"gbk");
               System.out.println("s2="+s2);//解码正确
        }
}

12.18.5.4“联通”问题

问题描述:新建一个记事本文件,在里面写入“你好”,两个汉字,保存。然后打开,将“你好”改成“联通”然后保存。再次打开,则出现乱码。此时的编码为UTF-8。

字符流和转化字符流,在读取数据的时候是读取的编码数组,一次读取两个字节,然后去查字符集;查表一次返回一个中文,然后再读后两个字节。而字节流读取的方式是一个字节一个字节的读取。可是当编码数组使用的是UTF-8编码时,有的是两个字节表示一个文字,有的是三个字节表示一个文字。这时字符流读取数组的时候不太确定。

所以,UTF-8字符集对所有的字节加了一个标识头信息。当使用这个表示头信息的时候UTF-8就知道一次读取几个字节。

12.18.5.5UTF-8的标识头是UTF-8修改版

DataInput 和 DataOutput 接口的实现表示稍作改版的 UTF-8 格式的 Unicode 字符串。(关于标准 UTF-8 格式的信息,请参阅The Unicode Standard,Version 4.03.9Unicode Encoding Forms 节)。注意,在下表中,最高有效位显示在最左边的列中。

'\u0001''\u007F' 范围内的所有字符都是用单个字节表示的:

位值

字节 1

0

位 6-0

null 字符 '\u0000' 以及从 '\u0080''\u07FF' 的范围内的字符用两个字节表示:

位值

字节 1

1

1

0

位 10-6

字节 2

1

0

位 5-0


'\u0800''\uFFFF' 范围内的char 值用三个字节表示:

位值

字节 1

1

1

1

0

位 15-12

字节 2

1

0

位 11-6

字节 3

1

0

位 5-0

这种格式与标准 UTF-8 格式之间的不同如下:

  • null 字节 '\u0000' 是用 2-byte 格式而不是 1-byte 格式编码的,因此已编码的字符串中决不会有嵌入的 null。
  • 仅使用 1-byte、2-byte 和 3-byte 格式。
  • 增补字符是以代理项对的形式表示的。

从以下版本开始:

JDK1.0

另请参见:

DataInputStream,DataOutput

12.18.5.6“联通”问题示例代码

//联通的GBK编码数组的二进制表示形式,记事本软件在解码的时候由于其特殊性已经具备了UTF-8的标识头的特征,所以会使用其UTF-8的字符集进行解码,所以会出现乱码。解决办法前面只要有汉字就可以了。

class EncodeDemo2 
{
        public static void main(String[] args) throws Exception
        {
               String s = "联通";
               byte[] by = s.getBytes("gbk");
               for(byte b : by)
               {
        System.out.println(Integer.toBinaryString(b&255));
               }
               System.out.println("Hello World!");
        }
}

12.19章节练习

需求:
        有五个学生,每个学生有3门课的成绩,
        从键盘输入以上数据(包括姓名,三门课成绩),
        输入的格式:如:zhagnsan,30,40,60计算出总成绩,
     并把学生的信息和计算出的总分数高低顺序存放在磁盘文件"stud.txt"中。
 
1,描述学生对象。
2,定义一个可操作学生对象的工具类。
思想:
1,通过获取键盘录入一行数据,并将该行中的信息取出封装成学生对象。
2,因为学生对象有很多,那么就需要存储,使用到集合。因为要对学生对象的总分排序。所以可以使用TreeSet。
3,将集合的信息写入到一个文件中。
import java.io.*;//流操作
import java.util.*;//集合
class Student implements Comparable//学生具有比较性
{
        private String name;//学生姓名
        private int ma,cn,en;//定义三门成绩
        private int sum;//总分数
Student(String name,int ma,int cn,int en)//构造函数初始化
        {
               this.name = name;
               this.ma = ma;
               this.cn = cn;
               this.en = en;
               sum = ma + cn + en;//算出总成绩
        }
public int compareTo(Student s)//比较方法
        {
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
               if(num==0)
                       return this.name.compareTo(s.name);
               return num;
        }
public String getName()//获取姓名
        {
               return name;
        }
public int getSum()//获取总分
        {
               return sum;
        }
public int hashCode()//复写HashCode方法,
        {
                return name.hashCode()+sum*78;
        }
public boolean equals(Object obj)//复写equals方法
        {
               if(!(obj instanceof Student))//判断传入的对象
                       throw new ClassCastException("类型不匹配");
               Student s = (Student)obj;//强制转换
               return this.name.equals(s.name) && this.sum==s.sum;
        }
public String toString()//转换成字符串输出
        {
               return "student["+name+", "+ma+", "+cn+", "+en+"]";
        }
}
class StudentInfoTool
{
        public static Set getStudents()throws IOException
        {
               return getStudents(null);//无比较器的比较
        }
               public static Set getStudents(Comparator cmp)throws IOException//带比较器的比较
        {
               BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘输入
               String line = null;
               Set stus  = null;
               if(cmp==null)
                       stus = new TreeSet();
               else
                       stus = new TreeSet(cmp);
               while((line=bufr.readLine())!=null)
               {
                       if("over".equals(line))
                               break;
                       String[] info = line.split(",");
        Student stu = new Student(info[0],Integer.parseInt(info[1]),
                                       Integer.parseInt(info[2]),
                                       Integer.parseInt(info[3]));
                       stus.add(stu);
               }
               bufr.close();
               return stus;
        }
public static void write2File(Set stus)throws IOException
        {
               BufferedWriter bufw = new BufferedWriter(new FileWriter("stuinfo.txt"));
               for(Student stu : stus)
               {
                       bufw.write(stu.toString()+"\t");
                       bufw.write(stu.getSum()+"");
                       bufw.newLine();
                       bufw.flush();
               }
               bufw.close();
        }
}
class StudentInfoTest 
{
        public static void main(String[] args) throws IOException
        {
               Comparator cmp = Collections.reverseOrder();
               Set stus = StudentInfoTool.getStudents(cmp);
               StudentInfoTool.write2File(stus);
        }
}

你可能感兴趣的:(Java学习)