12. JAVA IO Party 1 (目标、File类、RandomAccessFile类、字节流和字符流、转换流) ----- 学习笔记

本章目标:

  1. 掌握java.io包中类的继承的关系
  2. 可以使用File类进行文件的操作
  3. 可以使用字节流或字符流操作文件内容并区分出字节流与字符流的区别
  4. 掌握内存操作输入/输出流的使用
  5. 了解线程通讯流 --- 管道流的作用
  6. 掌握System类对IO的支持System.out、System.err、System.in
  7. 可以使用打印流方便地打印输出的内容,并可以使用Java新特性格式化输出
  8. 可以使用BufferReader类读取缓冲区中的内容
  9. 了解Java提供的输入工具类Scanner类的使用
  10. 掌握数据操作流DataInputStream和DataOuputStream类的使用
  11. 可以使用SequenceInputStrem合并两个文件的内容
  12. 可以使用压缩流完成ZIP文件格式的压缩
  13. 了解回退流(PushBackInputStream)类的作用
  14. 了解字符的主要编码类型及乱码产生的原因
  15. 掌握对象序列化的作用以及Serializable接口、Externalizable接口、transient关键字的使用

Java IO操作主要指使用Java进行输入、输出操作。Java中的所有操作类都存放在java.io包中,在使用时需要导入此包。

在整个java.io包中最重要的就是5个类和一个接口:

  1. File类
  2. OuputStream类
  3. InputStream类
  4. Writer类
  5. Reader类
  6. Serializable接口


12.1  File类  -----  操作文件的类

       12.1.1  File类的基本介绍

                在整个io包中,唯一与文件本身有关的类就是File类。使用File类可以进行创建或删除文件等常用操作。

        File类的构造方法如下:

public File(String pathname)                       //实例化File类时,必须设置好路径!!!!!!!!!!!!!!!!

如果要使用一个File类,则必须向File类的构造方法中传递一个文件路径。 例如,现在要操作D盘下的test.txt文件,则路径必须写成“ D:\\test.txt ”,其中“ \\ ”表示一个" \ ";

要操作文件,还需要使用File类中定义的若干方法,File类的主要方法如下所示:

12. JAVA IO Party 1 (目标、File类、RandomAccessFile类、字节流和字符流、转换流) ----- 学习笔记_第1张图片

Q: 为什么File类中的敞亮定义的命名规则不符合标准命名规则?一个常量的全部标识符应该全部大写!! 

A:因为Java的发展是经过了相当长的时间,而命名规范也是逐步形成的,File类因为出现的较早,所以当时并没有对明明规范有严格的要求,这些都属于Java的历史遗留问题。

       12.1.2 使用File类操作文件

(1) 创建一个新文件。

      File类的对象实例化完成之后,就可以使用createNewFile()方法创建一个新文件。但是此方法是用了throws关键字表示不进行异常的处理,所以在使用createNewFile()方法时,必须使用try...catch进行异常的处理。

范例:创建新文件

/*
 *File类应用之创建新文件
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo01{
    public static void main(String args[]){
        File f = new File("D:\\test.txt");    //若此路径不存在,编译会出错!
        try{
            f.createNewFile();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

----不同的操作系统中,路径的分隔符表示是不一样的,例如:

  • Windows中使用反斜杠表示目录的分隔符“ \ ”
  • Linux中使用正斜杠表示目录的分隔符 “ / ”

          那么,既然Java程序程序本身具有可移植性的特性,则在编写路径时最好可以根据程序所在的操作系统自动使用符合本地操作系统要求的分隔符,这样才能达到可移植性的目的。 要实现这样的功能,则就需要用到File类中提供的两个常量:separator、pathSeparator!!!!!!!

/*
 *File类应用之路径分隔符
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo02{
    public static void main(String args[]){
        System.out.println("pathSeparator is: " + File.pathSeparator);
        System.out.println("separator is: " + File.separator);
    }
}

  • File.pathSeparator指的是分割连续多个路径字符串的分隔符, 例如    java -cp test.jar;abc.jar  HelloWorld   就是指 " ; "  (有点像classpath里面的分隔符)
  • File.separator才是用来分割同一个路径字符串中的目录的, 例如    C:\Program Files\Common Files 就是指  " \ "

针对不同的操作系统,创建新文件的通用程序:

/*
 *File类应用之创建新文件,针对不同的操作系统。
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo03{
    public static void main(String args[]){
        String path = "C:" + File.separator + "newFile.txt";
        File f = new File(path);
        try{
            f.createNewFile();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

  • 在操作文件时,一定要使用File.separator表示分隔符。  在程序的开发中,往往会使用Windows开发环境,因为在Windows操作系统中支持的工具较多,使用方便;而在程序发布时,往往是直接在Linux或其他操作系统上部署,所以这个时候如果不是用File.separator,则程序运行就有可能存在问题!!!!

(2)删除一个指定的文件

   File类中也支持删除文件的操作,如果要删除一个文件,则可以使用File类中的delete()方法。

/*
 *File类应用之删除指定的文件
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo04{
    public static void main(String args[]){
        String path = "C:" + File.separator + "newFile.txt";    //必须给出路径
        File f = new File(path);
        f.delete();
        //File f = new File("C:" + File.separator +"newFile.txt");
        //f.delete()
    }
}

但是FileDemo04的操作有一个问题,就是在删除文件前应该保证文件存在,所以以上程序在使用时最好先判断文件是否存在;如果存在,则执行删除操作。判断一个文件是否存在可以直接使用File类提供的exists()方法,此方法返回boolean类型。

====在删除文件中增加判断====

/*
 *File类应用之删除指定的文件,增加判断
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo05{
    public static void main(String args[]){
        String path = "C:" + File.separator + "newFile.txt";    //必须给出路径
        File f = new File(path);
        if(f.exists()){
        	f.delete();
        }
    }
}

(3)综合创建和删除文件的操作

    现给定一个文件的路径,如果此文件存在,则将其删除,如果文件不存在则创建一个新的文件。

/*
 *File类应用之综合创建和删除文件的操作
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo06{
    public static void main(String args[]){
        String path = "C:" + File.separator + "newFile.txt";    //必须给出路径
        File f = new File(path);
        if(f.exists()){
        	f.delete();
        }else{
            try{
            	f.createNewFile();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

以上程序完成了所要求的功能,但是在每次程序执行完毕之后,文件并不会立刻创建或删除,而是会有一些延时!!!这是因为所有的操作都需要通过JVM完成所造成的。

***************所以在进行文件操作时,一定要考虑到延时的影响*******************

  • 文件后缀可有可无。 一个文件的后缀本身没有任何的意义,即不管有没有后缀,并不影响文件本身的内容。 但是在Windows中为了实现程序使用的便捷化管理,应将文件的后缀进行比较合理的应用

(4)创建一个文件夹

   除了可以创建文件之外,File类还可以指定一个文件夹。直接使用mkdir()方法就可以完成创建一个文件的操作功能

/*
 * 创建文件夹
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo07{
    public static void main(String args[]){
        String path = "C:" + File.separator + "newFolder"; 
        File f = new File(path);   //给定路径
        f.mkdir();                 //创建文件夹
    }
}

(5)列出指定目录的全部文件

      如果现在给出了一个目录,则可以直接列出目录中的内容。File类中定义了如下两个列出文件夹内容的方法:

  1. public String[] list():  列出全部名称,返回一个字符串数组
  2. public File[] listFiles():  列出完整路径,返回一个File对象数组

范例:使用list()方法列出一个目录中的全部内容

/*
 *使用list()方法列出一个目录中的全部内容
 */
public class FileDemo08{
    public static void main(String args[]){
        String path = "C:" + File.separator;
        File f = new File(path);
        String s[] = f.list();   //列出给定目录中的内容
        for(String s1:s){
            System.out.println(s1 + "、");
        }
    }
}

以上程序运行后列出的只是指定目录中的文件名称,并不是一个文件的完整路径,所以如果想列出每一个文件的完整路径,就必须使用另外一个列出方法-----------listFiles()

范例:使用listFiles()方法列出一个目录中的全部内容

/*
 *使用listFiles()方法列出一个目录中的全部内容
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo09{
    public static void main(String args[]){
        String path = "C:" + File.separator;
        File f = new File(path);
        File files[] = f.listFiles();
        for(File f1:files){
            System.out.println(f1);
        }
    }
}

以上是直接打印File类对象,可以把一个完整的路径取出。两者比较之后可以发现,使用listFiles()方法列出目录中的内容更为方便!!!!!!!!

(6)判断一个给定的路径是否是目录

      可以使用File类中的方法isDirectory()判断给定的路径是否是目录。

范例:判断给定路径是否是目录

/*
 *使用isDirectory()判断给定路径是否是目录
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo10{
    public static void main(String args[]){
        String path = "C:" + File.separator;
        File f = new File(path);
        if(f.isDirectory()){
            System.out.println(f.getPath() + "路径是目录。");
        }else{
            System.out.println(f.getPath() + "路径不是目录。");
        }
    }
}

       12.1.3范例 ----- 列出指定目录的全部内容

     给定一个目录,要求列出此目录下的全部内容,因为给定目录有可能存在子文件夹,此时要求也可以把所有的子文件夹的子文件列出来。 

/*
 *列出指定目录的全部内容
 */
package org.forfan06.filedemo;
import java.io.File;
import java.io.IOException;
public class FileDemo11{
    public static void main(String args[]){
        String path = "C:" + File.separator +  "Java Demo";
        File myfile = new File(path);
        print(myfile);
    }
    
    public static void print(File file){             //递归调用此方法
        if(file != null){                      //增加一个检查机制
            if(file.isDirctory()){      //判断是否是目录
                File files[] = file.listFiles();    //如果是目录,则列出所有内容
                if(files != null){             //有可能有无法列出目录中的文件
                    for(File subfile:files){   
                        print(subfile);     //继续列出内容
                    }
                }
            }else{
                System.out.println(file);    //如果不是目录,则直接打印路径信息
            }
        }
    }
}

12.2 RandomAccessFile类

           File类只是针对文件本身进行操作,而如果要针对文件内容进行操作,则可以使用RandomAccessFile类,此类属于随机读取类。 可以随机地读取一个文件中指定位置的数据。

    加入现在在文件中保存了以下3个数据:

  • forfan06, 27
  • linda, 31
  • eric, 17

      那么如果使用RandomAccessFile类读取“linda”信息时,就可以将“forfan06”的信息跳过,相当于在文件中设置了一个指针,根据此指针的位置进行读取。 但是如果想要实现这样的功能,则每个数据的长度应该保持一致,所以在设置姓名时应统一设置为8位,数字为4位。

      想要实现该功能,必须依靠RandomAccess中的几种模式,然后在构造方法中传递此模式! RandomAccess类中的常用方法如下所示:

12. JAVA IO Party 1 (目标、File类、RandomAccessFile类、字节流和字符流、转换流) ----- 学习笔记_第2张图片

             需要注意的是,如果使用rw方式声明RandomAccessFile对象时,要写入的文件不存在,系统将自动进行创建!!! 

       12.2.1 使用RandomAccessFile类写入数据

   下面写入上面3个数据,为了保证可以进行随机读取,所以写入的名字都是8个字节;写入的数字都是固定的4个字节。

范例:写文件

package org.forfan06.randomaccessfiledemo;
import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
public class RandomAccessFileDemo01{
    public static void main(String args[]) throws Exception{  //直接抛出异常,程序中可以不再分别处理
        File f = new File("C:" + File.separator + "test.txt");
        RandomAccessFile raf = null;   //声明一个RandomAccessFile类对象
        raf = new RandomAccessFile(f, "rw");  //调用构造方法,以读写方式打开文件,会自动创建新文件
        
        setFile(raf, "forfan06", 27);
        setFile(raf, "linda   ", 17);
        setFile(raf, "eric    ", 31);
        
        raf.close();         //关闭文件
    }
    
    public static void setFile(RandomAccessFile raf, String name, int age) throws IOException{
        String temp = new String(name.getBytes(), "UTF-8");
        raf.writeBytes(temp);
        //!!!!此时打开test.txt文件看,年龄显示为乱码!!那是因为int使用的是一个4个字节保存的,想显示出来必须转换为字符串!!
        raf.writeInt(age);
        //下面这个可以解决中文乱码问题!!
        //byte[] bs = name.getBytes("UTF-8");
        //raf.write(bs);
    }
}
 
  

写完后可以直接通过RandomAccessFile类进行随机读取

       12.1.2 使用RandomAccessFile类读取数据

    读取数据时直接使用 r 的模式即可,以只读的方式打开文件。

    读取时所有的字符串只能按照byte数组的方式读取出来。而且所有的长度是8位

范例:随机读取数据 

package org.forfan06.randomaccessdemo;
import java.io.File;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo02{
    //throw the exception out directly, do not deal with the exception in the code
    public static void main(String args[]){
        String path = "D:" + File.separator + "test.txt";
        File f = new File(path);
        
        RandomAccessFile raf = null;
        raf = new RandeomAccessFile(f, "r");
        
        String name = null;
        int age = 0;
        
        byte[] b = new byte[8];
        raf.skipBytes(12);
        for(int i = 0; i < b.length; i++){
            b[i] = raf.readByte();
        }
        name = new String(b);
        age = raf.readInt();
        System.out.println("the second person's info: name: " + name + ", age: " + age);
        
        raf.seek(0);
        b= new byte[8];
        for(int i = 0; i < b.length; i++){
            b[i] = raf.readByte();
        }
        name = new String(b);
        age = raf.readInt();
        System.out.println("the first person's info: name: " + name + ", age: " + age);
        
        raf.skipBytes(12);
        b = new byte[8];
        for(int i = 0; i < b.length; i++){
            b[i] = raf.readByte();
        }
        name = new String(b);
        age = raf.readInt();
        System.out.println("the third person's info: name: " + name + ", age: " +age);
        
        raf.close();
    }
}

程序可以随机跳过12位读取信息,也可以回到开始点重新读取。主要是看seek方法将pointer设置到哪里~

随机读写流可以实现对文件内容的操作,但是一般情况下操作文件内容往往会使用字节或字符流。

12.3 字节流与字符流基本操作

               在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据时要使用输入流读取数据,而当程序需要将一些数据保存起来时,就要使用输出流! 

               在java.io包中, 流的操作主要有字节流、字符流2大类,2个类都有输入和输出操作。

  • 在字节流中,输出数据主要使用OutputStream类完成, 输入使用的是InputStream类;
  • 在字符流中,输出数据主要使用Writer类完成,  输入使用的主要是Reader类。

          在java中IO操作也是有相应步骤的,以文件的操作为例,主要的操作流程如下:

  1. 使用File类打开一个文件,
  2. 通过字节流或字符流得子类指定输出的位置,
  3. 进行读/写操作 ,
  4. 关闭输入/输出

       12.3.1 字节流

           字节流主要操作 byte  类型数据,以byte数组为例,主要操作类是OutputStream类和InputStream类。

(1)字节输出流:OutputStream

   OutputStream是整个IO包中字节输出流的最大父类, 此类的定义如下:

public abstract class OutputStream extends Object implements Closeable, Flushable

 从它的定义可以发现,OutputStream类是一个抽象类,如果要使用此类,则首先必须通过子类实例化对象。

  如果现在要操作的是一个文件,则可以使用FileOutputStream类,通过向上转型后,可以为OutputStream类实例化。

FileOutputStream类的其中一个构造方法为:

public FileOutputStream(File file) throws FileNotFoundException

在OutputStream类中的主要操作方法如下:

public void close() throws IOException                          //关闭输出流
public void flush() throws IOException                  //刷新缓冲区
public void write(byte[] b) throws IOException                                  //将一个byte[]数组写入数据流
public void write(byte[] b, int off, int len) throws IOException              //将一个指定范围的byte[]数组写入数据流
public abstract void write(int b) throws IOException                  //将一个字节数据写入数据流

此时使用FileOutputStream子类,此类的构造方法如下:

public FileOutputStream(File file) throws FileNotFoundException

操作时,必须接收File类的实例对象,指明要输出的文件路径!!!

========================================

OutputStream类还实现了Closeable接口和Flushable接口,

Closeable接口定义如下:

public interface Closeable{
    void close() throws IOException
}

Flushable接口定义如下:

public interface Flushable{
     void flush() throws IOException
}

Closeable接口和Flushable接口的作用从其定义方法中可以发现, Closeable表示可关闭;Flushable表示可刷新。 而且在OutputStream类中已经有了这2个方法的实现,所以操作时用户一般不会关心这两个接口,而直接使用OutputStream类即可!!!!

========================================

范例:向文件中写入字符串

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
public class OutputStreamDemo01{
    public static void main(String args[]) throws IOException{
        //Step 1: 使用File类实例定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2: 通过子类实例化父类对象
        OutputStream out = null;
        out = new FileOutputStream(f);  //通过对象多态性,进行实例化
        String str = "Hello World!!!";
        byte b[] = str.getBytes();  //只能输出byte[]数组,所以将字符串变为byte数组
        out.write(b);
        out.close();
    }
}

以上程序, 在实例化、写、关闭时都有异常产生的可能。为了方便,统一不处理!!!

  • 文件不存在的情况下,系统会自动创建!!!!!!

范例:使用write(int b)的方式写入内容

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
public class OutputStreamDemo02{
    public static void main(String args[]) throws IOException{
        //Step 1: 使用File类对象定位一个文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2: 通过子类实例化父类对象
        OutputStream out = new FileOutputStream(f);
        //Step 3: 进行写操作
        String str = "Hello World!!!";
        byte b[] = str.getBytes();
        
        for(int i = 0; i < b.length; i++){
            out.write(b[i]);
        }
        //Step 4:  关闭输出流
        out.close();
    }
}

(2)追加新内容

    之前的操作中,如果重新执行程序,则肯定会覆盖文件中的已有内容,那么此时可以通过FileOutputStream类向文件中追加内容。FileOutputStream的另外一个构造方法如下:

public FileOutputStream(File file, Boolean append) throws FileNotFoundException

                     在构造方法中,如果将append设置为true,则表示在文件的末尾追加内容

范例:追加文件内容

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
public class OutputStreamDemo03{
    public static void main(String args[]) throws IOException{
        //Step 1: 通过File类实例定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2:通过子类实例化父类对象
        OutputStream out = new FileOutputStream(f, true);
        //Step 3:进行写操作
        String str = "Hello World!!!";
        byte b[] = str.getBytes();
        for(int i = 0; i< b.length; i++){
            out.write(b[i]);
        }
        //Step 4:关闭输出流
        out.close();
    }
}

追加文件内容,换行问题??

如果要换行,则直接在字符串要换行处加入一个 “ \r\n ”。

//Step 3: 进行写操作
String str = "\r\n Hello World!!!";    //准备一个字符串

(3)字节输入流InputStream

既然程序可以向文件中写入内容,则可以通过InputStream从文件中把文件读取出来。 InputStream类的定义如下:

public abstract class InputStream extends Object implements Closeable

与OutputStream类一样,InputStream本身也是一个抽象类,必须依靠其子类,如果现在从文件中读取, 子类肯定是FileInputStream类。

FileInputStream类的主要方法有:

public int available() throws IOException                            //可以取得输入文件的大小
public void close() throws IOException                //关闭输入流
public abstract int read() throws IOException               //读取内容,以数字的方式读取
public int read(byte[] b) throws IOException             //将内容读到byte数组中,同时返回读入的个数!!!


FileInputStream类的构造方法:

public FileInputStream(File file) throws FileNotFoundException


范例: 从文件中读取内容

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class InputStreamDemo01{
    public static void main(String args[]) throws IOException{
        //Step 1:通过File类对象定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2:通过子类实例化父类对象
        InputStream in = new FileInputStream(f);
        //Step 3:进行读操作
        byte b[] = new byte[1024];  //把内容读到此数组中
        in.read(b);
        //Step 4:关闭输入流
        in.close();
        System.out.println("内容为:" + new String(b));
    }
}

文件内容被成功读取出来了。但是因为上面byte数组是我们自己定义的,这有可能会发生实际的内容与我们开辟的空间出入非常大的情况,此时会有很多的无用空间!!也在最后输出时,将很多空白转换成了字符串进行显示。

 I:如果想要解决上面的空白转换为字符串的问题,就必须用到read()方法,它返回了一个表示向数组中写入了多少个数据的int返回值。

范例: 优化InputStreamDemo01

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class InputStreamDemo02{
    public static void main(String args[]) throws IOException{
        //Step 1:通过File类对象定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2:通过子类实例化父类对象
        InputStream in = new FileInputStream(f);
        //Step 3:进行读操作
        byte b[] = new byte[1024];  //把内容读到此数组中
        int len = in.read(b);
        //Step 4:关闭输入流
        in.close();
        Sytem.out.println("读入数据的长度为:" + len);
        System.out.println("内容为:" + new String(b, 0 , len));
    }
}

 II:解决开辟空间与实际内容相差较大的问题。 则要从File类中着手,因为File类中存在一个length()方法,此方法可以取得文件的大小!!!!!!!!!!

public long length()throws SecurityException              //returns the length of the file denoted by this abstract pathname. the return vlue is unspecified if this pathname denotes a directory

范例: 开辟指定大小的byte数组

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class InputStreamDemo03{
    public static void main(String args[]) throws IOException{
        //Step 1:通过File类对象定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2:通过子类实例化父类对象
        InputStream in = new FileInputStream(f);
        //Step 3:进行读操作
        byte b[] = new byte[(int)f.length()];  //把内容读到此数组中,数组大小由文件决定
        in.read(b);
        //Step 4:关闭输入流
        in.close();
        System.out.println("内容为:" + new String(b));
    }
}


除了以上方式外,还可以通过循环从文件中一个个地把内容读取进来,直接使用read()方法即可!!!

范例:使用read()通过循环读取

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class InputStreamDemo04{
    public static void main(String args[]) throws IOException{
        //Step 1: 通过File类对象定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(f);
        //Step 2: 通过子类实例化父类对象
        InputStream in = new FileInputStream(f);
        //Step 3: 进行读操作
        byte b[] = new byte[(int)f.length()];
        for(int i = 0; i < b.length; i++){
            b[i] = (byte)in.read();
        }
        //Step 4: 关闭输入流
        in.close();
        Sytem.out.println("内容是:" + new String(b));
    }
}

如果不知道要输入的内容大小时,只能通过判断是否读到了文件的末尾的方式来读取文件!!!!!

范例:判断是否读到了文件的末尾方式读取文件

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class InputStreamDemo05{
    public static void main(String args[]) throws IOException{
        //Step 1: 通过File类对象定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(f);
        //Step 2: 通过子类实例化父类对象
        InputStream in = new FileInputStream(f);
        //Step 3: 进行读操作
        byte b[] = new byte[1024];
        int len = 0;
        int temp = 0;
        while((temp = in.read()) != -1){
            //将每次的读取内容给temp变量,如果temp不是-1,则表示文件没有读完
            b[len] = (byte) temp;
            len++;
        }
        //Step 4: 关闭输入流
        in.close();
        Sytem.out.println("内容是:" + new String(b, 0, len));
    }
}

只有当输入流得内容已经读到底,才会返回-1!!!!!!

       12.3.2 字符流 

                在程序中一个字符等于两个字节,那么Java提供了Reader类和Writer类两个专门操作字符流的类。

(1)  字符输出流Writer    Writer类本身是一个字符流得输出类,此类的定义如下:

public abstract class Writer extends Object implements Appendable, Closeable, Flushable

此类本身是一个抽象类,如果要使用此类,则肯定要使用其子类。此时如果是向文件中写入内容,应该使用FileWriter的子类。

Writer类的常用方法有:

public abstract void close() throws IOException          //关闭输出流
public void write(String str) throws IOException      //将字符串输出
public void write(char[] cbuf) throws IOException         //将字符数组输出
public abstract void flush() throws IOException     //强制性清空缓存


FileWriter类的构造方法定义如下:

public FileWriter(File file) throws IOException



==========================================

Appendable接口定义:此接口表示内容可以被追加,接收的参数是CharSequence,实际上String类就实现了此接口,所以可以直接通过此接口的方法向输出流中追加内容

public interface Appendable{
     Appendable append(CharSequence csq) throws IOException;
     Appendable append(CharSequence csq, int start, int end) throws IOException
     Appendable append(char c) throws IOException
}

==========================================
范例:向文件中写入数据

package org.forfan06.chariodemo;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.io.IOException;
public class WriterDemo01{
    public static void main(String args[]) throws IOException{
        //Step 1: 通过File类对象定位文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(f);
        //Step 2: 通过子类实例化父类对象
        Writer writer= new FileWriter(f);
        //Step 3: 进行写操作
        String str = "Hello World!!!";
        writer.write(str);
        //Step 4: 关闭输出流
        writer.close();
    }
}
       上面程序与OutputStream类的操作流程并没有什么太大的区别,唯一的好处是, 可以直接输出字符串,而不用讲字符串变为byte数组之后再输出处理。

(2)使用FileWriter追加文件的内容

   在使用字符流操作时,也可以实现文件的追加功能,直接使用FileWriter类中的下面构造方法即可实现追加功能:

public FileWriter(File file, boolean append) throws IOException

将append的值设置为true, 表示文件内容可追加!!!

范例:追加文件内容

package org.forfan06.chariodemo;
import java.io.File;
import java.io.Writer;
import java.io.FileWriter;
import java.io.IOException;
public class WriterDemo02{
    public static void main(String args[]) throws IOException{
        //Stemp 1: 通过File类对象定位到文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Stemp 2: 通过子类实例化父类对象
        Writer writer = new FileWriter(f, true);
        //Stemp 3: 进行写操作
        String str = "\r\nHello World!!!";
        writer.write(str);
        //Stemp 4: 关闭输出流
        writer.close();
    }
}

(3)字符输入流Reader

  Reader类是使用字符的方式从文件中取得数据, Reader类的定义如下:

public abstract class Reader extends Object implements Readable, Closeable

    Reader类是一个抽象类,如果现在要从文件中读取内容,则可以使用FileReader子类。

Reader类的常用方法有:

public abstract void close() throws IOException         //关闭输入流
public int read() throws IOException   //读取单个字符
public int read(char[] cbuf) throws IOException      //将内容读到字符数组中,返回读入的长度


FileReader类的构造方法:

public FileReader(File file) throws FileNotFoundException


范例:从文件中读取内容

package org.forfan06.chariodemo;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.io.IOException;
public class ReaderDemo01{
    public static void main(String args[]) throws IOException{
        //Step 1: 通过File类对象定位到文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2: 通过子类实例化父类对象
        Reader reader = new FileReader(f);
        //Step 3: 进行读操作
        char c[] = new char[1024];
        int len = reader.read(c);
        //Step 4: 关闭输入流
        reader.close();
        System.out.println("内容为:" + new String(c, 0, len));
    }
}

范例: 使用循环的方式读取内容。 判断是否读完文件的标记,返回值为-1

package org.forfan06.chariodemo;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.io.IOException;
public class ReaderDemo01{
    public static void main(String args[]) throws IOException{
        //Step 1: 通过File类对象定位到文件
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2: 通过子类实例化父类对象
        Reader reader = new FileReader(f);
        //Step 3: 进行读操作
        char c[] = new char[1024];
        int len = 0;
        int temp = 0;
        while((temp = reader.read()) != -1){
            c[len] = (char) temp;
            len++;
        }
        //Step 4: 关闭输入流
        reader.close();
        System.out.println("内容为:" + new String(c, 0, len));
    }
}

       12.3.3 字节流与字符流得区别

        字节流在操作时本身不会用到缓冲区(内存), 是文件本身直接操作; 而字符流在操作时使用了缓冲区,通过缓冲区再操作文件!!!

下面以两个写文件的操作为主进行比较。 为了看出区别,我们在操作时字节流和字符流得操作完成之后都不关闭输出流

范例1,使用字节流, 不关闭输出流

package org.forfan06.byteiodemo;
import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputStreamDemo07{
    public static void main(String args[]) throws IOException{
        //Step 1:
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2: 
        OutputStream out = new FileOutputStream(f, true);
        String str = "Hello World!!!";
        byte b[] = str.getBytes();
        //Step 3:
        out.write(b);
        //Step 4:
        
    }
}

===此时没有关闭字节流操作,但是运行程序后,文件中依然存在了输出的内容。 证明字节流是直接操作文件本身的!!!!!!!!!!!!

范例2,使用字符流,不关闭输出流

package org.forfan06.chariodemo;
import java.io.File;
import java.io.Writer;
import java.io.FileWriter;
import java.io.IOException;
public class WriterDemo07{
    public static void main(String args[]) throws IOException{
        //Step 1:
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2:
        Writer writer = new FileWriter(f, true);
        //Step 3:
        String str = "Hello World!!!";
        writer.write(str);
        //Step 4:
    }
}

=====执行程序后,发现文件中没有任何内容。这是因为字符流操作时使用了缓冲区,而在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭输出/输入流,则缓冲区中的内容是无法输出的。

!!!!!字符流使用了缓冲区,而字节流没有使用缓冲区!!!!!

 ------------------------------------------------------------------------------------

缓冲区可以简单地理解为一段内存区域。 某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库), 则性能会很低,此时为了提升性能,就可以将一部份数据暂时读入到内存的一块区域之中,以后直接从此区域读取数据即可,因为读取内存速度会比较快。这样可以提升程序的性能!

   在字符流得操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。。。

 ------------------------------------------------------------------------------------

  如果想在不关闭时也可以将字符流得内容全部输出,则可以使用Writer类中的flush()方法完成

范例:强制性清空缓冲区

package org.forfan06.chariodemo;
import java.io.File;
import java.io.Writer;
import java.io.FileWriter;
public class WriterDemo08{
    public static void main(String args[]) throws Exception{
        //Step 1:
        String path = "E:" + File.separator + "test.txt";
        File f = new File(path);
        //Step 2: 
        Writer writer = new FileWriter(f, true);
        //Step 3:
        String str = "Hello World!!!";
        writer.write(str);
        
        writer.flush();
        //Step 4:
    }
}

此时,内容也存在了文件中! 更加进一步证明了内容是保存在缓冲区的。 这点,在以后的开发中要特别特别注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  • 使用字节流好,还是字符流好??   所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式储存的;而字符是只有在内存中才会形成。 所以在开发中,字节流使用较为广泛!!!

       12.3.4 范例  -- 文件复制

(1)实现要求, 在dos命令中存在一个文件的复制命令(copy), 例如,现在要将D盘中的test.txt文件复制到D盘中的demo.txt文件。 如果输入的源文件不存在,则提示找不到指定的文件信息。 下面使用java完成以上功能,程序运行时可以按照如下格式进行: 

 java copy 源文件 目标文件

(2) 程序实现

   从运行格式中可以发现,要想输入源文件或目标文件的路径,可以通过命令行参数完成,但是此时必须对输入的参数进行验证。如果输入的参数个数不是两个,或者输入的源文件路径不存在,则程序都应该给出错误信息并退出。

   此时又面临一个选择,即是使用字节流完成操作还是使用字符流完成操作呢??

   因为要复制的文件不一定都是文本文件,也有可能包含图片或声音等,所以如果此时使用字符流的话肯定不能很好地完成操作,所以必须使用字节流完成,使用OutputStream类和 InputStream类。

    而且要完成这样的复制程序可以用以下两种方式操作:

  1. 将源文件中的内容全部读取到内存中,并一次性写入到目标文件中
  2. 不将源文件中的内容全部读取进来,而是采用边读边写的方式

  很明显,采用第2中方式更加合理,因为将源文件的内容一次性读取进来的话,如果文件内容过大,则整个内存是无法保存的,程序肯定会出现异常; 而如果采取边读边写的方式,肯定要比全部读进来性能高很多。下面就按照这种思路来完成程序。

范例: 实现复制功能

package org.forfan06.byteiodemo;
import java.io.FileOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
 * @author HE
 * java小程序完成复制功能
 */
public class Copy {
	public static void main(String[] args) {
		if(args.length != 2){    //判断是否是两个参数
			System.out.println("输入的参数不正确!!");
			System.out.println("例如: java Copy 源文件路径   目标文件路径 ");
			System.exit(1);
		}
		File f1 = new File(args[0]);   //源文件的File对象
		File f2 = new File(args[1]);   //目标文嘉的呢File对象 
		if(!f1.exists()){
			System.out.println("源文件不存在!!!");
			System.exit(1);
		}
		InputStream input = null;  //准备好输入流对象,读取源文件
		OutputStream out = null;   //准备好输出流对象,写入目标文件
		try{
			input = new FileInputStream(f1);
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}
		try{
			out = new FileOutputStream(f2);
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}
		if(input != null && out != null){  //判断输入或输出是否准备好
			int temp = 0;
			try{
				while((temp = input.read()) != -1){   //开始复制 
					out.write(temp);           //边读边写
				}
			}catch(IOException e){
				e.printStackTrace();
				System.out.println("复制失败!");
			}
			try{
				input.close();
				out.close();
			}catch(IOException e){
				e.printStackTrace();
			}
		}
	}

}

12.4 转换流 --- OutputStreamWriter类与 InputStreamReader类

   整个IO 包实际上分为字节流和字符流,但是除了这两个流之外,还存在一组字节流-字符流的转换类。

  • OutputStreamWriter: 是Writer类的子类, 将输出的字符流变为字节流;即将一个字符流的输出对象变为字节流输出对象
  • InputStreamReader:  是Reader类的子类, 将输入的字节流变为字符流; 即将一个字节流的输入对象变为字符流的输入对象

    如果以文件操作为例,则内存中的字符数据需要通过OutputStreamWriter变为字节流 才能保存在文件中;读取时需要将读入的字节流通过InputStreamReader变为字符流,转换步骤如下所示:

12. JAVA IO Party 1 (目标、File类、RandomAccessFile类、字节流和字符流、转换流) ----- 学习笔记_第3张图片

OutputStreamWriter类的构造方法如下 :

public OutputStreamWriter(OutputStream out)

范例: 将字节输出流变为字符输出流

package org.forfan06.changeiodemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class OutputStreamWriterDemo01 {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		String path = "E:" + File.separator + "test.txt";
		File f = new File(path);
		Writer out = null;
		out = new OutputStreamWriter(new FileOutputStream(f));
		out.write("Hello World!!!");
		out.close();
	}

}

范例: 将字符输入流变为字节输入流

package org.forfan06.changeiodemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
public class InputStreamReaderDemo01 {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		String path = "E:" + File.separator + "test.txt";
		File f = new File(path);
		Reader reader = null;
		reader = new InputStreamReader(new FileInputStream(f));
		char c[] = new char[1024];
		int len = reader.read(c);
		reader.close();
		System.out.println(new String(c, 0, len));
	}

}

  • FileWriter和FileReader的说明: 从JDK文档中可以知道FileOutputStream是OutputStream类的直接子类;FileInputStream也是InputStream的直接之类; 但是在字符流文件中的两个操作类却有一些特殊: FileWriter并不直接是Writer的子类,而是OutputStreamWriter的子类;而FileReader也不直接是Reader的子类,而是InputStreamReader的子类。 那么从这两个类的继承关系就可以清楚地发现,不管使用字节流还是字符流实际上最终都是以字节的形式操作输入/输出流的。

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