「JavaSE」- IO流

IO

File类

1、File类的使用

  • java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关

  • File 能新建、删除、重命名文件和目录,但File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。

  • 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。

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

2、常用构造器

  • public File(String pathname)

        以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。

        绝对路径:是一个固定的路径,从盘符开始

        相对路径:相对于某个位置开始的路径

  • public File(String parent,String child)

        以 parent 为父路径,child 为子路径创建File对象。

  • public File(File parent,String child)

        根据一个父File对象和子文件路径创建File对象

3、路径分割符

  • 路径中的每级目录之间用一个路径分隔符隔开。

  • 路径分隔符和系统有关:

    • windows和DOS系统默认使用 “ \ ” 来表示

    • UNIX和URL使用 “ / ” 来表示

  • Java程序支持跨平台运行,因此路径分隔符要慎用。

  • 为了解决这个隐患,File类提供了一个常量:public static final String separator。根据操作系统,动态的提供分隔符。

  • 举例:

     File file1 = new File("d:\\atguigu\\info.txt");
     File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
     File file3 = new File("d:/atguigu");

4、常用方法

  • File类的获取功能

    • public String getAbsolutePath():获取绝对路径

    • public String getPath():获取路径

    • public String getName():获取名称

    • public String getParent():获取上层文件目录路径。若无,返回null

    • public long length():获取文件长度(即:字节数)。不能获取目录的长度

    • public long lastModified():获取最后一次的修改时间,毫秒值

    如下两个方法适用于文件目录:

    • public String[] list():获取指定目录下的所有文件或者文件目录名称构成的数组

    • public File[] listFiles():获取指定目录下的所有文件或者文件目录的File构成的数组

  • File类的重命名功能

    • public boolean renameTo(Filedest):把文件重命名为指定的文件路径

      • 比如:file1.renameTo(file2)为例:要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。

  • File类的判断功能

    • public boolean isDirectory():判断是否是文件目录

    • public boolean isFile():判断是否是文件

    • public boolean exists():判断是否存在

    • public boolean canRead():判断是否可读

    • public boolean canWrite():判断是否可写

    • public boolean isHidden():判断是否隐藏

  • File类的创建文件/文件目录功能

    • public boolean createNewFile():创建文件。

      • 若文件存在,则不创建,返回false

    • public boolean mkdir():创建文件目录。

      • 如果此文件目录存在,就不创建了。

      • 如果此文件目录的上层目录不存在,也不创建。

    • public boolean mkdirs():创建文件目录。

      • 如果上层文件目录不存在,一并创建。

注意事项:如果创建文件或者文件目录没有写盘符路径,默认在项目路径下。

  • File类的删除文件/文件目录功能

    • public boolean delete():删除文件或者文件夹

    删除注意事项:

    • Java中的删除不走回收站

    • 要删除一个文件目录,注意该文件目录内不能包含文件或者文件目录

 File dir1 = new File(File("D:/IOTest/dir1");
 if(!dir1.exists()) {//如果D:/IOTest/dir1不存在,就创建为目录
   dir1.mkdir();
 }
 //创建以dir1为父目录,名为"dir2"的 File 对象
 File dir2 = new File(dir1,"dir2");
 if(!dir2.exists()) {//如果还不存在,就创建为目录
   dir2.mkdirs();
 }
                      
 File dir4 = new File(dir1,"dir3/dir4");
 if(!dir4.exists()){
   dir4.mkdirs();
 }
                      
 //创建以dir2为父目录,名为"test.txt"的File对象
 File file = new File(dir2,"test.txt");
 if(!file .exists()) {//如果还不存在,就创建为文件
   file.createNewFile();
 }

小结:

  • File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)。

  • File类声明在java.io包下。

  • File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流完成。

  • 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的“终点”。

IO流原理及流的分类

1、Java IO原理

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

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

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

  • 在Java程序中,对于数据的输入/输出操作以“流”(Stream)方式进行;J2SDK提供了各种各样的“流”类,用以获取不同种类的数据:程序中通过标准的方法输入或输出数据。

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

  • 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。【程序(内存)外部数据 ----> 存储设备】

读入写出

流是用来读写数据的,java有一个类叫File,它封装的是文件的文件名,只是内存里面的一个对象,真正的文件是在硬盘上的一块空间,在这个文件里面存放着各种各样的数据,是通过一个流的方式来读。

JAVA里面的流式输入/输出跟水流的原理一模一样,当你要从文件读取数据的时候,一根管道插到文件里面去,然后文件里面的数据就顺着管道流出来,这时你在管道的另一头就可以读取到从文件流出来的各种各样的数据了。当你要往文件写入数据时,也是通过一根管道,让要写入的数据通过这根管道哗啦哗啦地流进文件里面去。除了从文件去取数据以外,还可以通过网络,比如用一根管道把我和你的机子连接起来,有的时候,一根管道不够用,比方说这根管道流过来的水有一些杂质,我们就可以在这个根管道的外面再包一层管道,把杂质给过滤掉。

2、流的分类

Java.io 包中定义了多个流类型(类或抽象类)来实现输入/输出功能;可以从不同的角度对其进行分类:

  • 按数据流的方向不同分为:输入流、输出流

  • 按照处理数据单位不同分为:字节流(8 bit)字符流(16 bit)

  • 按照功能(角色)不同分为:节点流、处理流

(抽象基类)字节流字符流
输入流InputStreamReader
输出流OutStreamWriter
  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。

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

理解两个概念:

  • 字节流:最原始的一个流,读出的数据是010101这种最底层的数据表示形式,只不过它是按照字节来读的,一个字节(Byte)是8位(bit)读的时候不是一个位一个位的来读,而是一个字节一个字节来读。

  • 字符流:字符流是一个字符一个字符地往外读取数据。一个字符是2个字节

J2SDK所提供的所有流类型位于包 Java.io内,都分别继承自以下四种抽象流类型。

  • 输入流:InputStream(字节流),Reader(字符流)

  • 输出流:OutPutStream(字节流),Writer(字符流)

这四个类都是抽象类,可以把这四个类想象成四根不同的管道。一端接着你的程序,另一端接着数据源,可以通过输出管道从数据源里面往外读数据,也可以通过输入管道往数据源里面输入数据,总之,通过这四根管道可以让数据流进来和流出去。

IO流体系

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
访问字符串StringReaderStringWriter
缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
转换流InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputStream
FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流PrintStreamPrintWriter
推回输入流PushbackInputStreamPushbackReader
特殊流DataInputStreamDataOutputStream

记住,以后说输入流和输出流都是站在程序的角度上来说

1)节点流 & 处理流

  • 节点流:直接从数据源或目的地读写数据

  • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

2)InputStream & Reader

  • InputStream和Reader 是所有输入流的基类

  • InputStream(典型实现:FileInputStream

    • int read()

    • int read(byte[] b)

    • int read(byte[] b, int off, int len)

  • Reader(典型实现:FileReader

    • int read()

    • int read(char [] c)

    • int read(char [] c, int off, int len)

  • 程序中打开的文件IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO 资源。

  • FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用FileReader

InputStream

  • int read()

        从输入流中读取数据的下一个字节。返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回值-1。

  • int read(byte[] b)

        从此输入流中将最多b.length个字节的数据读入一个byte数组中。如果因为已经到达流末尾而没有可用的字节,则返回值-1。否则以整数形式返回实际读取的字节数。

  • int read(byte[] b,int off,int len)

        将输入流中最多len个数据字节读入byte数组。尝试读取len个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值-1。

  • public void close() throws IOException

        关闭此输入流并释放与该流关联的所有系统资源。

Reader

  • intread()

        读取单个字符。作为整数读取的字符,范围在0到65535之间(0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回-1

  • intread(char[]cbuf)

        将字符读入数组。如果已到达流的末尾,则返回-1。否则返回本次读取的字符数。

  • intread(char[]cbuf,int off,int len)

        将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回-1。否则返回本次读取的字符数。

  • public void close() throws IOException

        关闭此输入流并释放与该流关联的所有系统资源。

3)OutputStream& Writer

  • OutputStream 和Writer 也非常相似:

    • void write(int b/intc);

    • void write(byte[] b/char[] cbuf);

    • void write(byte[] b/char[] buff,int off, int len);

    • void flush();

    • void close(); 需要先刷新,再关闭此流

  • 因为字符流直接以字符作为操作单位,所以Writer 可以用字符串来替换字符数组,即以String 对象作为参数

    • void write(String str);

    • void write(String str, int off, int len);

  • FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用FileWriter

OutputStream

  • void write(int b)

        将指定的字节写入此输出流。write的常规协定是:向输出流写入一个字节。要写入的字节是参数b的八个低位。b的24个高位将被忽略。即写入0~255范围的。

  • void write(byte[] b)

        将b.length个字节从指定的byte数组写入此输出流。write(b)的常规协定是:应该与调用write(b,0,b.length)的效果完全相同。

  • void write(byte[] b,int off,int len)

        将指定byte数组中从偏移量off开始的len个字节写入此输出流。

  • public void flush() throws IOException

        刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。

  • public void close() throws IOException

        关闭此输出流并释放与该流关联的所有系统资源。

Writer

  • void write(int c)

        写入单个字符。要写入的字符包含在给定整数值的16个低位中,16高位被忽略。即写入0到65535之间的Unicode码。

  • void write(char[] cbuf)

        写入字符数组。

  • void write(char[] cbuf,int off,int len)

        写入字符数组的某一部分。从off开始,写入len个字符

  • void write(String str)

        写入字符串。

  • void write(String str,int off,int len)

        写入字符串的某一部分。

  • void flush()

        刷新该流的缓冲,则立即将它们写入预期目标。

  • public void close() throws IOException

        关闭此输出流并释放与该流关联的所有系统资源。

节点流和处理流

        你要是对原始的流不满意,你可以在这根管道外面再套其它的管道,套在其它管道之上的流叫处理流。为什么需要处理流呢?这就跟水流里面有杂质,你要过滤它,你可以再套一层管道过滤这些杂质一样。

1、节点流类型

类型字符流字节流
File(文件)FileReader、FileWriterFileInputStream、FileOutputStream
Memory ArrayCharArrayReader、CharArrayWriterByteArrayInputStream、ByteArrayOutputStream
Memory StringStringReader、StringWriter/
Pipe(管道)PipedReader、PipedWriterPipedInputStream、PipedOutputStream

节点流就是一根管道直接插到数据源上面,直接读数据源里面的数据,或者是直接往数据源里面写入数据。

典型的节点流是文件流:

  • 文件的字节输入流(FileInputStream)

  • 文件的字节输出流(FileOutputStream)

  • 文件的字符输入流(FileReader)

  • 文件的字符输出流(FileWriter)

读取文件

  • read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1。

  • 异常的处理:为了保证资源一定要执行关闭操作。需使用try-catch-finally处理

  • 读入的文件一定要存在,否则就会报 FileNotFoundException

 1.建立一个流对象,将已存在的一个文件加载进流。
 FileReader fr = new FileReader(new File(“Test.txt”));
 ​
 2.创建一个临时存放数据的数组。
 char[] ch = new char[1024];
 ​
 3.调用流对象的读取方法将流中的数据读入到数组中。
 fr.read(ch);
 ​
 4.关闭资源。
 fr.close();
 
//从硬盘中的文件里读取数据到内存中
 public void testFileReader(){
   FileReader fr = null;
   try{
     //1.File的实例化
     File file = new File("test.txt");
     //2.FileReader流的实例化
     fr = new FileReader(File);
     //3.读入的操作
     //read(char[] buf):返回每次读入buf数组中的字符的个数,如果达到文件末尾,返回-1。
     char[] buf = new char [1024];
     int len;
     while((len = fr.read(buf)) != -1){
         System.out.print(new String(buf,0,len));
     }
   }catch(IOException e){
     System.out.println(("read-Exception:" + e.getMessage());
   }finally{
     if(fr != null){
       try{
         //4.资源的关闭
         fr.close();
       }catch(IOException e){
         System.out.println(("close-Exception :" + e.getMessage());
       }
     }
   }
 }

写入文件

  • 输出操作,对应的File文件可以不存在,并不会报异常。

  • File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。

  • File对应的硬盘中的文件如果存在:

    • 如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件进行覆盖操作。

    • 如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上进行追加内容。

 
1.创建流对象,建立数据存放文件
 FileWriter fw = new FileWriter(new File(“Test.txt”));
 ​
 2.调用流对象的写入方法,将数据写入流
 fw.write (“atguigu songhongkang”);
 ​
 3.关闭流资源,并将流中的数据清空到文件中。
 fw.close();
 
//从内存中写出数据到硬盘的文件里。
 public void testFileWriter(){
   FileWriter fw = null;
   try{
     //1.提供File类的对象,指明写出到的文件
     //File file = new File("Test.txt");
     //2.提供FileWriter的对象,用于数据的写出
     //FileWriter fw = new FileWriter(file);
     fw = new FileWriter(new File(File("Test.txt"));
     //3.写出的操作
     fw.write("atguigu songhongkang");
   }catch(IOException e){
     e.printStackTrace();
   }finally{
     if(fw != null)
       try{
         //4.流资源的关闭
         fw.close
       }catch(IOException e){
           e.printStackTrace();
     }
   }
 }

使用FileReader和FileWriter实现对文本文件的复制

 public void testFileReaderFileWriter(){
   FileWriter fw = null;
   try{
     //1.创建File类的对象,指明读入和写出的文件
     File srcFile = new File("hello.txt");
     File destFile = new File("hello2.txt");
     
     //2.创建输入流和输出流的对象
     fr = new FileReader(srcFile);
     fw = new FileWriter(destFile);
     
     //3.数据的读入和写出操作
     char[] cbuf = new char[5];
     int len;//记录每次读入到cbuf数组中的字符的个数
     while((len = fr.read(cbuf)) != -1){
       //每次写出len个字符
       fw.write(cbuf,0,len);
     }  
   }catch (IOException e){
     e.printStackTrace();
   }finally{
     //4.关闭流资源
     //方式1
 //    try{
 //      if(fw != null)
 //        fw.close();
 //    }catch (IOExpection e){
 //      e.printStackTrace();
 //    }finally{
 //      try{
 //        if(fr != null)
 //          fr.close();
 //      }catch (IOExpection e){
 //        e.printStackTrace();
 //      }
     //方式2
     try{
       if(fw != null)
         fw.close();
     }catch (IOExpection e){
       e.printStackTrace();
     }
   
     try{
         if(fr != null)
           fr.close();
       }catch (IOExpection e){
         e.printStackTrace();
       }
   }  
 }

节点流(或文件流):注意点

  • 定义文件路径时,注意:可以用“/”或者“\”。

  • 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖

  • 如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容

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

  • 字节流操作字节,操作非文本文件,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt

  • 字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

2、处理流类型

处理类型字符流字节流
BufferingBufferedReader、BufferedWriterBufferedInputStream、BufferedOutputStream
FilteringFilterReader、FilterWriterFilterInputStream,FilterOutputStream
Converting between bytes and chaacterInputStreamReader、OutputStreamWriter/
Object Serialization/ObjectInputStream、ObjectOutputStream
Data conversion/DataInputStream、DataOutputStream
CountingLineNumberReaderLineNumberInputStream
Peeking aheadPusbackReaderPushbackInputStream
PrintingPrintWriterPrintStream

处理流是包在别的流上面的流,相当于是包到别的管道上面的管道。

InputStream(输入流)

凡是以InputStream结尾的管道,都是以字节的形式向程序输入数据。

继承自InputStream的流都是用于向程序中输入数据,且数据的单位为字节(8bit);下图中深色为节点流,浅色为处理流。

1、InputStream的基本方法

 //读取一个字节并以整数的形式返回(0~255)
 //如果返回-1就说明已经到了输入流的末尾
 int read() throws IOException
   
 //读取一系列字节并存储到一个数组buffer
 //返回实际读取的字节数,如果读取前已到输入流的末尾,则返回-1
 int read(byte[] buffer) throws IOException
   
 //读取length个字节
 //并存储到一个字节数组buffer,从length位置开始
 //返回实际读取的字节数,如果读取前以到输入流的末尾返回-1.
 int read(byte[] buffer,int offset,int length) throws IOException
   
 //关闭流释放内存资源
 void close() throws IOException
   
 //跳过n个字节不读,返回实际跳过的字节数
 long skip(long n) throws IOException

read()方法是一个字节一个字节地往外读,每读取一个字节,就处理一个字节。

read(byte[] buffer)方法读取数据时,先把读取到的数据填满byte[]类型的数组buffer(buffer是内存里面的一块缓冲区),然后再处理数组里面的数据。

2、案例

以File(文件)类型作为讲解节点流的典型代表

【源码查看,分析结构】

【演示:使用FileInputStream流来读取FileInputStream.java文件的内容

package com.kuang.chapter;

import java.io.*;

public class TestFileInputStream {
  public static void main(String args[]) {
    // 使用变量b来装调用read()方法时返回的整数
    int b = 0;
    FileInputStream in = null;
    // 使用FileInputStream流来读取有中文的内容时,读出来的是乱码,
    //因为使用InputStream流里面的read()方法读取内容时是一个字节一个字节地读取的,而一个汉字是占用两个字节的,所以读取出来的汉字无法正确显示。
    // FileReader in = null;
    // 使用FileReader流来读取内容时,中英文都可以正确显示,因为Reader流里面的read()方法是一个字符一个字符地读取的,这样每次读取出来的都是一个完整的汉字,这样就可以正确显示了。
      try {
        in = new FileInputStream("E:\\教学\\班级            \\Test\\Lesson2\\src\\com\\kuang\\chapter\\Student.java");
  	// in = new FileReader("E:\\教学\\班级                          \\Test\\Lesson2\\src\\com\\kuang\\chapter\\Student.java");
        } catch (FileNotFoundException e) {
            System.out.println("系统找不到指定文件!");
            System.exit(-1);// 系统非正常退出
        }
    
        long num = 0;// 使用变量num来记录读取到的字符数
                
    	// 调用read()方法时会抛异常,所以需要捕获异常
     try {
         	while ((b = in.read()) != -1) {
           // 调用int read() throws Exception方法时,返回的是一个int类型的整数
          // 循环结束的条件就是返回一个值-1,表示此时已经读取到文件的末尾了。
         // System.out.print(b+"\t");//如果没有使用“(char)b”进行转换,那么直接打印出来的b就是数字,而不是英文和中文了
          	System.out.print((char) b);
        // “char(b)”把使用数字表示的汉字和英文字母转换成字符输入
            num++;
        }
       in.close();// 关闭输入流
       System.out.println();
       System.out.println("总共读取了" + num + "个字节的文件");
     } catch (IOException e1) {
       System.out.println("文件读取错误!");
     }
  }
}

OutputStream(输出流)

继承自OutputStream的流是用于程序中输出数据,且数据的单位为字节(8bit):下图中深色的为节点流,浅色为处理流。

1、OutputStream的基本方法

//向输出流中写入一个字节数据,该字节数据为参数b的低8位
void write(int b) throws IOException
  
//将一个字节类型的数组中的数据写入输出流
void write(byte[] b) throws IOException
  
//将一个字节类型的数组中的从指定位置(off)开始的len个字节写入到输出流
void write(byte[] b,int off,int len) throws IOException
  
//关闭流释放内存资源
void close() throws IOException
  
//将输出流中缓冲的数据全部写出到目的地
void flush() throws IOException

2、案例

【使用FileOutputStream流往一个文件里面写入数据】

package com.kuang.chapter;

import java.io.*;

public class TestFileOutputStream {
  public static void main(String args[]) {
    int b = 0;
    FileInputStream in = null;
    FileOutputStream out = null;
		try {
				in = new FileInputStream("E:\\教学\\班级\\Test\\Lesson2\\src\\com\\kuang\\chapter\\Student.java");
				out = new FileOutputStream("E:\\教学\\班级\\Test\\Lesson2\\src\\com\\kuang\\chapter\\StudentNew.java");
			// 指明要写入数据的文件,如果指定的路径中不存在StudentNew.java这样的文件,则系统会自动创建一个
      while ((b = in.read()) != -1) {
        out.write(b);
        // 调用write(int c)方法把读取到的字符全部写入到指定文件中去
      }
      
      in.close();
      out.close();
    } catch (FileNotFoundException e) {
      System.out.println("文件读取失败");
      System.exit(-1);// 非正常退出
    } catch (IOException e1) {
      System.out.println("文件复制失败!");
      System.exit(-1);
    }
    System.out
      .println("Student.StudentNew.java里面");
  }
}

FileInputStream和FileOutputStream这两个流都是字节流,都以一个字节为单位进行输入和输出的。所以对于占用2个字节存储空间的字符来说读取出来时就会显示成乱码。

Reader流

Reader : 和InputStream一模一样,唯一的区别就在于读的数据单位不同

继承自Reader的流都是用于向程序中输入数据,且数据的单位为字符(16bit)

16位:一个字符也就是两个字节,使用Reader流读取数据时都是两个字节两个字节往外读的。

为什么要有这种两个字节的读取方式呢?

因为有些字符是占2个字节的,如中文字符在Java里就占两个字节。如果采用一个字节一个字节往外读的方式,那读出来的就是半个汉字,这样Java就没办法正确的显示中文字符,所以有必要存在这种流,一个字符一个字符地往外读。

1、Reader的基本方法

//读取一个字节并以整数的形式返回(0~255)
//如果返回-1就说明已经到了输入流的末尾
int read() throws IOException
  
//读取一系列字节并存储到一个数组buffer
//返回实际读取的字节数,如果读取前已到输入流的末尾,则返回-1
int read(byte[] buffer) throws IOException
  
//读取length个字节
//并存储到一个字节数组buffer,从length位置开始
//返回实际读取的字节数,如果读取前以到输入流的末尾返回-1.
int read(byte[] buffer,int offset,int length) throws IOException
  
//关闭流释放内存资源
void close() throws IOException
  
//跳过n个字节不读,返回实际跳过的字节数
long skip(long n) throws IOException

Writer流

继承自Writer的流都是用于程序中输出数据,且数据的单位为字符(16bit);

1、Writer的基本方法

//向输出流中写入一个字节数据,该字节数据为参数b的低16位
void write(int b) throws IOException
  
//将一个字节类型的数组中的数据写入输出流
void write(byte[] b) throws IOException
  
//将一个字节类型的数组中的从指定位置(off)开始的len个字节写入到输出流
void write(byte[] b,int off,int len) throws IOException
  
//关闭流释放内存资源
void close() throws IOException
  
//将输出流中缓冲的数据全部写出到目的地
void flush() throws IOException

2、演示

【演示:使用FileWriter(字符流)向指定文件中写入数据

package com.kuang.chapter;

/*使用FileWriter(字符流)向指定文件中写入数据写入数据时以1个字符为单位进行写入*/
import java.io.*;
public class TestFileWriter{
  public static void main(String args[]){
    /*使用FileWriter输出流从程序把数据写入到Uicode.dat文件中
使用FileWriter流向文件写入数据时是一个字符一个字符写入的*/
    FileWriter fw = null;
		try{
				fw = new FileWriter("E:\\教学\\班级\\Test\\Lesson2\\src\\com\\kuang\\chapter\\StudentNew.java");
				//字符的本质是一个无符号的16位整数
				//字符在计算机内部占用2个字节
				//这里使用for循环把0~60000里面的所有整数都输出
				//这里相当于是把全世界各个国家的文字都0~60000内的整数的形式来表示
      for(int c=0;c<=60000;c++){
        fw.write(c);
        //使用write(int c)把0~60000内的整数写入到指定文件内
        //调用write()方法时,我认为在执行的过程中应该使用了“(char)c”进行强制转换,即把整数转换成字符来显示
        //因为打开写入数据的文件可以看到,里面显示的数据并不是0~60000内的整数,而是不同国家的文字的表示方式
      }
			/*使用FileReader(字符流)读取指定文件里面的内容
			读取内容时是以一个字符为单位进行读取的*/
      int b = 0;
      long num = 0;
      FileReader fr = null;
			fr = new FileReader("E:\\教学\\班级\\Test\\Lesson2\\src\\com\\kuang\\chapter\\StudentNew.java");
      while((b = fr.read())!= -1){
        System.out.print((char)b + "\t");
        num++;
      }
      System.out.println();
      System.out.println("总共读取了"+num+"个字符");
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

FileReader和FileWriter这两个流都是字符流,都是以一个字符为单位进行输入和输出的。

所以读取和写入占用2个字节的字符时都可以正常地显示出来,以上是以File(文件)类型为例对节点流进行了讲解,所谓的节点流指的就是直接把输入流或输出插入到数据源上,直接往数据源里面写入数据或读取数据。

处理流

1、处理流之一—缓冲流(Buffering)

  • 为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区

  • 缓冲流要”套接“在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。根据数据操作单位可以把缓冲流分为:

    • BufferedInputStreamBufferedOutputStream

    • BufferedReaderBufferedWriter

  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区

  • 当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。

  • 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流

  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流会相应关闭内层节点流。

  • flush()方法的使用:手动将buffer中内容写入文件

  • 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

  • J2SDK提供了四种缓冲流,常用构造方法如下:

BufferedReader(Reader in)
BufferedReader(Reader in,int sz) //sz为自定义缓冲区的大小
BufferedWriter(Writer out)
BufferedWriter(Writer out,int sz)
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in,int size)
BufferedOutputStream(InputStream in)
BufferedOutputStream(InputStream in,int size)
  • 缓冲输入流支持其父类的mark和reset方法。

  • BufferedReader提供了readLine方法用于读取一行字符串

  • BufferedWriter提供了newLine用于写入一个行分隔符

  • 对于输出的缓冲流,写出的数据会现在内存中缓存,使用flush方法将会使内存中的数据立刻写出

缓冲区(Buffer)就是内存里面的一小块区域,读写数据时都是先把数据放到缓冲区域里,减少io对硬盘的访问次数,保护硬盘。先把数据放置到缓冲区上,等到缓冲区满了以后,再一次把缓冲区里的数据写入到硬盘上或者读取出来,可以有效地减少对硬盘的访问次数,有利于保护硬盘。

【缓冲流测试代码:BufferedInputStream】

package com.kuang.chapter;

import java.io.*;

public class TestBufferStream {
  public static void main(String args[]) {
    FileInputStream fis = null;
		try {
				fis = new FileInputStream("Student.java");
				//在FileInputStream节点流的外面套接一层处理流BufferedInputStream
				BufferedInputStream bis = new BufferedInputStream(fis);
				int c = 0;
				System.out.println((char) bis.read());
				System.out.println((char) bis.read());
				bis.mark(100);//在第100个字符处做一个标记
				for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
						System.out.print((char) c);
				}
      System.out.println();
      bis.reset();//重新回到原来标记的地方
      for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
        System.out.print((char) c);
      }
      bis.close();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (Exception e1) {
      e1.printStackTrace();
    }
  }
}

【演示:BufferedReader】

import java.io.*;
public class TestBufferStream{
  public static void main(String args[]){
		try{
				BufferedWriter bw = new BufferedWriter(new FileWriter("Student.txt"));
				//在节点流FileWriter的外面套一层处理流BufferedWriter
			String s = null;
      for(int i=0;i<100;i++){
        s = String.valueOf(Math.random());
        //Math.random():将生成一系列介于0~1之间的随机数
			  //valueOf()方法:作用是把一个double类型的数转换成字符串
		    //valueOf()是一个静态方法,可以使用 类型.静态方法名 的形式来调用
        bw.write(s);//把随机数字符串写入到指定文件中
        bw.newLine();//调用newLine()方法使得每写入一个随机数就换行显示
      }
      bw.flush();//调用flush()方法清空缓冲区
      BufferedReader br = new BufferedReader(new FileReader("Student.txt"));
      //在节点流FileReader的外面再套一层处理流BufferedReader
      while((s = br.readLine())!=null){
        //使用BufferedReader处理流提供的String readLine()方法读取文件中的数据时是一行一行读取的
       //循环结束条件:使用readLine()方法读取数据返回的字符串为空值则表示已经读取到文件末尾
          System.out.println(s);
      }
      bw.close();
      br.close();
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

2、处理流之二—转换流

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

  • Java API提供了两个转换流:

    • InputStreamReader:将一个 字节的输入流InputStream 转换为 字符的输入流Reader

    • OutputStreamWriter:将一个 字符的输出流Writer 转换为 字节的输出流OutputStream

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

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

  • 转换流在构造时可以指定其编码集合

InputStream isr = new InputStreamReader(System.in,"ISO8859-1")

转换流有以下两种:

InputStreamReader

  • 实现将 字节的输入流 按指定字符集转换为 字符的输入流

  • 需要和InputStream“套接”。

  • 构造器

    • public InputStreamReader(InputStreamin)

    • public InputSreamReader(InputStreamin,StringcharsetName)

如:

Reader isr= new InputStreamReader(System.in,”gbk”);

OutputStreamWriter

  • 实现将 字符的输出流 按指定字符集转换为 字节的输出流

  • 需要和OutputStream“套接”。

  • 构造器

    • public OutputStreamWriter(OutputStreamout)

    • public OutputSreamWriter(OutputStreamout,StringcharsetName)

【转换流测试代码】

import java.io.*;

public class TestTransform1 {
  public static void main(String args[]) {
		try {
				OutputStreamWriter osw = new OutputStreamWriter(
 					new FileOutputStream("char.txt"));
				osw.write("MircosoftsunIBMOracleApplet");// 把字符串写入到指定的文件中去
				System.out.println(osw.getEncoding());// 使用getEncoding()方法取得当前系统的默认字符编码
				osw.close();
				osw = new OutputStreamWriter(new FileOutputStream(
"char.txt", true), "ISO8859_1");
				// 如果调用FileOutputStream构造方法时没有加入true,那新加入的字符串就会替换掉原来写入的字符串,在调用构造方法时指定了字符的编码
				osw.write("MircosoftsunIBMOracleApplet");//再次向指定的文件写入字符串,新写入的字符串加入到原来字符串的后面
      System.out.println(osw.getEncoding());
      osw.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

import java.io.*;
public class TestTransform2{
  public static void main(String args[]){
    try{
      InputStreamReader isr = new InputStreamReader(System.in);
      //System.in:这里的in是一个标准的输入流,用来接收从键盘输入的数据
      BufferedReader br = new BufferedReader(isr);
      String s = null;
      s = br.readLine();//使用readLine()方法把读取到的一行字符串保存到字符串变量s中去
      while(s != null){
          System.out.println(s.toUpperCase());//把保存在内存s中的字符串打印出来
          s = br.readLine();//在循环体内继续接收从键盘的输入
          if(s.equalsIgnoreCase("exit")){
            //只要输入exit循环就结束,就会退出
            break;
          }
        }
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

补充:字符编码

  • 编码表的由来

    计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。

  • 常见的编码表

    • ASCII:美国标准信息交换码。

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

    • ISO8859-1:拉丁码表/欧洲码表

      • 用一个字节的8位表示。

    • GB2312:中国的中文编码表,最多两个字节编码所有字符。

    • GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多两个字节编码。

    • Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码,所有的文字都用两个字节来表示。

    • UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

  • Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。

  • 编码:字符串 ----> 字节数组

  • 解码:字节数组 ----> 字符串

  • 转换流的编码应用

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

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

    • 指定编码表的动作由构造器完成

3、处理流之三—标准输入、输出流

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

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

  • System.in的类型是InputStream

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

  • 重定向:通过System类的setIn()setOut()方法重新指定输入和输出的流。

    • public static void setIn(InputStream in)

    • public static void setOut(PrintStream out)

例题:从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。

练习:创建一个名为 MyInput.java 的程序:包含从键盘读取 int、double、float、boolean、short、byte 和 String 值的方法。

import java.io.*;

public class MyInput {
    // Read a string from the keyboard
    public static String readString() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // Declare and initialize the string
        String string = "";

        // Get the string from the keyboard
        try {
            string = br.readLine();

        } catch (IOException ex) {
            System.out.println(ex);
        }

        // Return the string obtained from the keyboard
        return string;
    }

    // Read an int value from the keyboard
    public static int readInt() {
        return Integer.parseInt(readString());
    }

    // Read a double value from the keyboard
    public static double readDouble() {
        return Double.parseDouble(readString());
    }

    // Read a byte value from the keyboard
    public static double readByte() {
        return Byte.parseByte(readString());
    }

    // Read a short value from the keyboard
    public static double readShort() {
        return Short.parseShort(readString());
    }

    // Read a long value from the keyboard
    public static double readLong() {
        return Long.parseLong(readString());
    }

    // Read a float value from the keyboard
    public static double readFloat() {
        return Float.parseFloat(readString());
    }
}

4、处理流之四—数据流

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

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

    • DataInputStreamDataOutputStream

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

  • DataInputStream 和 DataOutputStream 分别继承自InputStream 和 OutputStream , 它属于处理流,需要分别“套接”在InputStream 和 OutputStream类型的节点流上。

  • DataInputStream 和 DataOutputStream 提供了可以存取与机器无关的Java原始类型数据(int,double等)的方法。

  • DataInputStream中的方法

    • boolean readBoolean()

    • byte readByte()

    • char readChar()

    • float readFloat()

    • double readDouble()

    • short readShort()

    • long readLong()

    • int readInt()

    • String readUTF()

    • void readFully(byte[] b)

  • DataOutputStream中的方法

    • 将上述的方法的read改为相应的write即可。

  • DataInputStream 和 DataOutputStream 的构造方法

DataInputStream (InputStream in)
DataOutputStream (OutputStream out)

先写数据

将内存中的字符串、基本数据类型的变量写出到文件中。

读数据

将文件中存储的基本数据类型和字符串读取到内存中,保存在变量中。

注意点:读取不同类型的数据的顺序要与当初写入文件时,保存数据的顺序一致!(先写的要先读

【数据流测试代码】

import java.io.*;

public class TestDataStream{
  public static void main(String args[]){
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    //在调用构造方法时,首先会在内存里面创建一个ByteArray字节数组
    DataOutputStream dos = new DataOutputStream(baos);
    //在输出流的外面套上一层数据流,用来处理int,double类型的数
    try{
      dos.writeDouble(Math.random());//把产生的随机数直接写入到字节数组ByteArray中
      dos.writeBoolean(true);//布尔类型的数据在内存中就只占一个字节
      ByteArrayInputStream bais = new
      ByteArrayInputStream(baos.toByteArray());
      System.out.println(bais.available());
      DataInputStream dis = new DataInputStream(bais);
      System.out.println(dis.readDouble());//先写进去的就先读出来,调用readDouble()方法读取出写入的随机数
      System.out.println(dis.readBoolean());//后写进去的就后读出来,这里面的读取顺序不能更改位置,否则会打印出不正确的结果
      dos.close();
      bais.close();
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

通过bais这个流往外读取数据时,是一个字节一个字节地往外读取的,读出来的数据无法判断是字符串还是bool类型的值,因此要在它的外面再套一个流,通过dataInputStream把读出来的数据转换就可以判断了。

注意:读取数据时先写进去的要先读出来,因此读ByteArray字节数组数据的顺序应该是先把占8个字节的double类型的数读出来,然后再读只占一个字节的boolean类型的数,因为double类型的数是先写进数组里面的,读的时候要先读它。这就是所谓的 先写的要先读 。如果先读Boolean类型的数,那读出来的情况可能就是把double类型数的8个字节里面的一个字节读了出来。

5、处理流之五—打印流Print

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

  • 打印流:PrintStream PrintWriter

    • PrintWriter 和 PrintStream 都属于输出流,分别针对与字符和字节

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

    • PrintStream 和 PrintWriter 的输出不会抛出 IOException异常,用户通过检测错误状态获取错误信息

    • PrintStream 和 PrintWriter 有自动flush功能

    • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用PrintWriter 类。

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

  • Println方法用于多种数据类型的输出

PrintWriter(Writer out)
PrintWriter(Writer out,boolean autoFlush)
PrintWriter(OutputStream out)
PrintWriter(OutputStream out,boolean autoFlush)
PrintStream(OutputStream out)
PrintStream(OutputStream out,boolean autoFlush)

【测试代码】

/*这个小程序是重新设置打印输出的窗口,
* 把默认在命令行窗口输出打印内容设置成其他指定的打印显示窗口
*/
import java.io.*;

public class TestPrintStream{
  public static void main(String args[]){
    PrintStream ps = null;
    try{
      FileOutputStream fos = new FileOutputStream("E:\\教学\\班级                                               \\Test\\Lesson2\\src\\com\\kuang\\chapter\\log.txt");
      ps = new PrintStream(fos);//在输出流的外面套接一层打印流,用来控制打印输出
      if(ps != null){                                              						System.setOut(ps);//这里调用setOut()方法改变了输出窗口,以前写System.out.print()默认的输出窗口就是命令行窗口.
             //但现在使用System.setOut(ps)将打印输出窗口改成了由ps指定的文件里面,通过这样设置以后,打印输出时都会在指定的文件内打印输出
             //在这里将打印输出窗口设置到了log.txt这个文件里面,所以打印出来的内容会在log.txt这个文件里面看到
       }
      for(char c=0;c<=1000;c++){
        System.out.print(c+"\t");//把世界各国的文字打印到log.txt这个文件中去
      }
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

6、处理流之六—对象流Object

  • ObjectInputStreamOjbectOutputSteam

    • 用于存储和读取基本数据类型数据或对象的处理流。

    • 它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来

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

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

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

    • transient关键字:透明的,用它来修饰的成员变量在序列化的时候不予考虑,即当成不存在。

对象的序列化机制

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

  • 序列化是RMI(Remote Method Invoke –远程方法调用)过程的参数和返回值都必须实现的机制,而RMI 是JavaEE的基础。因此序列化机制是JavaEE平台的基础。

  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常。

    • Serializable

    • Externalizable

  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

    • private static final long serialVersionUID;

    • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。

    • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,显式声明。

  • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

使用对象流序列化对象

  • 若某个类实现了Serializable接口,该类的对象就是可序列化的:

    • 创建一个ObjectOutputStream

    • 调用ObjectOutputStream对象的writeObject(对象) 方法输出可序列化对象

    • 注意写出一次,操作flush()一次

  • 反序列化

    • 创建一个ObjectInputStream

    • 调用readObject() 方法读取流中的对象

  • 强调:如果某个类的属性不是基本数据类型或String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field 的类也不能序列化

//序列化:将对象写入到磁盘或进行网络传输
//使用ObjectOutputStream实现
//要求对象必须实现序列化
ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(("data.txt"));
Person p = new Person("韩梅梅",18,"中华大街",new Pet());
oos.writeObject(p);
oos.flush();//刷新操作
oos.close();
                                                
//反序列化:将磁盘中的对象数据源读出
//使用ObjectInputStream来实现                                               
ObjectInputStream ois = new ObjectInputStream( new FileInputStream(("data.txt"));
Person p1 = (Person)ois.readObject();
System.out.println(p1.toString());
ois.close();                                              

import java.io.*;

public class TestObjectIo {
  public static void main(String args[]) {
    T t = new T();
    t.k = 8;//把k的值修改为8
    try {
      FileOutputStream fos = new FileOutputStream("TestObjectIo.txt");
      ObjectOutputStream oos = new ObjectOutputStream(fos);
     //ObjectOutputStream流专门用来处理Object,在fos流的外面套接ObjectOutputStream流可以直接把一个Object写进去
     	oos.writeObject(t);//直接把一个t对象写入到指定的文件里面
     	oos.flush();
     	oos.close();
     	FileInputStream fis = new FileInputStream("TestObjectIo.txt");
     	ObjectInputStream ois = new ObjectInputStream(fis);
     // ObjectInputStream专门用来读一个Object的
     	T tRead = (T) ois.readObject();
     // 直接把文件里面的内容全部读取出来然后分解成一个Object对象,并使用强制转换成指定类型T
     	System.out.print(tRead.i + "\t" + tRead.j + "\t" + tRead.d + "\t" + tRead.k);
      ois.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
/*
* 凡是要将一个类的对象序列化成一个字节流就必须实现Serializable接口
* Serializable接口中没有定义方法,Serializable接口是一个标记性接口,用来给类作标记,
只是起到一个标记作用。
* 这个标记是给编译器看的,编译器看到这个标记之后就可以知道这个类可以被序列化 如果想把某个
类的对象序列化,就必须得实现Serializable接口
*/
class T implements Serializable {
  // Serializable的意思是可以被序列化的
  int i = 10;
  int j = 9;
  double d = 2.3;
  int k = 15;
  // transient int k = 15;
  // 在声明变量时如果加上transient关键字,那么这个变量就会被当作是透明的,即不存在。
}

直接实现Serializable接口的类是JDK自动把这个类的对象序列化,而如果实现public interfaceExternalizable extends Serializable的类则可以自己控制对象的序列化,建议能让JDK自己控制序列化的就不要让自己去控制

谈谈你对java.io.Serializable接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?

  • 实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。

  • 由于大部分作为参数的类如StringInteger等都实现了java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更灵活。

随机存取文件流-RandomAccessFile类

RandomAccessFile类

  • RandomAccessFile 声明在java.io包下,直接继承于java.lang.Object类,实现了DataInput、DataOutput这两个接口。

  • RandomAccessFile类既可以作为一个输入流,也可以作为一个输出流。

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

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

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

  • RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile类对象可以自由移动记录指针:

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

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

  • 构造器

    • public RandomAccessFile(File file, String mode)

    • public RandomAccessFile(String name, String mode)

  • 创建RandomAccessFile类实例需要指定一个mode 参数,该参数指定RandomAccessFile的访问模式:

    • r :以只读方式打开

    • rw:打开以便读取和写入

    • rwd:打开以便读取和写入;同步文件内容的更新

    • rws:打开以便读取和写入;同步文件内容和元数据的更新

  • 如果模式为只读r,则不会创建文件,而会去读取一个已经存在的文件。

    • 如果读取的文件不存在则会出现异常。

    • 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。

//读取文件内容
RandomAccessFile raf = new RandomAccessFile("test.txt","rw");
raf.seek(5);
byte[] b = new byte[1024];

int off = 0;
int len = 5;
raf.read(b,off,len);

String str = new String(b,0,len);
System.out.println(str);

raf.close

  • 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。

  • 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头开始覆盖)

  • 可以通过 相关的操作,实现RandomAccessFile “插入” 数据的效果

//写入文件内容
RandomAccessFile raf = new RandomAccessFile("test.txt","rw");
raf.seek(5);
//先读出来
String temp = raf.readLine();

raf.seek(5);
raf.write("xyz".getBytes());
raf.write(temp.getBytes());

raf.close();

ByteArrayOutputStream

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import org.junit.Test;

public class ByteArrayOutputStreamTest {

	@Test
	public void test1() throws Exception {
		FileInputStream fis = new FileInputStream("abc.txt");
		String info = readStringFromInputStream(fis);
		System.out.println(info);
	}

	private String readStringFromInputStream(FileInputStream fis) throws IOException {
		// 方式一:可能出现乱码
		// String content = "";
		// byte[] buffer = new byte[1024];
		// int len;
		// while((len = fis.read(buffer)) != -1){
		// content += new String(buffer);
		// }
		// return content;

		// 方式二:BufferedReader
		BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
		char[] buf = new char[10];
		int len;
		String str = "";
		while ((len = reader.read(buf)) != -1) {
			str += new String(buf, 0, len);
		}
		return str;

		// 方式三:避免出现乱码
		// ByteArrayOutputStream baos = new ByteArrayOutputStream();
		// byte[] buffer = new byte[10];
		// int len;
		// while ((len = fis.read(buffer)) != -1) {
		// baos.write(buffer, 0, len);
		// }
		//
		// return baos.toString();
	}
}

我们可以用RandomAccessFile这个类,来实现一个 多线程断点下载 的功能,用过下载工具都知道,下载前都会建立 两个临时文件 ,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能。

IO流总结

  • 流是用来处理数据的。

  • 处理数据时,一定要先明确数据源,与数据目的地

    • 数据源可以是文件,可以是键盘。

    • 数据目的地可以是文件、显示器或者其他设备。

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

NIO.2中Path、Paths、Files类的使用

1、Java NIO概述

  • Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。

  • Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO

    • java.nio.channels.Channel

    • FileChannel:处理本地文件

    • SocketChannel:TCP网络编程的客户端的Channel

    • ServerSocketChannel:TCP网络编程的服务器端的Channel

    • DatagramChannel:UDP网络编程中发送端和接收端的Channel

2、NIO.2

  • 随着JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为NIO.2。因为NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分。

3、Path、Paths和Files核心API

  • 早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息

  • NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。

  • 在以前IO操作都是这样写的:

import java.io.File;
File file = new File("index.html");
  • 但在Java7 中,我们可以这样写:

import java.nio.file.Path; 
import java.nio.file.Paths; 
Path path = Paths.get("index.html");
  • 同时,NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。

  • Paths 类提供的静态get() 方法用来获取Path 对象:

    • static Pathget(String first, String … more): 用于将多个字符串串连成路径

    • static Path get(URI uri): 返回指定uri对应的Path路径

4、Path接口

//Path 常用方法:
String toString() 
  返回调用Path对象的字符串表示形式
boolean startsWith(String path)
  判断是否以path路径开始
boolean endsWith(String path)
  判断是否以path路径结束
boolean isAbsolute()
  判断是否是绝对路径
Path getParent()
  返回Path对象包含整个路径,不包含Path对象指定的文件路径
Path getRoot()
  返回调用Path对象的根路径
Path getFileName() 
  返回与调用Path对象关联的文件名
int getNameCount()
  返回Path根目录后面元素的数量
Path getName(int idx)
  返回指定索引位置idx的路径名称
Path toAbsolutePath()
  作为绝对路径返回调用Path对象
Path resolve(Path p)
  合并两个路径,返回合并后的路径对应的Path对象
File toFile()
  将Path转化为File类的对象

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.junit.Test;

/**
 * 1. jdk7.0时,引入了Path、Paths、Files三个类。
 * 2.此三个类声明在:java.nio.file包下。
 * 3.Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
 * 4.如何实例化Path:使用Paths.
 * static Path get(String first, String … more) : 用于将多个字符串串连成路径
 * static Path get(URI uri): 返回指定uri对应的Path路径
 *
 */
public class PathTest {

    //如何使用Paths实例化Path
    @Test
    public void test1() {
        Path path1 = Paths.get("d:\\nio\\hello.txt");//new File(String filepath)
        Path path2 = Paths.get("d:\\", "nio\\hello.txt");//new File(String parent,String filename);

        System.out.println(path1);
        System.out.println(path2);

        Path path3 = Paths.get("d:\\", "nio");
        System.out.println(path3);
    }

    //Path中的常用方法
    @Test
    public void test2() {
        Path path1 = Paths.get("d:\\", "nio\\nio1\\nio2\\hello.txt");
        Path path2 = Paths.get("hello.txt");

//		String toString():返回调用Path对象的字符串表示形式
        System.out.println(path1);

//		boolean startsWith(String path):判断是否以path路径开始
        System.out.println(path1.startsWith("d:\\nio"));
//		boolean endsWith(String path):判断是否以path路径结束
        System.out.println(path1.endsWith("hello.txt"));
//		boolean isAbsolute():判断是否是绝对路径
        System.out.println(path1.isAbsolute() + "~");
        System.out.println(path2.isAbsolute() + "~");
//		Path getParent():返回Path对象包含整个路径,不包含Path对象指定的文件路径
        System.out.println(path1.getParent());
        System.out.println(path2.getParent());
//		Path getRoot():返回调用Path对象的根路径
        System.out.println(path1.getRoot());
        System.out.println(path2.getRoot());
//		Path getFileName():返回与调用Path对象关联的文件名
        System.out.println(path1.getFileName() + "~");
        System.out.println(path2.getFileName() + "~");
//		int getNameCount():返回Path根目录后面元素的数量
//		Path getName(int idx):返回指定索引位置idx的路径名称
        for (int i = 0; i < path1.getNameCount(); i++) {
            System.out.println(path1.getName(i) + "*****");
        }

//		Path toAbsolutePath():作为绝对路径返回调用Path对象
        System.out.println(path1.toAbsolutePath());
        System.out.println(path2.toAbsolutePath());
//		Path resolve(Path p):合并两个路径,返回合并后的路径对应的Path对象
        Path path3 = Paths.get("d:\\", "nio");
        Path path4 = Paths.get("nioo\\hi.txt");
        path3 = path3.resolve(path4);
        System.out.println(path3);

//		File toFile():将Path转化为File类的对象
        File file = path1.toFile();//Path--->File的转换
        Path newPath = file.toPath();//File--->Path的转换
    }
}

5、File类

  • java.nio.file.Files 用于操作文件或目录的工具类。

  • Files常用方法:

    • Path copy(Path src, Path dest, CopyOption … how): 文件的复制

    • Path createDirectory(Path path, FileAttribute<?> … attr): 创建一个目录

    • Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件

    • void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错

    • void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除

    • Path move(Path src, Path dest, CopyOption…how) : 将src 移动到dest 位置

    • long size(Path path) : 返回path 指定文件的大小

6、Files类

  • Files常用方法:用于判断

    • boolean exists(Path path, LinkOption … opts) : 判断文件是否存在

    • boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录

    • boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

    • boolean isHidden(Path path): 判断是否是隐藏文件

    • boolean isReadable(Path path): 判断文件是否可读

    • boolean isWritable(Path path): 判断文件是否可写

    • boolean notExists(Path path, LinkOption … opts): 判断文件是否不存在

  • Files常用方法:用于操作内容

    • SeekableByteChannel newByteChannel(Path path, OpenOption…how): 获取与指定文件的连接,how 指定打开方式。

    • DirectoryStream<Path> newDirectoryStream(Path path) : 打开path 指定的目录

    • InputStream newInputStream(Path path, OpenOption…how):获取InputStream 对象

    • OutputStream newOutputStream(Path path, OpenOption…how): 获取OutputStream 对象

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;

import org.junit.Test;

/**
 * Files工具类的使用:操作文件或目录的工具类
 */
public class FilesTest {

	@Test
	public void test1() throws IOException{
		Path path1 = Paths.get("d:\\nio", "hello.txt");
		Path path2 = Paths.get("atguigu.txt");
		
//		Path copy(Path src, Path dest, CopyOption … how):文件的复制
		//要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
//		Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
		
//		Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
		//要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
		Path path3 = Paths.get("d:\\nio\\nio1");
//		Files.createDirectory(path3);
		
//		Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
		//要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
		Path path4 = Paths.get("d:\\nio\\hi.txt");
//		Files.createFile(path4);
		
//		void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
//		Files.delete(path4);
		
//		void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
		Files.deleteIfExists(path3);
		
//		Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
		//要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
//		Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
		
//		long size(Path path) : 返回 path 指定文件的大小
		long size = Files.size(path2);
		System.out.println(size);

	}

	@Test
	public void test2() throws IOException{
		Path path1 = Paths.get("d:\\nio", "hello.txt");
		Path path2 = Paths.get("atguigu.txt");
//		boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
		System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));

//		boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
		//不要求此path对应的物理文件存在。
		System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));

//		boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

//		boolean isHidden(Path path) : 判断是否是隐藏文件
		//要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
//		System.out.println(Files.isHidden(path1));

//		boolean isReadable(Path path) : 判断文件是否可读
		System.out.println(Files.isReadable(path1));
//		boolean isWritable(Path path) : 判断文件是否可写
		System.out.println(Files.isWritable(path1));
//		boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
		System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
	}

	/**
	 * StandardOpenOption.READ:表示对应的Channel是可读的。
	 * StandardOpenOption.WRITE:表示对应的Channel是可写的。
	 * StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
	 * StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
	 *
	 * @author shkstart 邮箱:shkstart@126.com
	 * @throws IOException
	 */
	@Test
	public void test3() throws IOException{
		Path path1 = Paths.get("d:\\nio", "hello.txt");

//		InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
		InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);

//		OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
		OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);


//		SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
		SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

//		DirectoryStream<Path>  newDirectoryStream(Path path) : 打开 path 指定的目录
		Path path2 = Paths.get("e:\\teach");
		DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
		Iterator<Path> iterator = directoryStream.iterator();
		while(iterator.hasNext()){
			System.out.println(iterator.next());
		}

	}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值