【Java】IO流

一、IO流基本概念

1.1、比特(Bit)、字节(Byte)、字符(Char)

  • Bit 位,是计算机最小的二进制单位 ,取0或1,主要用于计算机操作。

  • Byte 字节,是数据的最小单位,由8位bit组成,取值(-128-127),主要用于计算机操作数据。

  • Char 字符,是用户可读写的最小单位,由16位bit(2个byte)组成,取值(0-65535),主要用于用户操数数据。

1.2、IO流的概念

  1. 它是指数据从源头 流到 目的地,所以常把这种数据流叫做IO流。

  2. I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于 处理设备之间的数据传输。如读/写文件,网络通讯等。

  3. Java程序中,对于数据的输入/输出操作以“流(stream)” 的 方式进行。 

  4. java.io包下提供了各种“流”类和接口,用以获取不同种类的 数据,并通过标准的方法输入或输出数据。  

  5. 输入input: 读取外部数据(磁 盘、光盘等存储设备的数据)到 程序(内存)中。

  6. 输出output: 将程序(内存) 数据输出到磁盘、光盘等存储设 备中。

1.3、流的分类

  1. 按操作数据单位不同分为: 字节流(8 bit)字符流(16 bit) 

  2. 按数据流的流向不同分为: 输入流输出流 

  3. 按流的角色的不同分为: 节点流处理流

  

1.4、IO 流体系

    

  1、Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个 抽象基类派生的。

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

  

  IO流继承体系

  

二、字节流

2.1、Inpustream

  InputStream有read方法,一次读取一个字节,OutputStream的write方法一次写一个int。这两个类都是抽象类。意味着不能创建对象,那么需要找到具体的子类来使用。操作流的步骤都是:

  第一步:1:实例化File类对象

  第二步:2:打开流(即创建流)

  第三步:3:通过流读取内容

  第四步:4:用完后,关闭流资源

  案例一:使用 read()方法,一次读取一个字节,读到文件末尾返回-1.

 1 public void testFileInputStream() {
 2     // 1、实例化File类对象
 3     File file = new File("hello.txt");
 4     // 2、提供具体流
 5     FileInputStream fis = null;
 6     try {
 7         fis = new FileInputStream(file);
 8         // 3、数据读取
 9         // read():返回读入的一个字符。如果达到文件末尾了,返回-1
10         int data;
11         while ( (data=fis.read()) != -1) {
12             System.out.print((char) data);
13         }
14 
15     } catch (IOException e) {
16         e.printStackTrace();
17 
18     } finally {
19         // 4、关闭流
20         if (fis != null) {
21             try {
22                 fis.close();
23             } catch (IOException e) {
24                 e.printStackTrace();
25             }
26         }
27     }
28 }
View Code

  案例二(推荐使用):使用read()方法的时候,可以将读到的数据装入到字节数组中,相当于缓冲(提高效率),并循环读取(读完所有内容).

 1 public void testFileInputStream() {
 2     // 1、造文件
 3     File file = new File("hello.txt");
 4 
 5     // 2、造输入流
 6     FileInputStream fileInputStream = null;
 7     try {
 8         fileInputStream = new FileInputStream(file);
 9 
10         // 3、读数据
11         byte[] buffer = new byte[5];
12         int len;
13         while ((len = fileInputStream.read(buffer)) != -1) {
14             System.out.print(new String(buffer, 0, len));
15         }
16 
17     } catch (FileNotFoundException e) {
18         e.printStackTrace();
19     } catch (IOException e) {
20         e.printStackTrace();
21     } finally {
22         // 4、关闭流资源
23         if (fileInputStream != null) {
24             try {
25                 fileInputStream.close();
26             } catch (IOException e) {
27                 e.printStackTrace();
28             }
29         }
30     }
31 }
View Code  

2.2、OutputStream

  OutputStram 的write方法,一次只能写一个字节。成功的向文件中写入了内容。但是并不高效,如何提高效率呢?可以使用缓冲,在OutputStram类中有write(byte[] b)方法,将 b.length个字节从指定的 byte 数组写入此输出流中。

  操作步骤与读取类似

 1 public void testFileOutputStream() {
 2     // 1、提供File类对象,并指明写出的文件
 3     File file = new File("hello2.txt");
 4 
 5     // 2、提供FileOutputStream的对象,用于数据的写出
 6     FileOutputStream fos = null;
 7     try {
 8         // 第二个参数,是否在原文件上追加内容,默认为false
 9         fos = new FileOutputStream(file, true);
10 
11         // 3、写出的操作
12         fos.write("I have a Dream".getBytes());
13 
14     } catch (IOException e) {
15         e.printStackTrace();
16     } finally {
17         // 4、流资源的关闭
18         if (fos != null) {
19             try {
20                 fos.close();
21             } catch (IOException e) {
22                 e.printStackTrace();
23             }
24         }
25     }
26 
27 }
View Code 

2.3、输入输出流综合使用——文件拷贝实现 

  可以根据拷贝的需求调整数组的大小,一般是1024的整数倍。使用缓冲后效率大大提高。目前我们是抛出处理,一旦出现了异常,close就没有执行,也就没有释放资源。那么为了保证close的执行该如何处理呢。那么就需要使用try{} catch(){}finally{}语句。try中放入可能出现异常的语句,catch是捕获异常对象,fianlly是一定要执行的代码。

 1 public void testFileInputOutputStream() {
 2     // 1、创建File类的对象,指明读入和写出的文件
 3     File srcFile = new File("tomcat.png");
 4     File destFile = new File("tomcat2.png");
 5 
 6 
 7     // 2、创建输入流和输出流的对象
 8     FileInputStream fileInputStream = null;
 9     FileOutputStream fileOutputStream = null;
10     try {
11         fileInputStream = new FileInputStream(srcFile);
12         fileOutputStream = new FileOutputStream(destFile);
13 
14         // 3、数据的读入和写出操作
15         byte[] cbuf = new byte[1024];
16         int len;
17         while ((len = fileInputStream.read(cbuf)) != -1) {
18             fileOutputStream.write(cbuf, 0, len);
19         }
20     } catch (FileNotFoundException e) {
21         e.printStackTrace();
22     } catch (IOException e) {
23         e.printStackTrace();
24     } finally {
25 
26         // 4、关闭流资源
27         if (fileOutputStream != null) {
28             try {
29                 fileOutputStream.close();
30             } catch (IOException e) {
31                 e.printStackTrace();
32             }
33         }
34         if (fileInputStream != null) {
35             try {
36                 fileInputStream.close();
37             } catch (IOException e) {
38                 e.printStackTrace();
39             }
40         }
41 
42     }
43 }

三、字符流

  计算机并不区分二进制文件与文本文件。所有的文件都是以二进制形式来存储的,因此,从本质上说,所有的文件都是二进制文件。所以字符流是建立在字节流之上的,它能够提供字符层次的编码和解码。可以说字符流就是:字节流 + 编码表,为了更便于操作文字数据。字符流的抽象基类:Reader , Writer。由这些类派生出来的子类名称都是以其父类名作为子类名的后缀,如FileReader、FileWriter

文件拷贝实现示例
 1 public void testFileReaderFileWriter() {
 2     // 1、创建File类的对象,指明读入和写出的文件
 3     File srcFile = new File("hello.txt");
 4     File destFile = new File("hello2.txt");
 5 
 6     // 2、创建输入流和输出流的对象
 7     FileReader fileReader = null;
 8     FileWriter fileWriter = null;
 9     try {
10         fileReader = new FileReader(srcFile);
11         fileWriter = new FileWriter(destFile);
12 
13         // 3、数据的读入和写出操作
14         char[] cbuf = new char[5];
15         int len;
16         while ((len = fileReader.read(cbuf)) != -1) {
17             fileWriter.write(cbuf, 0, len);
18         }
19     } catch (FileNotFoundException e) {
20         e.printStackTrace();
21     } catch (IOException e) {
22         e.printStackTrace();
23     } finally {
24 
25         // 4、关闭流资源
26         if(fileWriter != null ){
27             try {
28                 fileWriter.close();
29             } catch (IOException e) {
30                 e.printStackTrace();
31             }
32         }
33         if (fileReader != null) {
34             try {
35                 fileReader.close();
36             } catch (IOException e) {
37                 e.printStackTrace();
38             }
39         }
40 
41     }
42 }
* 结论:
* 1、对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
* 2、对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt),使用字节流文件

四、缓冲流

  Java其实提供了专门的字节流缓冲来提高效率。BufferedInputStream 和 BufferedOutputStream。BufferedOutputStreamBufferedOutputStream类可以通过减少读写次数来提高输入和输出的速度。它们内部有一个缓冲区,用来提高处理效率。查看API文档,发现可以指定缓冲区的大小。其实内部也是封装了字节数组。没有指定缓冲区大小,默认的字节是8192。显然缓冲区输入流和缓冲区输出流要配合使用。首先缓冲区输入流会将读取到的数据读入缓冲区,当缓冲区满时,或者调用flush方法,缓冲输出流会将数据写出。
注意:当然使用缓冲流来进行提高效率时,对于小文件可能看不到性能的提升。但是文件稍微大一些的话,就可以看到实质的性能提升了。

  文件拷贝实现示例

 1 public void bufferedStreamTest(){
 2     // 1、造文件
 3     File srcFile = new File("hello.txt");
 4     File destFile = new File("hello2.txt");
 5 
 6     // 2、造流
 7     // 2.1、造节点流
 8     FileInputStream fis = null;
 9     FileOutputStream fos = null;
10     BufferedInputStream bis = null;
11     BufferedOutputStream bos = null;
12     try {
13         fis = new FileInputStream(srcFile);
14         fos = new FileOutputStream(destFile);
15 
16         // 2.2、造缓冲流
17         bis = new BufferedInputStream(fis);
18         bos = new BufferedOutputStream(fos);
19 
20         // 3、复制的细节:读取、写入
21         byte[] buffer = new byte[1024];
22         int len;
23         while ((len = bis.read(buffer)) != -1) {
24             bos.write(buffer, 0, len);
25 
26             // 刷新缓冲区,手动将buffer中的数据写入文件
27 //                bos.flush();
28         }
29     } catch (FileNotFoundException e) {
30         e.printStackTrace();
31     } catch (IOException e) {
32         e.printStackTrace();
33     } finally {
34 
35         // 4、资源关闭
36         // 要求:先关闭外层的流,在关闭内层的流
37         if(bos != null) {
38             try {
39                 bos.close();
40             } catch (IOException e) {
41                 e.printStackTrace();
42             }
43         }
44         if(bis != null) {
45             try {
46                 bis.close();
47             } catch (IOException e) {
48                 e.printStackTrace();
49             }
50         }
51         // 说明:在关闭外层流的同时,内层流也会自动的进行关闭
52         // 关闭内层流的操作,可以忽略
53 //        fos.close();
54 //        fis.close();
55     }
56 }

五、转换流

1、转换流提供了在字节流和字符流之间的转换

  2、Java API提供了两个转换流:

    1)InputStreamReader:将InputStream转换为Reader  

    2)OutputStreamWriter:将Writer转换为OutputStream

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

  4、很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能。

  示例代码如下:

 1 public void test2() {
 2     InputStreamReader isr = null;
 3     OutputStreamWriter osw = null;
 4 
 5     try {
 6         FileInputStream fis = new FileInputStream("hello.txt");
 7         FileOutputStream fos = new FileOutputStream("hello2.txt");
 8 
 9         isr = new InputStreamReader(fis, "utf-8");
10         osw = new OutputStreamWriter(fos, "gbk");
11 
12         char[] cbuf = new char[5];
13         int len;
14         while ((len = isr.read(cbuf)) != -1) {
15             String str = new String(cbuf, 0, len);
16             System.out.print(str);
17             osw.write(str);
18         }
19 
20     } catch (FileNotFoundException e) {
21         e.printStackTrace();
22     } catch (UnsupportedEncodingException e) {
23         e.printStackTrace();
24     } catch (IOException e) {
25         e.printStackTrace();
26     } finally {
27         if(osw != null){
28             try {
29                 osw.close();
30             } catch (IOException e) {
31                 e.printStackTrace();
32             }
33         }
34         if (isr != null) {
35             try {
36                 isr.close();
37             } catch (IOException e) {
38                 e.printStackTrace();
39             }
40 
41         }
42     }
43 }
View Code

六、其他流介绍

6.1、标准输入、输出流(了解)

  •   System.in和System.out分别代表了系统标准的输入和输出设备

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

  •   System.in的类型是InputStream

  •   System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类

  •   重定向:通过System类的setIn,setOut方法对默认设备进行改变。

       public static void setIn(InputStream in)
       public static void setOut(PrintStream out)

6.2、打印流(了解)

   实现将基本数据类型的数据格式转化为字符串输出

  打印流:PrintStream和PrintWriter

  •   提供了一系列重载的print()和println()方法,用于多种数据类型的输出

  •   PrintStream和PrintWriter的输出不会抛出IOException异常

  •   PrintStream和PrintWriter有自动flush功能

  •   PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。

      在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。

  •   System.out返回的是PrintStream的实例

6.3、数据流(了解)

  1、为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流

  2、数据流有两个类:(用于读取和写出基本数据类型、String类的数据) 

    DataInputStream 和 DataOutputStream

    分别“套接”在 InputStream 和 OutputStream 子类的流上

  3、DataInputStream中的方法

    • boolean readBoolean()
    • char readChar()
    • double readDouble()
    • long readLong()
    • String readUTF()
    • byte readByte()
    • float readFloat()
    • short readShort()
    • int readInt()
    • void readFully(byte[] b)

  4、DataOutputStream中的方法
    将上述的方法的read改为相应的write即可。

 1 /**
 2  * 数据流 DataInputStream 和 DataOutputStream
 3  * 作用:用于读取和写出基本数据类型的变量或字符串
 4  *
 5  */
 6 public void testDataOutputStream(){
 7     DataOutputStream dos = null;
 8     try {
 9         dos = new DataOutputStream(new FileOutputStream("d.txt"));
10 
11         dos.writeUTF("中国人");
12         dos.flush(); // 刷新操作,一定执行就会将数据写入文件
13         dos.writeInt(12);
14         dos.writeDouble(1.1);
15         dos.writeBoolean(true);
16     } catch (FileNotFoundException e) {
17         e.printStackTrace();
18     } catch (IOException e) {
19         e.printStackTrace();
20     } finally {
21         if(dos != null) {
22             try {
23                 dos.close();
24             } catch (IOException e) {
25                 e.printStackTrace();
26             }
27         }
28     }
29 
30 }
31 
32 public void testDataInputStream(){
33     DataInputStream dis = null;
34     try {
35         dis = new DataInputStream(new FileInputStream("d.txt"));
36 
37         String str = dis.readUTF();
38         System.out.println(str);
39         int num = dis.readInt();
40         System.out.println(num);
41         double dou = dis.readDouble();
42         System.out.println(dou);
43         boolean b = dis.readBoolean();
44         System.out.println(b);
45     } catch (FileNotFoundException e) {
46         e.printStackTrace();
47     } catch (IOException e) {
48         e.printStackTrace();
49     } finally {
50         if(dis != null) {
51             try {
52                 dis.close();
53             } catch (IOException e) {
54                 e.printStackTrace();
55             }
56         }
57     }
58 
59 }
View Code

6.4、对象流

  ObjectInputStream和OjbectOutputSteam

  用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

  序列化:用ObjectOutputStream类保存基本类型数据或对象的机制

  反序列化:用ObjectInputStream类读取基本类型数据或对象的机制

  ObjectOutputStream和ObjectInputStream不能序列化static和transient修 饰的成员变量

  使用参考:【Java】Java对象的序列化和反序列化

6.5、随机存取文件流

RandomAccessFile 类

  RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也 可以写。

  RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意 地方来读、写文件

    > 支持只访问文件的部分内容

    > 可以向已存在的文件后追加内容

  RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置

  RandomAccessFile 类对象可以自由移动记录指针

    > long getFilePointer():获取文件记录指针的当前位置

    > void seek(long pos):将文件记录指针定位到 pos 位置

  代码示例 

  1 /**
  2  * RandomAccessFile的使用
  3  * 1、RandomAccessFile直接继承与Java.lang.Object类,实现类DataInput和Output两个接口
  4  * 2、RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
  5  * 3、如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建
  6  * 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头开始)
  7  */
  8 public class RandomAccessFileTest {
  9 
 10     public static void main(String[] args) {
 11         RandomAccessFileTest test = new RandomAccessFileTest();
 12         test.test3();
 13     }
 14 
 15 
 16     public void test1() {
 17         RandomAccessFile raf1 = null;
 18         RandomAccessFile raf2 = null;
 19         try {
 20             raf1 = new RandomAccessFile("hello.txt", "r");
 21             raf2 = new RandomAccessFile("hello2.txt", "rw");
 22 
 23             byte[] buff = new byte[5];
 24             int len;
 25             while ((len = raf1.read(buff)) != -1) {
 26                 raf2.write(buff, 0, len);
 27                 System.out.print(new String(buff, 0, len));
 28             }
 29         } catch (FileNotFoundException e) {
 30             e.printStackTrace();
 31         } catch (IOException e) {
 32             e.printStackTrace();
 33         } finally {
 34             if (raf2 != null) {
 35                 try {
 36                     raf2.close();
 37                 } catch (IOException e) {
 38                     e.printStackTrace();
 39                 }
 40             }
 41 
 42             if (raf1 != null) {
 43                 try {
 44                     raf1.close();
 45                 } catch (IOException e) {
 46                     e.printStackTrace();
 47                 }
 48             }
 49 
 50         }
 51     }
 52 
 53 
 54     public void test2() {
 55         RandomAccessFile raf2 = null;
 56         try {
 57             raf2 = new RandomAccessFile("hello2.txt", "rw");
 58             // 将指针调到角标为3的位置
 59             raf2.seek(3);
 60             raf2.write("xyz".getBytes());
 61         } catch (FileNotFoundException e) {
 62             e.printStackTrace();
 63         } catch (IOException e) {
 64             e.printStackTrace();
 65         } finally {
 66             if (raf2 != null) {
 67                 try {
 68                     raf2.close();
 69                 } catch (IOException e) {
 70                     e.printStackTrace();
 71                 }
 72             }
 73         }
 74     }
 75 
 76 
 77     /**
 78      * 使用RandomAccessFile实现插入效果
 79      */
 80     public void test3() {
 81         RandomAccessFile raf2 = null;
 82         ByteArrayOutputStream bos =null;
 83         try {
 84             raf2 = new RandomAccessFile("hello2.txt", "rw");
 85             // 将指针调到角标为3的位置
 86             raf2.seek(3);
 87 
 88 //            // 方式一,使用StringBuilder
 89 //            StringBuilder sb =new StringBuilder("NBA");
 90 //            byte[] buff = new byte[5];
 91 //            int len;
 92 //            while ((len = raf2.read(buff)) != -1) {
 93 //                sb.append(new String(buff, 0, len));
 94 //            }
 95 
 96             // 方式二,使用ByteArrayOutputStream
 97             bos = new ByteArrayOutputStream();
 98             bos.write("CBA".getBytes());
 99             byte[] buff = new byte[5];
100             int len;
101             while ((len = raf2.read(buff)) != -1) {
102                 bos.write(buff, 0, len);
103             }
104 
105             // 指针指回插入位置
106             raf2.seek(3);
107             raf2.write(bos.toByteArray());
108 
109         } catch (FileNotFoundException e) {
110             e.printStackTrace();
111         } catch (IOException e) {
112             e.printStackTrace();
113         } finally {
114             if (bos != null) {
115                 try {
116                     bos.close();
117                 } catch (IOException e) {
118                     e.printStackTrace();
119                 }
120             }
121             if (raf2 != null) {
122                 try {
123                     raf2.close();
124                 } catch (IOException e) {
125                     e.printStackTrace();
126                 }
127             }
128         }
129     }
130 }
View Code

 

原文地址:https://www.cnblogs.com/h--d/p/14163423.html