JAVA基础之I/O体系及常用类

I/O流的概念

I/O流 即输入Input流/ 输出Output流的缩写,常见的是电脑屏幕输出设备和键盘鼠标输入设备,其广义上的定义就是:数据在内部存储器和外部存储器或其他周边设备之间的输入和输出;即:数据/ 输入/ 输出。

流是一个抽象但形象的概念,Java中把不同的输入/输出源(键盘,文件,网络连接等)抽象的表述为 “流”(Stream)。可以简单理解成一个数据的序列,输入流表示从一个源读取数据,输出流则表示向一个目标写数据,在Java程序中,对于数据的输入和输出都是采用 “流” 这样的方式进行的。

I/O流的分类

按流向分:输入流,输出流
输入流:只能从中读取数据,不能写入数据
输出流,只能向其写入数据,不能读取数据

这里的输入输出是相对而言的,在本地用程序读写文件时,始终是以程序为中心,即程序从文件中读取时就是硬盘向内存中输入,程序向文件中写入就是内存向硬盘输出。

但对于服务器和客户端来说,服务器将数据输出到网络中,这是Sever端程序的输出流。客户端从网络中读取数据,这是Client端的输入流。
 

按操作单元分:字节流和字符流
字节流:图片、视频文件中存储的都是二进制的字节(byte)。直观的想法,以一个字节单位来运输的,比如一杯一杯的取水。

以InputStream和OutputStream作为基类


字符流:一般文本文件中存放都是有明确含义的,可供人阅读理解的字符(char)。直观的想法,以多个字节来运输的,比如一桶一桶的取水,一桶水又可以分为几杯水。

以Reader和Writer作为基类

 

不管是文本、还是图书、视频最终在磁盘上的时候都是按照byte存储的。因此,Java要提供基于字符流的机制,就要处理字节和字符的相互转化,这里就涉及字符集合字符编码的问题。

字节流和字符流的区别:

字节流读取单个字节,字符流读取单个字符(一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码是 3 个字节,中文编码是 2 个字节。)字节流用来处理二进制文件(图片、MP3、视频文件)。

字符流用来处理文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)。

简而言之,字节是个计算机看的,字符才是给人看的。

其实现子类:FileInputStream和FileOutputStream可以对任意类型的文件进行读写操作,但 FileReader和FileWriter只能对纯文本文件进行操作。

从网上找来一张图,方便对Java I/O有个总统的认识。从这张图可以很清楚的看清Java I/O的字节流和字符流的整体情况。


 

IO体系的基类

InputStream/Reader,OutputStream/Writer

InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以他们的方法是所有输入流都可使用的方法。

  • InputStream 读文件方法

int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转为int型)

int read(byte []):从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
int read(byte[], int off, int len): 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入b数组时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数

  • Reader读文件方法

int read():从输入流中读取单个字符,返回所读取的字符数据(可直接转为int类型)
int read(char[]):从输入流中最多读取b.length个字符的数据,并将其存储在数组b中,返回实际读取的字符数。
int read(char[] b, int off, int len):从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b时,并不是从数组七点开始,而是从off位置开始,返回实际读取的字符数。
 

  • OutputStream写文件方法

void write(int c):将指定的字节/字符输出到输出流中,c可以代表字节,也可以代表字符
void write(byte[]/byte[] buf):将字节数组,字符数组的数据输出到指定输出流中
void write(byte[]/byte[] buf, int off, int len):将字节数组/字符数组从off位置开始,长度为len的字节/字符数组输出到输出流中

  • Writer写文件方法

因为字符流直接以字符为操作单位,所以Writer可以用字符串代替字符数组,即以String对象为参数。Writer中还包括这两个方法:

void write(String str):将str字符串里包含的字符输出到指定输出流中
void write(String str, int off, int len):将str字符串里从off位置开始,长度为len的字符输出到指定输出流中

 

IO常用类

  • 文件流:FileInputStream/FileOutputStream, FileReader/FileWriter

FileInputStream/FileOutputStream, FileReader/FileWriter是专门操作文件流的,用法高度相似,区别在于前面两个是操作字节流,后面两个是操作字符流。它们都会直接操作文件流,直接与OS底层交互。因此他们也被称为节点流

使用这几个流的对象之后,需要关闭流对象,因为java垃圾回收器不会主动回收。

不过在Java7之后,可以在 try() 括号中打开流,最后程序会自动关闭流对象,不再需要显示的close。

下面演示这四个流对象的基本用法

//FileInputStream一个一个字节读取
public class FileInputStreamDemo1 {
            public static void main(String[] args) throws IOException{
                FileInputStream fis = new FileInputStream("c:\\a.txt");
                //读取一个字节,调用方法read 返回int
                //使用循环方式,读取文件,  循环结束的条件  read()方法返回-1
                int len = 0;//接受read方法的返回值
            
                while( (len = fis.read()) != -1){
                    System.out.print((char)len);
                }
                //关闭资源
                fis.close();
            }
        }

//FileInputStream读取字节数组
  public class FileInputStreamDemo2 {
            public static void main(String[] args) throws IOException {
                FileInputStream fis = new FileInputStream("c:\\a.txt");
                //创建字节数组
                byte[] b = new byte[1024];
                
                int len = 0 ;
                while( (len = fis.read(b)) !=-1){
                    System.out.print(new String(b,0,len));
                }
                fis.close();
            }
        }

//FileOutputStream写单个字节
 public class FileOutputStreamDemo {
            public static void main(String[] args)throws IOException {
                FileOutputStream fos = new FileOutputStream("c:\\a.txt");
                //流对象的方法write写数据
                //写1个字节
                fos.write(97);
                //关闭资源
                fos.close();
            }
        }

//FileOutputStream写字节数组
  public class FileOutputStreamDemo {
            public static void main(String[] args)throws IOException {
                FileOutputStream fos = new FileOutputStream("c:\\a.txt");
                //流对象的方法write写数据
                //写字节数组
                byte[] bytes = {65,66,67,68};
                fos.write(bytes);
                
                //写字节数组的一部分,开始索引,写几个
                fos.write(bytes, 1, 2);
                //写入字节数组的简便方式
                //写字符串
                fos.write("hello".getBytes());
                //关闭资源
                fos.close();
            }
        }

//字符流复制文本
//FileReader读取数据源,FileWriter写入到数据目的

public class Copy {
        public static void main(String[] args) {
            FileReader fr = null;
            FileWriter fw = null;
            try{
                fr = new FileReader("c:\\1.txt");
                fw = new FileWriter("d:\\1.txt");
                char[] cbuf = new char[1024];
                int len = 0 ;
                while(( len = fr.read(cbuf))!=-1){
                    fw.write(cbuf, 0, len);
                    fw.flush();
                }
                
            }catch(IOException ex){
                System.out.println(ex);
                throw new RuntimeException("复制失败");
            }finally{
                try{
                    if(fw!=null)
                        fw.close();
                }catch(IOException ex){
                    throw new RuntimeException("释放资源失败");
                }finally{
                    try{
                        if(fr!=null)
                            fr.close();
                    }catch(IOException ex){
                        throw new RuntimeException("释放资源失败");
                    }
                }
            }
        }
    }
  • 转换流:InputStreamReader/OutputStreamWriter

InputStreamReader/OutputStreamWriter可以将字节流转换成字符流,被称为字节流与字符流之间的桥梁。经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类。

转换流作用:

1 . 是字符流和字节流之间的桥梁

2 . 可对读取到的字节数据经过指定编码换成字符

3 . 可对读取到的字符数据经过指定编码转成字节

 /*
   * 转换流对象OutputStreamWriter写文本
   * 采用UTF-8编码表写入
   */
  public static void writeUTF()throws IOException{
      //创建字节输出流,绑定文件
      FileOutputStream fos = new FileOutputStream("c:\\utf.txt");
      //创建转换流对象,构造方法保证字节输出流,并指定编码表是UTF-8
      OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
      osw.write("你好");
      osw.close();
  }
  
  /*
   * 转换流对象 OutputStreamWriter写文本
   * 文本采用GBK的形式写入
   */
  public static void writeGBK()throws IOException{
      //创建字节输出流,绑定数据文件
      FileOutputStream fos = new FileOutputStream("c:\\gbk.txt");
      //创建转换流对象,构造方法,绑定字节输出流,使用GBK编码表
      OutputStreamWriter osw = new OutputStreamWriter(fos);
      //转换流写数据
      osw.write("你好");
      
      osw.close();
  }


/*
 *  转换流,InputSteamReader读取文本
 *  采用UTF-8编码表,读取文件utf
 */
public static void readUTF()throws IOException{
    //创建自己输入流,传递文本文件
    FileInputStream fis = new FileInputStream("c:\\utf.txt");
    //创建转换流对象,构造方法中,包装字节输入流,同时写编码表名
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    char[] ch = new char[1024];
    int len = isr.read(ch);
    System.out.println(new String(ch,0,len));
    isr.close();
}
/*
 *  转换流,InputSteamReader读取文本
 *  采用系统默认编码表,读取GBK文件
 */
public static void readGBK()throws IOException{
    //创建自己输入流,传递文本文件
    FileInputStream fis = new FileInputStream("c:\\gbk.txt");
    //创建转换流对象,构造方法,包装字节输入流
    InputStreamReader isr = new InputStreamReader(fis);
    char[] ch = new char[1024];
    int len = isr.read(ch);
    System.out.println(new String(ch,0,len));
    
    isr.close();
}

 

  • 缓冲流:BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream

没有经过Buffered处理的IO, 意味着每一次读和写的请求都会由OS底层直接处理,这会导致非常低效的问题。

经过Buffered处理过的输入流将会从一个buffer内存区域读取数据,本地API只会在buffer空了之后才会被调用(可能一次调用会填充很多数据进buffer)。

经过Buffered处理过的输出流将会把数据写入到buffer中,本地API只会在buffer满了之后才会被调用。

 

BufferedReader/BufferedWriter可以将字符流(Reader)包装成缓冲流,这是最常见用的做法。

另外,BufferedReader提供一个readLine()可以方便地读取一行,因此BufferedReader也被称为行读取器。而FileInputStream和FileReader只能读取一个字节或者一个字符。

在原有的节点流对象外部包装缓冲流,为IO流增加了内存缓冲区,增加缓冲区的两个目的:

1 .允许IO一次不止操作一个字符,这样提高整个系统的性能
2 .由于有缓冲区,使得在流上执行skip,mark和reset方法都成为可能
缓冲流要套接在节点流之上,对读写的数据提供了缓冲功能,增加了读写的效率,同时增加了一些新的方法。例如:BufferedReader中的readLine方法,BufferedWriter中的newLine方法。

//字符输入流
BufferedReader(Reader in)//创建一个32字节的缓冲区
BufferedReader(Reader in, int size)//size为自定义缓存区的大小

//字符输出流
BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

//字节输入流
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)

//字节输出流
BufferedOutputStream(OutputStream in)
BufferedOutputStream(OutputStream in, int size)

对于输出的缓冲流,BufferedWriter和BufferedOutputStream会先在内存中缓存,使用flush方法会使内存中的数据立刻写出。

//字节输出流缓冲流BufferedOutputStream 
 public class BufferedOutputStreamDemo {
          public static void main(String[] args)throws IOException {
              //创建字节输出流,绑定文件
              //FileOutputStream fos = new FileOutputStream("c:\\buffer.txt");
              //创建字节输出流缓冲流的对象,构造方法中,传递字节输出流
              BufferedOutputStream bos = new
                      BufferedOutputStream(new FileOutputStream("c:\\buffer.txt"));
              
              bos.write(55);
              
              byte[] bytes = "HelloWorld".getBytes();
              
              bos.write(bytes);
              
              bos.write(bytes, 3, 2);
              
              bos.close();
          }
      }

//字节输入流缓冲流BufferedInputStream
 public class BufferedInputStreamDemo {
          public static void main(String[] args) throws IOException{
              //创建字节输入流的缓冲流对象,构造方法中包装字节输入流,包装文件
              BufferedInputStream bis = new 
                      BufferedInputStream(new FileInputStream("c:\\buffer.txt"));
              byte[] bytes = new byte[10];
              int len = 0 ;
              while((len = bis.read(bytes))!=-1){
                  System.out.print(new String(bytes,0,len));
              }
              bis.close();
          }
      }

//字符流缓冲区流复制文本文件
*
         *  使用缓冲区流对象,复制文本文件
         *  数据源  BufferedReader+FileReader 读取
         *  数据目的 BufferedWriter+FileWriter 写入
         *  读取文本行, 读一行,写一行,写换行
         */
        public class Copy_1 {
            public static void main(String[] args) throws IOException{
                BufferedReader bfr = new BufferedReader(new FileReader("c:\\w.log"));   
                BufferedWriter bfw = new BufferedWriter(new FileWriter("d:\\w.log"));
                //读取文本行, 读一行,写一行,写换行
                String line = null;
                while((line = bfr.readLine())!=null){
                    bfw.write(line);
                    bfw.newLine();
                    bfw.flush();
                }
                bfw.close();
                bfr.close();
            }
        }

 

  • 对象流(ObjectInputStream/ObjectOutputStream)

如果我们要写入文件内的数据不是基本数据类型(使用DataInputStream),也不是字符型或字节型数据,而是一个对象,应该怎么写?这个时候就用到了处理流中的对象流。

对象的序列化
对象中的数据,以流的形式,写入到文件中保存过程称为写出对象,对象的序列化
ObjectOutputStream将对象写道文件中,实现序列化

对象的反序列化
在文件中,以流的形式,将对象读出来,读取对象,对象的反序列化
ObjectInputStream 将文件对象读取出来

注意:

  • 先序列化后反序列化; 反序列化顺序必须与序列化一致
  • 不是所有对象都可以序列化 必须实现 java.io.Serializable
  • 不是所有属性都需要序列化 不需要序列化的属性 要加 transient
    public class Person implements Serializable {
        private String name;
        private int age;
        private transient String intro;   //不需要序列化
    
        public Person(String name, int age, String intro) {
            this.name = name;
            this.age = age;
            this.intro = intro;
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    
        public String getIntro() {
            return intro;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setIntro(String intro) {
            this.intro = intro;
        }
    
        public String toString(){
            return name + " " + age + " " + intro;
        }
    }

    进行序列化和反序列化后:

    public class WriteObject {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(
                    new File("d:\\test.txt"))));
    
            oos.writeObject(new Person("shi",13,"A"));
            oos.writeObject(new Person("zhang",15,"B"));
            oos.writeObject(new Person("hao",18,"C"));
    
            oos.close();
    
            ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(
                    new FileInputStream(new File("d:\\test.txt"))));
            Person p1 = (Person) ois.readObject();
            Person p2 = (Person) ois.readObject();
            Person p3 = (Person) ois.readObject();
            ois.close();
            System.out.println(p1);
            System.out.println(p2);
            System.out.println(p3);
        }
    }

    可以发现,自定义类中被transient 标记的数据将不被序列化和反序列化,而且自定义类也必须要实现Serializable接口。

注意:

ObjectOutputStream 对JAVA对象进行序列化处理,处理后的对象不是文本数据。所以数据保存到文件中后,用记事本、写字板、Word等文本编辑器打开,是无法识别的,一定会显示乱码。只有使用相同版本的Java的ObjectInputStream进行读取操作,方可获取文件中的对象内容。

序列化时的参数类型和反序列化时的返回类型都是Object类型,所以在反序列化接收类对象数据时要用强制类型转换。

 

总结上面几种流的应用场景:

  • FileInputStream/FileOutputStream  需要逐个字节处理原始二进制流的时候使用,效率低下
  • FileReader/FileWriter 需要组个字符处理的时候使用
  • StringReader/StringWriter 需要处理字符串的时候,可以将字符串保存为字符数组
  • PrintStream/PrintWriter 用来包装FileOutputStream 对象,方便直接将String字符串写入文件 
  • Scanner 用来包装System.in流,很方便地将输入的String字符串转换成需要的数据类型
  • InputStreamReader/OutputStreamReader ,  字节和字符的转换桥梁,在网络通信或者处理键盘输入的时候用
  • BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream , 缓冲流用来包装字节流后者字符流,提升IO性能,BufferedReader还可以方便地读取一行,简化编程。

参考文章:

https://blog.csdn.net/wintershii/article/details/81281710

https://blog.csdn.net/sj13051180/article/details/47710099

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章