【SanSui】javaIO各种流分析

来源:互联网 发布:华为ec6108v9 安装软件 编辑:程序博客网 时间:2024/05/12 03:07

早就想彻底把java各种IO流研究一遍了,这次网校大师让我去封装android的IO和http,既然是网校第一次给我的任务,那我当然要好好对待下啦,于是我花了3天时间努力查看了很多资料,写了很多Demo才弄懂了很多流的机制,不过话说对于封装android的Io流帮助不算太大(只是让我尽量向小的封装吧,好转换流),不过好处还是大大的,至少我知道什么情况用什么流比较适合了;

话不多说,先介绍下要用到什么流:

InputStream/OutputStream

BufferedReader流

FileOutputStream/FileInputStream流

PrintWriter流

DataOut/DataIn流

Object流

FileChannel类;


1. InputStream/OutputStream抽象类

所有流的基类(除了RandomAccessFile)

字节流 拥有方法read和Writer,则可以支持任意二进制文件的读写;

可以被封包为字符流,或者进一步封包为字符缓冲流

因为其功能强大所以经常被直接当做返回值,至于后面的转化要看具体需求;


Demo我就不贴了,网上有大把方法,而且转化的流不一样,几行很难全部概括

2.BufferedReader

强大的字符流,也可以说是最快最好用的字符流,其强大的Readline方法简直恐怖

使用demo:

public static String httpGetDownload(int RunOutTime,String UrlPath) throws IOException{URL url = new URL(UrlPath);HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();httpConn.setRequestMethod("GET");httpConn.setConnectTimeout(RunOutTime);InputStream in = httpConn.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(in));String result = null ;String line = null ;while((line = br.readLine()) != null){result = result + line;}br.close();in.close();return result ;}
注释就不写了,想必大家都看的懂;至于readline的好处呢,首先他很快,放于缓冲区里面快速的读写;其二呢是它不用考虑末尾byte的缓冲问题,一行一行读取;不像字节流还需要考虑到末尾byte的处理;要是处理不好就会导致IO文件不一致等问题;


3.FileOutputStream/FileInputStream流

这个流是继承于InputStream和OutputStream的流,其内的read和writer是直接使用父类的方法,但是在其内自身封装了很多很好用的方法:比如其构造方法就是内部调用File类直接创建File,等等;但其实内部实现还是一个字节流

字节流简单使用demo:

public static void httpGetDownload(String UrlPath,String dirPath,String fileName,int RunOutTime) throws IOException{URL url = new URL(UrlPath);HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();httpConn.setRequestMethod("GET");httpConn.setConnectTimeout(RunOutTime);InputStream in = httpConn.getInputStream();int length = 0 ;FileOutputStream fis = new FileOutputStream("d:\\demo.dat")while((length = in.read()) != -1 ){fis.write(length);}in.close();}

这是字节流最简单的用法,下面我们写个带byte的字节流 :  更快更好用

public static void httpGetDownload(String UrlPath,String dirPath,String fileName,int RunOutTime) throws IOException{URL url = new URL(UrlPath);HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();httpConn.setRequestMethod("GET");httpConn.setConnectTimeout(RunOutTime);InputStream in = httpConn.getInputStream();byte[] buffer = new byte[1024];int length = 0 ;FileOutputStream fis = new FileOutputStream("d:\\demo.dat")while((length = in.read(buffer)) != -1 ){fis.write(buffer,0,length);}in.close();}

我们看到FileOutputStream其构造函数是可以直接创建一个File的;这一点是OutputStream做不到的;



4.PrintWriter流

最好用的文本输入流,他说封装的Println是可以直接进行文本输入的

其实这个类主要讲三点:A.他构造方法的auto-flush的含义

     B.其与System.out.println的区别

     C.PrintWriter如何用文本保存对象;


A. auto-flush,也就是PrintWriter构造方法的第二个参数,那么具体的的实际意识是什么呢?

   书上给了这么一个解释:如果设置了自动清空模式,则只要Println(Println其实就是一个简单的Print加一个回车换行,win的话是"\n\r")调用,那么缓冲区的所有字符都会被发送到它们的目的地;

测试demo:

  

/* 讨论PrintWriter的自动清空; */ PrintWriter out = new PrintWriter("Test.dat");out.println("This is a Test");out.close();/* * 上面的有close和下面的自动清空模式一样 */ PrintWriter out = new PrintWriter(new FileOutputStream("Test.dat"),true);out.println("This is a Test2");

然后跑完一遍之后,你会发现上面的不加close你的println里面的内容永远不会到目的地,然而下面如果不加的话确实没有影响;

效果很显然,其实这就是PrintWriter这个类的特殊性,其他流都是当io结束后给你close掉释放一些内存资源,而PrintWriter调用close的时候却是让你将所有缓冲资源都送达目的地;

B.讨论其与System.out.println的区别

测试demo:


/*PrintWriter和System.out.println的区别; *  *PrintWriter out = new PrintWriter("Test.dat");FileInputStream fis = new FileInputStream("D:\\eclipese\\RedRock_IOSubject\\TestInput.txt");int bytesAvailable = fis.available();while(bytesAvailable > 0){byte[] data = new byte[bytesAvailable];fis.read(data);out.write();}int c = 0 ;while((c = fis.read()) != -1){out.write((char)c);}fis.close();out.close();*/

   不难看出其实PrintWriter还是可以调用原生质的writer(int)和writer(byte[])()这两个输出方法的,而System.out.println确实传统意义上的流,可以说是PrintWriter的一个很小的功能点;


C.PrintWirter文本记录对象

其实这也可以使PrintWriter的一个很好地使用了,但是只能做简单使用;

不多说上demo:


public class TestPrintWriter {/** * @desc PrintWriter流 * @author SanSui  * @throws IOException  */public static void main(String[] args) throws IOException  {try {/* 利用PrintWriter的文本格式保存对象, * 再通过一行一行的读来将该对象读出来,并用spilt和正则进行分割; */Employee[] staff = new Employee[3];staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);PrintWriter out = new PrintWriter("employee.dat");writeData(staff, out);out.close();Scanner in = new Scanner(new FileReader("employee.dat"));Employee[] newStaff = readData(in);in.close();         for (Employee e : newStaff)System.out.println(e);} catch(IOException e){e.printStackTrace();}}private static void writeData(Employee[] employees, PrintWriter out) throws IOException {out.println(employees.length);for (Employee e : employees)e.writeData(out);}private static Employee[] readData(Scanner in) {int n = in.nextInt();in.nextLine();Employee[] employees = new Employee[n];for (int i = 0; i < n; i++) {employees[i] = new Employee();employees[i].readData(in);}return employees;}}class Employee{private String name;private double salary;private Date hireDay;public Employee(){}public Employee(String n, double s, int year, int month, int day){name = n;salary = s;GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);hireDay = calendar.getTime();}public String getName(){return name;}public double getSalary(){return salary;}public Date getHireDay(){return hireDay;}public void raiseSalary(double byPercent){double raise = salary * byPercent / 100;salary += raise;}public String toString(){return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay+ "]";}public void writeData(PrintWriter out) {GregorianCalendar calendar = new GregorianCalendar();calendar.setTime(hireDay);out.println(name + "|" + salary + "|" + calendar.get(Calendar.YEAR) + "|"+ (calendar.get(Calendar.MONTH) + 1) + "|" + calendar.get(Calendar.DAY_OF_MONTH));}public void readData(Scanner in){//永航解析来返回一行String line = in.nextLine();//再用正则表达式来将对象的信息进行筛选;String[] tokens = line.split("\\|");name = tokens[0];salary = Double.parseDouble(tokens[1]);int y = Integer.parseInt(tokens[2]);int m = Integer.parseInt(tokens[3]);int d = Integer.parseInt(tokens[4]);GregorianCalendar calendar = new GregorianCalendar(y, m - 1, d);hireDay = calendar.getTime();}}

如图所示:然后你会在当前项目的文件夹下面发现一个文件:

我们将文件打开查看


3Carl Cracker|75000.0|1987|12|15Harry Hacker|50000.0|1989|10|1Tony Tester|40000.0|1990|3|15


这就是对象的信息了,然后只需要我们readline一下再拿正则表达式+Split这个类进行区分就可以得到该对象了;


5.DataOut/DataIn流

这是读写二进制数据的流,其内定义了各种方法用来以二进制格式读写数组,字符等等;这个流是直接与二进制文件打交道的,所以对二进制文件的IO操作,该类确实好用;

而因其实二进制直接写入,写入数据为某一特定对象,我们就可以使用RandomAccessFile类的seek方法直接操作(seek方法可以将文件指针设置到文件内部任何一个位置),由于seek方法的特殊性,我只需要要找到该二进制对象的大小(每个一样大),然后将指针指向就可以成功读取对象了;


public class RandomFileTest{     public static void main(String[] args)   {      Employee[] staff = new Employee[3];      staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);      staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);      try      {           //将所有对象保存在工程文件夹的employee.dat中         DataOutputStream out = new DataOutputStream(new FileOutputStream("employee.dat"));         for (Employee e : staff)            e.writeData(out);         out.close();               // 将所有对象存储在一个array数组里面         RandomAccessFile in = new RandomAccessFile("employee.dat", "r");            // 计算数组内容个数         int n = (int)(in.length() / Employee.RECORD_SIZE);         Employee[] newStaff = new Employee[n];         // 将指针指向每个每个对象的头         for (int i = n - 1; i >= 0; i--)         {              newStaff[i] = new Employee();            in.seek(i * Employee.RECORD_SIZE);            newStaff[i].readData(in);         }         in.close();                  // 将每个对象打印出来         for (Employee e : newStaff)             System.out.println(e);      }      catch (IOException e)      {           e.printStackTrace();       }   }}class Employee{   public Employee() {}   public Employee(String n, double s, int year, int month, int day)   {        name = n;      salary = s;      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);      hireDay = calendar.getTime();   }   public String getName()   {      return name;   }   public double getSalary()   {      return salary;   }   public Date getHireDay()   {        return hireDay;   }   public void raiseSalary(double byPercent)   {        double raise = salary * byPercent / 100;      salary += raise;   }   public String toString()   {        return getClass().getName()         + "[name=" + name         + ",salary=" + salary         + ",hireDay=" + hireDay         + "]";   }   public void writeData(DataOutput out) throws IOException   {      DataIO.writeFixedString(name, NAME_SIZE, out);      out.writeDouble(salary);      GregorianCalendar calendar = new GregorianCalendar();      calendar.setTime(hireDay);      out.writeInt(calendar.get(Calendar.YEAR));      out.writeInt(calendar.get(Calendar.MONTH) + 1);      out.writeInt(calendar.get(Calendar.DAY_OF_MONTH));   }   public void readData(DataInput in) throws IOException   {      name = DataIO.readFixedString(NAME_SIZE, in);      salary = in.readDouble();      int y = in.readInt();      int m = in.readInt();      int d = in.readInt();      GregorianCalendar calendar = new GregorianCalendar(y, m - 1, d);      hireDay = calendar.getTime();   }   public static final int NAME_SIZE = 40;   public static final int RECORD_SIZE = 2 * NAME_SIZE + 8 + 4 + 4 + 4;   private String name;   private double salary;   private Date hireDay;}class DataIO{   public static String readFixedString(int size, DataInput in)       throws IOException   {        StringBuilder b = new StringBuilder(size);      int i = 0;      boolean more = true;      while (more && i < size)      {           char ch = in.readChar();         i++;         if (ch == 0) more = false;         else b.append(ch);      }      in.skipBytes(2 * (size - i));      return b.toString();   }   public static void writeFixedString(String s, int size, DataOutput out)       throws IOException   {      for (int i = 0; i < size; i++)      {           char ch = 0;         if (i < s.length()) ch = s.charAt(i);         out.writeChar(ch);      }   }}

很好用是不是;


6.Object流(貌似现在已经被json取代了);

简单一点说就是,该类封装了对实现了寻列花的对象保存和读写的各种方法,当然事实肯定不是那么简单,java里面封装了很多细节不让用户看到,

整体机制:

将对象从内存中拿出——>经过各种处理保存在文件里——>其他人可以通过相同对象返回值取得该保存对象l

这是一套很方便且很严谨的机制(至少比上面两个保存数据的方式严谨多了)

其内封装的细节:

利用对象序列化保存文件格式:1.类名, 2.序列化版本的唯一ID 3.描述序列化方法的标志集4.对数据域的描述;

具体的我也不了解,简单来说就是通过这个类的特殊性(可能一个属性或者方法就会改变特性)然后生成一种序列化的ID指纹(其内封装了很多涉及安全的算法)然后保存在硬盘中,而后当读取的时候也是通过ID来识别是否为同一个类,若是的话就会被读取;


简单demo:


class ObjectStreamTest{   public static void main(String[] args)   {      Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);      Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);      carl.setSecretary(harry);      Manager tony = new Manager("Tony Tester", 40000, 1990, 3, 15);      tony.setSecretary(harry);      Employee[] staff = new Employee[3];      staff[0] = carl;      staff[1] = harry;      staff[2] = tony;      try      {         //将该对象保存在employee.dat文件下         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.dat"));         out.writeObject(staff);         out.close();         //读出         ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.dat"));         Employee[] newStaff = (Employee[]) in.readObject();         in.close();
         //将读出的对象打印出来         for (Employee e : newStaff)            System.out.println(e);      }      catch (Exception e)      {         e.printStackTrace();      }   }}class Employee implements Serializable{   public Employee()   {   }   public Employee(String n, double s, int year, int month, int day)   {      name = n;      salary = s;      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);      hireDay = calendar.getTime();   }   public String getName()   {      return name;   }   public double getSalary()   {      return salary;   }   public Date getHireDay()   {      return hireDay;   }   public void raiseSalary(double byPercent)   {      double raise = salary * byPercent / 100;      salary += raise;   }   public String toString()   {      return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay            + "]";   }   private String name;   private double salary;   private Date hireDay;}class Manager extends Employee{   public Manager(String n, double s, int year, int month, int day)   {      super(n, s, year, month, day);      secretary = null;   }   public void setSecretary(Employee s)   {      secretary = s;   }   public String toString()   {      return super.toString() + "[secretary=" + secretary + "]";   }   private Employee secretary;}

调用ObjectStream是否非常简单呢?其实就是两步:一个writerObject,一个readObejct是不是很简单呢,java已经帮你封装了细节了;

我们查看下employee.dat

 ur [LEmployee;6艖?  xp   sr Manager6?c廦? L secretaryt LEmployee;xr Employee嬉唥 D salaryL hireDayt Ljava/util/Date;L namet Ljava/lang/String;xp@髨     sr java.util.Datehj?KYt  xpw   冮9?xt Carl Crackersq ~ @鑚     sq ~ w   ?N眬xt Harry Hackerq ~ sq ~ @銏     sq ~ w   攎>?xt Tony Testerq ~ 

一堆乱七八糟的, 加了密的二进制文件;



7.FileChannel类:

这个是nio新封装的类,全名叫做文件映射内存,简单来说就是用虚拟内存将一个文件或者文件的一部分映射到内存里面去,然后这个文件就可以被当做内存数组一样访问了;

注意:这里的前提是文件,也就是说不是所有流都可以使用这套机制的,我就是在这上面吃了大亏的,因为我想将从网络上获得inputStream也放进去直接快速读写,没想到更本成功不了,Java原来封装的目的也想到了是用来对文件进行快速读写的,所以能够打开FileChannel这个类的流只有是FileOutputStream 和RandomAccessFile ; 

下面一个demo简单的测试一下FileChannel的高效性即使用:

public class NIOTest{   public static long checksumInputStream(String filename) throws IOException   {      InputStream in = new FileInputStream(filename);      CRC32 crc = new CRC32();      int c;      while ((c = in.read()) != -1)         crc.update(c);      return crc.getValue();   }   public static long checksumBufferedInputStream(String filename) throws IOException   {      InputStream in = new BufferedInputStream(new FileInputStream(filename));      CRC32 crc = new CRC32();      int c;      while ((c = in.read()) != -1)         crc.update(c);      return crc.getValue();   }   public static long checksumRandomAccessFile(String filename) throws IOException   {      RandomAccessFile file = new RandomAccessFile(filename, "r");      long length = file.length();      CRC32 crc = new CRC32();      for (long p = 0; p < length; p++)      {         file.seek(p);         int c = file.readByte();         crc.update(c);      }      return crc.getValue();   }   public static long checksumMappedFile(String filename) throws IOException   {      FileInputStream in = new FileInputStream(filename);      FileChannel channel = in.getChannel();      CRC32 crc = new CRC32();      int length = (int) channel.size();      MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, length);      for (int p = 0; p < length; p++)      {         int c = buffer.get(p);         crc.update(c);      }      return crc.getValue();   }   public static void main(String[] args) throws IOException   {      System.out.println("Input Stream:");      long start = System.currentTimeMillis();      long crcValue = checksumInputStream(args[0]);      long end = System.currentTimeMillis();      System.out.println(Long.toHexString(crcValue));      System.out.println((end - start) + " milliseconds");      System.out.println("Buffered Input Stream:");      start = System.currentTimeMillis();      crcValue = checksumBufferedInputStream(args[0]);      end = System.currentTimeMillis();      System.out.println(Long.toHexString(crcValue));      System.out.println((end - start) + " milliseconds");      System.out.println("Random Access File:");      start = System.currentTimeMillis();      crcValue = checksumRandomAccessFile(args[0]);      end = System.currentTimeMillis();      System.out.println(Long.toHexString(crcValue));      System.out.println((end - start) + " milliseconds");      System.out.println("Mapped File:");      start = System.currentTimeMillis();      crcValue = checksumMappedFile(args[0]);      end = System.currentTimeMillis();      System.out.println(Long.toHexString(crcValue));      System.out.println((end - start) + " milliseconds");   }}

这个demo简单使用了各种io流对文件进行crc操作的速度,结果自己可以跑一下..


8.总结

其实以上列出了这么多类,我整个的总结一下,其总体可以分三个类:

1.字节流 2.字符流 3.功能流 ;每个流功能不一,按需求使用吧!



本文demo下载地址:

demo下载请点击这里

0 0