Java I/O系统

Java I/O系统

2018/4/22

“对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务。”
                                                                ――《Thinking in Java》

1,IO的分类

Java IO一般包含两个部分:
1.java.io包中堵塞型IO;
2.java.nio包中的非堵塞型IO,通常称为New IO。
学过操作系统的朋友都知道系统运行的瓶颈一般在于IO操作,一般打开某个IO通道需要大量的时间,同时端口中不一定就有足够的数据,这样read方法就一直等待读取此端口的内容,从而浪费大量的系统资源。有人也许会提出使用java的多线程技术啊!但是在当前进程中创建线程也是要花费一定的时间和系统资源的,因此不一定可取。Java New IO的非堵塞技术主要采用了Observer模式,就是有一个具体的观察者和=监测IO端口,如果有数据进入就会立即通知相应的应用程序。这样我们就避免建立多个线程,同时也避免了read等待的时间。不过本篇主要讲述java的堵塞型IO,就是我们通常应用的那个包。

Java的IO主要包含三个部分:

1.流式部分――IO的主体部分;
2.非流式部分――主要包含一些辅助流式部分的类,
    如:File类、RandomAccessFile类和FileDescriptor等类;
3.文件读取部分的与安全相关的类,
    如:SerializablePermission类。
    以及与本地操作系统相关的文件系统的类,
        如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。

非流式部分:

1,File类

File:文件和目录(文件夹)路径名的抽象表示形式
构造方法:

    // File(String pathname):根据一个路径得到File对象
    // 把e:\\demo\\a.txt封装成一个File对象
    File file = new File("E:\\demo\\a.txt");

    // File(String parent, String child):根据一个目录和一个子文件/目录得到File对象
    File file2 = new File("E:\\demo", "a.txt");

    // File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对象
    File file3 = new File("e:\\demo");
    File file4 = new File(file3, "a.txt");

创建功能:

*创建功能:
*public boolean createNewFile():创建文件 如果存在这样的文件,就不创建了
*public boolean mkdir():创建文件夹 如果存在这样的文件夹,就不创建了
*public boolean mkdirs():创建文件夹,如果父文件夹不存在,会帮你创建出来
    // 需求:我要在e盘目录下创建一个文件夹demo
    File file = new File("e:\\demo");
    System.out.println("mkdir:" + file.mkdir());

    // 需求:我要在e盘目录demo下创建一个文件a.txt
    File file2 = new File("e:\\demo\\a.txt");
    System.out.println("createNewFile:" + file2.createNewFile());
    // 其实我们有更简单的方法
    File file7 = new File("e:\\aaa\\bbb\\ccc\\ddd");
    System.out.println("mkdirs:" + file7.mkdirs());

    // 看下面的这个东西:
    File file8 = new File("e:\\liuyi\\a.txt");
    System.out.println("mkdirs:" + file8.mkdirs());

删除功能:

public boolean delete()
    // 删除功能:我要删除a.txt这个文件
    File file3 = new File("a.txt");
    System.out.println("delete:" + file3.delete());

    // 删除功能:我要删除ccc这个文件夹
    File file4 = new File("aaa\\bbb\\ccc");
    System.out.println("delete:" + file4.delete()); 

重命名功能:public boolean renameTo(File dest)

  • 如果路径名相同,就是改名。
  • 如果路径名不同,就是改名并剪切。

判断功能:

  • public boolean isDirectory():判断是否是目录
  • public boolean isFile():判断是否是文件
  • public boolean exists():判断是否存在
  • public boolean canRead():判断是否可读
  • public boolean canWrite():判断是否可写
  • public boolean isHidden():判断是否隐藏

获取功能:

  • public String getAbsolutePath():获取绝对路径
  • public String getPath():获取相对路径
  • public String getName():获取名称
  • public long length():获取长度。字节数
  • public long lastModified():获取最后一次的修改时间,毫秒值
  • public String[] list():获取指定目录下的所有文件或者文件夹的名称数组
  • public File[] listFiles():获取指定目录下的所有文件或者文件夹的File数组

     // 指定一个目录
     File file = new File("e:\\");
    
     // public String[] list():获取指定目录下的所有文件或者文件夹的名称数组
     String[] strArray = file.list();
     for (String s : strArray) {
         System.out.println(s);
     }
     System.out.println("------------");
    
     // public File[] listFiles():获取指定目录下的所有文件或者文件夹的File数组
     File[] fileArray = file.listFiles();
     for (File f : fileArray) {
         System.out.println(f.getName());
     }
    

    文件名称过滤器

  • public String[] list(FilenameFilter filter)
  • public File[] listFiles(FilenameFilter filter)

    public static void main(String[] args) {

     // 封装e判断目录
     File file = new File("e:\\");
    
     // 获取该目录下所有文件或者文件夹的String数组,适合采用匿名内部类
     // public String[] list(FilenameFilter filter)
     String[] strArray = file.list(new FilenameFilter() {
         @Override
         public boolean accept(File dir, String name) {
             // 通过这个测试,我们就知道了,到底把这个文件或者文件夹的名称加不加到数组中,取决于这里的返回值是true还是false
             //使用list()可以回调accept()
             return new File(dir, name).isFile() && name.endsWith(".jpg");
         }
     });
    
     // 遍历
     for (String s : strArray) {
         System.out.println(s);
     }
    

    流式部分可以概括为:

    两个对应一个桥梁。

     两个对应指:
         1.字节流(Byte Stream)和字符流(Char Stream)的对应;
         2.输入和输出的对应。
     一个桥梁指:
         从字节流到字符流的桥梁。
             对应于输入和输出为InputStreamReader和OutputStreamWriter。
    

    在流的具体类中又可以具体分为:

    1.介质流(Media Stream或者称为原始流Raw Stream)

     ――主要指一些基本的流,他们主要是从具体的介质上,如:文件、内存缓冲区(Byte数组、Char数组、StringBuffer对象)等,读取数据;
    

    2.过滤流(Filter Stream)

     ――主要指所有FilterInputStream/FilterOutputStream和FilterReader/FilterWriter的子类,主要是对其包装的类进行某些特定的处理,如:缓存等。
    

2018/4/23

2,IO中的流,输入和输出

流具有最基本的特点:“One dimension , one direction .” 即流是一维的,同时流是单向的。关于维和我们通常说的一维长度,二维平面,三维空间,四维时空……是同一个概念,流就是一维的。单向就是只可以一个方向(按顺序从头至尾依次)读取,不可以读到某个位置,再返回前面某个位置。流的概念和实际水流的概念基本一致,水只可以从高向低一个方向流动。我们某时在目地喝了一口水,下次在同一个地点喝水已经不是当时的那片水了。

流的这种特性在JMS(Java Message Service)的API设计中得到了体现。JMS是J2EE平台下面向消息中间件的一个标准。(关于中间件技术有机会和大家探讨)JMS中有五种具体类型的消息,这些消息一般分为两类:1.流式的消息――包含ByteMessage和StreamMessage;2.非流式的消息――包含TextMessage、ObjectMessage和MapMessage。我们在明白IO中流的特点后,基本可以明白JMS API设计者的意图。

可能有些场合我们需要在文件中随机插入数据、在流中来来回回地执行某些操作,这时候我们绝对不可以使用流相关的对象。很幸运JDK的设计者为我们设计了一个单独的类RandomAccessFile,它可以完成打开、关闭文件、以基本数据类型的方式读取数据、读取下一个行、以UTF等格式读取数据、写入各种类型的数据、比较特殊的是他可以通过文件指针的seek方法让文件指针移到某个位置,可以通过getFilePointer方法得到当前指针的位置、可以通过length()方法得到当前文件的容量、通过getFD得到FileDescriptor对象,通过getChannel方法得到FileChannel对象,从而和New IO整合。

Java 流在处理上分为字符流和字节流。字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。
Java 内用 Unicode 编码存储字符,字符流处理类负责将外部的其他编码的字符流和 java 内 Unicode 字符流之间的转换。而类 InputStreamReader 和 OutputStreamWriter 处理字符流和字节流的转换。字符流(一次可以处理一个缓冲区)一次操作比字节流(一次一个字节)效率高。

在上面的关系图中可以看出:

  1. InputStream是所有的输入字节流的父类,它是一个抽象类。
  2. ByteArrayInputStream、StringBufferInputStream、FileInputStream是三种基本的介质流,它们分别将Byte数组、StringBuffer、和本地文件中读取数据。PipedInputStream是从与其它线程共用的管道中读取数据,与Piped相关的知识会用专门的一小节讲解。
  3. ObjectInputStream和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。下表列出了这些流的功能及如何使用它们(具体使用在讲解完装饰器模式后会举几个例子)。

在上面的关系图中可以看出:

  1. OutputStream是所有的输出字节流的父类,它是一个抽象类。
  2. ByteArrayOutputStream、FileOutputStream是两种基本的介质流,它们分别向Byte数组、和本地文件中写入数据。PipedOutputStream是向与其它线程共用的管道中写入数据,
  3. ObjectOutputStream和所有FilterOutputStream的子类都是装饰流。

2018/4/24

IO流的分类:

      流向:
          输入流    读取数据
          输出流 写出数据
      数据类型:
          字节流
              字节输入流    读取数据    InputStream
              字节输出流    写出数据    OutputStream
          字符流
              字符输入流    读取数据    Reader
              字符输出流    写出数据    Writer

注意:
a:一般我们在探讨IO流的时候,如果没有明确说明按哪种分类来说,默认情况下是按照数据类型来分的。
b:除非文件用windows自带的记事本打开我们能够读懂,才采用字符流,否则建议使用字节流。
OutputStream 通过查看API,我们发现该流对象是一个抽象类,不能实例化。 所以,我们要找一个具体的子类。
注意:每种基类的子类都是以父类名作为后缀名。

      XxxOutputStream
      XxxInputStream
      XxxReader
      XxxWriter

字节输出流操作步骤:

  • A:创建字节输出流对象
  • B:写数据,调用write()方法
  • C:释放资源
public void write(int b):写一个字节
public void write(byte[] b):写一个字节数组
public void write(byte[] b,int off,int len):写一个字节数组的一部分

    // 创建字节输出流对象
    // FileOutputStream(String name)
    FileOutputStream fos = new FileOutputStream("fos.txt");
    /*
     * 创建字节输出流对象了做了几件事情:
     * A:调用系统功能去创建文件
     * B:创建fos对象
     * C:把fos对象指向这个文件
     */        
    //写数据
    fos.write("hello,IO".getBytes());
    fos.write("java".getBytes());

    //释放资源
    //关闭此文件输出流并释放与此流有关的所有系统资源。
    fos.close();
    /*
     * 为什么一定要close()呢?
     * A:让流对象变成垃圾,这样就可以被垃圾回收器回收了
     * B:通知系统去释放跟该文件相关的资源
     */

如何实现数据的换行?

  • 因为不同的系统针对不同的换行符号识别是不一样的?
  • windows:\r\n
  • linux:\n
  • Mac:\r
  • 而一些常见的个高级记事本,是可以识别任意换行符号的。

如何实现数据的追加写入?

  • 用构造方法带第二个参数是true的情况即可
    // 创建字节输出流对象
    // 创建一个向具有指定 name 的文件中写入数据的输出文件流。如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
    FileOutputStream fos = new FileOutputStream("fos3.txt", true);
    // 写数据
    for (int x = 0; x < 10; x++) {
        fos.write(("hello" + x).getBytes());
        fos.write("\r\n".getBytes());
    }
    // 释放资源
    fos.close();

加入异常处理的字节输出流操作

    // 为了在finally里面能够看到该对象就必须定义到外面,为了访问不出问题,还必须给初始化值
    FileOutputStream fos = null;
    try {
        // fos = new FileOutputStream("z:\\fos4.txt");
        fos = new FileOutputStream("fos4.txt");
        fos.write("java".getBytes());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 如果fos不是null,才需要close()
        if (fos != null) {
            // 为了保证close()一定会执行,就放到这里了
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

字节输入流操作步骤:

  • A:创建字节输入流对象
  • B:调用read()方法读取数据,并把数据显示在控制台
  • C:释放资源

读取数据的方式:

  • A:int read():一次读取一个字节
  • B:int read(byte[] b):一次读取一个字节数组

计算机是如何识别什么时候该把两个字节转换为一个中文呢?

  • 在计算机中中文的存储分两个字节:
  • 第一个字节肯定是负数。
  • 第二个字节常见的是负数,可能有正数。但是没影响。

String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组
byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组

    // String -- byte[] 编码:把看得懂的变成看不懂的
   String s = "我爱你中国";
    // [-50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
    byte[] bys = s.getBytes();
    System.out.println(Arrays.toString(bys));

    // byte[] -- String 解码:把看不懂的变成看得懂的
    String ss = new String(bys); // 你好
    // String ss = new String(bys, "GBK"); // 你好
    // String ss = new String(bys, "UTF-8"); // ???
    System.out.println(ss);

应用:
复制文本文件。

  • 数据源:从哪里来
a.txt    --    读取数据    --    FileInputStream    
  • 目的地:到哪里去
b.txt    --    写数据        --    FileOutputStream

代码体现:

    // 封装数据源
    FileInputStream fis = new FileInputStream("a.txt");
    // 封装目的地
    FileOutputStream fos = new FileOutputStream("b.txt");
    int by = 0;
    while ((by = fis.read()) != -1) {
        fos.write(by);
    }
    // 释放资源(先关谁都行)
    fos.close();
    fis.close();

缓冲区类(高效类)

  • 写数据:BufferedOutputStream
  • 读数据:BufferedInputStream

构造方法可以指定缓冲区的大小,但是我们一般用不上,因为默认缓冲区大小就足够了。
为什么不传递一个具体的文件或者文件路径,而是传递一个OutputStream对象呢?

  • 原因很简单,字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是呢,真正的读写操作还得靠基本的流对象实现。

注意:虽然我们有两种方式可以读取,但是,请注意,这两种方式针对同一个对象在一个代码中只能使用一个。

    // BufferedInputStream(InputStream in)
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
            "bos.txt"));
    // 读取数据
    byte[] bys = new byte[1024];
    int len = 0;
    while ((len = bis.read(bys)) != -1) {
        System.out.print(new String(bys, 0, len));
    }
    // 释放资源
    bis.close();

字节流四种方式复制文件:

  • 基本字节流一次读写一个字节: 共耗时:117235毫秒
  • 基本字节流一次读写一个字节数组: 共耗时:156毫秒
  • 高效字节流一次读写一个字节: 共耗时:1141毫秒
  • 高效字节流一次读写一个字节数组: 共耗时:47毫秒
// 高效字节流一次读写一个字节数组:
public static void method4(String srcString, String destString)
        throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
            srcString));
    BufferedOutputStream bos = new BufferedOutputStream(
            new FileOutputStream(destString));

    byte[] bys = new byte[1024];
    int len = 0;
    while ((len = bis.read(bys)) != -1) {
        bos.write(bys, 0, len);
    }

    bos.close();
    bis.close();
}

// 高效字节流一次读写一个字节:
public static void method3(String srcString, String destString)
        throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
            srcString));
    BufferedOutputStream bos = new BufferedOutputStream(
            new FileOutputStream(destString));

    int by = 0;
    while ((by = bis.read()) != -1) {
        bos.write(by);

    }

    bos.close();
    bis.close();
}

// 基本字节流一次读写一个字节数组
public static void method2(String srcString, String destString)
        throws IOException {
    FileInputStream fis = new FileInputStream(srcString);
    FileOutputStream fos = new FileOutputStream(destString);

    byte[] bys = new byte[1024];
    int len = 0;
    while ((len = fis.read(bys)) != -1) {
        fos.write(bys, 0, len);
    }

    fos.close();
    fis.close();
}

// 基本字节流一次读写一个字节
public static void method1(String srcString, String destString)
        throws IOException {
    FileInputStream fis = new FileInputStream(srcString);
    FileOutputStream fos = new FileOutputStream(destString);

    int by = 0;
    while ((by = fis.read()) != -1) {
        fos.write(by);
    }

    fos.close();
    fis.close();
}

InputStream类型:

FilterOutputStream类型

OutputStream类型:

FilterInputStream类型

SequenceInputStream :把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来,

//不管怎么样,先创建两个文件。从里面读取数据,(我的文件里面有内容!)    
InputStream in1=new FileInputStream(new File("d:\\jack.txt"));    
InputStream in2=new FileInputStream(new File("d:\\jack2.txt"));    
//读取,数据后要生成的文件    
OutputStream ou=new FileOutputStream(new File("d:\\jack3.txt"));    
//创建SequenceInputStream类,    
SequenceInputStream si=new SequenceInputStream(in1,in2);    
//因为SequenceInputStream,是一个字节一个字节读,要判断一下是否读完了。    
int c=0;    
while((c=si.read())!=-1){    
//这里也是一个字节一个字节写的。    
ou.write(c);    
}    
//关闭所有的资源    
si.close();    
ou.close();    
in2.close();    
in1.close();    
}    

ObjectInputStream:用于操作Object的stream,这个在stream主要用在对象传输的过程中,其中牵涉到了序列化的知识

递归:方法定义中调用方法本身的现象
注意事项:

  • A:递归一定要有出口,否则就是死递归
  • B:递归的次数不能太多,否则就内存溢出
  • C:构造方法不能递归使用
    递归实现
  • a:做递归要写一个方法
  • b:出口条件
  • c:规律

示例:
把E:\JavaSE目录下所有的java结尾的文件的绝对路径给输出在控制台。

分析:
      A:封装目录
      B:获取该目录下所有的文件或者文件夹的File数组
      C:遍历该File数组,得到每一个File对象
      D:判断该File对象是否是文件夹
          是:回到B
          否:继续判断是否以.java结尾
             是:就输出该文件的绝对路径
             否:不搭理它

public class FilePathDemo {
public static void main(String[] args) {
    // 封装目录
    File srcFolder = new File("E:\\JavaSE");

    // 递归功能实现
    getAllJavaFilePaths(srcFolder);
}

private static void getAllJavaFilePaths(File srcFolder) {
    // 获取该目录下所有的文件或者文件夹的File数组
    File[] fileArray = srcFolder.listFiles();

    // 遍历该File数组,得到每一个File对象
    for (File file : fileArray) {
        // 判断该File对象是否是文件夹
        if (file.isDirectory()) {
            getAllJavaFilePaths(file);
        } else {
            // 继续判断是否以.java结尾
            if (file.getName().endsWith(".java")) {
                // 就输出该文件的绝对路径
                System.out.println(file.getAbsolutePath());
            }
        }
    }
}
}

3,Reader和Writer

设计Reader和Writer继承体系结构主要是为了国际化。老的I/O流继承层次结构仅支持8位字节流,并且不能很好的处理16位的Unicode字符。由于Unicode用于字符国家化(Java本身的char也是16位的Unicode),所以添加Reader和Writer继承层次体系结构就是为了在所有的I/O操作中都支持Unicode。

Reader


CharArrayReader :与 ByteArrayInputStream 对应此类实现一个可用作字符输入流的字符缓冲区
StringReader : 与 StringBufferInputStream 对应其源为一个字符串的字符流。
FileReader : 与 FileInputStream 对应
PipedReader :与 PipedInputStream 对应
InputStreamReader:将InputStream转化成Reader

Writer


CharArrayWriter: 与 ByteArrayOutputStream 对应
StringWriter:无与之对应的以字节为导向的 stream
FileWriter: 与 FileOutputStream 对应
PipedWriter:与 PipedOutputStream 对应
OutputStreamWriter:将OutputStream转化成Writer
PrintReader:和PrintStream对应

“适配器”类
InputStreamReader(InputStream is):用默认的编码读取数据
InputStreamReader(InputStream is,String charsetName):用指定的编码读取数据
OutputStreamWriter(OutputStream out):根据默认编码把字节流的数据转换为字符流
OutputStreamWriter(OutputStream out,String charsetName):根据指定编码把字节流数据转换为字符流

把字节流转换为字符流。

字符流 = 字节流 +编码表。

面试题:close()和flush()的区别?

A:close()关闭流对象,但是先刷新一次缓冲区。关闭之后,流对象不可以继续再使用了。
B:flush()仅仅刷新缓冲区,刷新之后,流对象还可以继续使用。

由于我们常见的操作都是使用本地默认编码,所以,不用指定编码。

而转换流的名称有点长,所以,Java就提供了其子类供我们使用。
    OutputStreamWriter = FileOutputStream + 编码表(GBK)
    FileWriter = FileOutputStream + 编码表(GBK)
    InputStreamReader = FileInputStream + 编码表(GBK)
    FileReader = FileInputStream + 编码表(GBK)

把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中

    // 封装数据源
    InputStreamReader isr = new InputStreamReader(new FileInputStream(
            "a.txt"));
    // 封装目的地
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
            "b.txt"));

    // 读写数据
    // 方式1
    // int ch = 0;
    // while ((ch = isr.read()) != -1) {
    // osw.write(ch);
    // }

    // 方式2
    char[] chs = new char[1024];
    int len = 0;
    while ((len = isr.read(chs)) != -1) {
        osw.write(chs, 0, len);
        // osw.flush();
    }
    // 释放资源
    osw.close();
    isr.close();

把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中

    // 封装数据源 基本字符流
    FileReader fr = new FileReader("a.txt");
    // 封装目的地
    FileWriter fw = new FileWriter("b.txt");

    // 一次一个字符
    // int ch = 0;
    // while ((ch = fr.read()) != -1) {
    // fw.write(ch);
    // }

    // 一次一个字符数组
    char[] chs = new char[1024];
    int len = 0;
    while ((len = fr.read(chs)) != -1) {
        fw.write(chs, 0, len);
        fw.flush();
    }

    // 释放资源
    fw.close();
    fr.close();

字符流为了高效读写,也提供了对应的字符缓冲流。

  • BufferedWriter:字符缓冲输出流
  • BufferedReader:字符缓冲输入流

字符缓冲流的特殊方法:

BufferedWriter:
    public void newLine():根据系统来决定换行符
BufferedReader:
    public String readLine():一次读取一行数据
      包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null

把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中

    // 封装数据源 字符缓冲流
    BufferedReader br = new BufferedReader(new FileReader("a.txt"));
    // 封装目的地
    BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
    // 两种方式其中的一种一次读写一个字符数组
    char[] chs = new char[1024];
    int len = 0;
    while ((len = br.read(chs)) != -1) {
        bw.write(chs, 0, len);
        bw.flush();
    }
    // 释放资源
    bw.close();
    br.close();

    // 封装数据源 字符缓冲流
    BufferedReader br = new BufferedReader(new FileReader("a.txt"));
    // 封装目的地
    BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
    // 读写数据(推荐)
    String line = null;
    while ((line = br.readLine()) != null) {
        bw.write(line);
        bw.newLine();
        bw.flush();
    }
    // 释放资源
    bw.close();
    br.close();

BufferedReader

    |--LineNumberReader
        public int getLineNumber()获得当前行号。 
        public void setLineNumber(int lineNumber)

代码体现:

    LineNumberReader lnr = new LineNumberReader(new FileReader("my.txt"));
    // 从10开始才比较好
    // lnr.setLineNumber(10);
    String line = null;
    while ((line = lnr.readLine()) != null) {
        System.out.println(lnr.getLineNumber() + ":" + line);
    }
    lnr.close();

可以读写基本数据类型的数据

  • 数据输入流:DataInputStream

     DataInputStream(InputStream in)
    
  • 数据输出流:DataOutputStream

     DataOutputStream(OutputStream out) 
    

    格式化的内存输入输出:

private static void read() throws IOException {
    // DataInputStream(InputStream in)
    // 创建数据输入流对象
    DataInputStream dis = new DataInputStream(
            new FileInputStream("dos.txt"));
    // 读数据
    byte b = dis.readByte();
    short s = dis.readShort();
    int i = dis.readInt();
    long l = dis.readLong();
    float f = dis.readFloat();
    double d = dis.readDouble();
    char c = dis.readChar();
    boolean bb = dis.readBoolean();
    // 释放资源
    dis.close();
    System.out.println(b);
    System.out.println(s);
    System.out.println(i);
    System.out.println(l);
    System.out.println(f);
    System.out.println(d);
    System.out.println(c);
    System.out.println(bb);
}
private static void write() throws IOException {
    // DataOutputStream(OutputStream out)
    // 创建数据输出流对象
    DataOutputStream dos = new DataOutputStream(new FileOutputStream(
            "dos.txt"));
    // 写数据了
    dos.writeByte(10);
    dos.writeShort(100);
    dos.writeInt(1000);
    dos.writeLong(10000);
    dos.writeFloat(12.34F);
    dos.writeDouble(12.56);
    dos.writeChar('a');
    dos.writeBoolean(true);
    // 释放资源
    dos.close();
}
例2:
public class FormattedMemoryInput{
    public static void main(String[] args) throws IOException {
    try {
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(
        //The method read(byte[]) in the type FilterInputStream is not applicable for the arguments (String)
                BufferedInputStream.read("FormattedMemoryInput.java")
                        .getBytes()));
        while (true)
            System.out.print((char) in.readByte());
    } catch (EOFException e) {
        System.err.println("End of str eam");
    }
   }
}

内存操作流:用于处理临时存储信息的,程序结束,数据就从内存中消失。

字节数组:
      ByteArrayInputStream
      ByteArrayOutputStream
字符数组:
      CharArrayReader
      CharArrayWriter
字符串:
      StringReader
      StringWriter

    // 写数据
    // ByteArrayOutputStream()
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 写数据
    for (int x = 0; x < 10; x++) {
        baos.write(("hello" + x).getBytes());
    }
    // 释放资源
    // 通过查看源码我们知道这里什么都没做,所以根本需要close()
    // baos.close();
    // public byte[] toByteArray()
    byte[] bys = baos.toByteArray();
    // 读数据
    // ByteArrayInputStream(byte[] buf)
    ByteArrayInputStream bais = new ByteArrayInputStream(bys);
    int by = 0;
    while ((by = bais.read()) != -1) {
        System.out.print((char) by);
    }
    // bais.close();

打印流

字节流打印流 PrintStream
字符打印流 PrintWriter
打印流的特点:

    A:只有写数据的,没有读取数据。只能操作目的地,不能操作数据源。
    B:可以操作任意类型的数据。
    C:如果启动了自动刷新,能够自动刷新。
    D:该流是可以直接操作文本文件的。

哪些流对象是可以直接操作文本文件的呢?

        FileInputStream
        FileOutputStream
        FileReader
        FileWriter
        PrintStream
        PrintWriter

看API,查流对象的构造方法,如果同时有File类型和String类型的参数,一般来说就是可以直接操作文件的。

流:

基本流:就是能够直接读写文件的
高级流:在基本流基础上提供了一些其他的功能

1:可以操作任意类型的数据。

print()
println()

2:启动自动刷新

PrintWriter pw = new PrintWriter(new FileWriter("pw2.txt"), true);
    还是应该调用println()的方法才可以
    这个时候不仅仅自动刷新了,还实现了数据的换行。
println()
   其实等价于于:
       bw.write();
       bw.newLine();        
       bw.flush();

示例:
DataStreamDemo.java复制到Copy.java中
分析:

数据源:
     DataStreamDemo.java -- 读取数据 -- FileReader -- BufferedReader
目的地:
    Copy.java -- 写出数据 -- FileWriter -- BufferedWriter -- PrintWriter

    // 封装数据源
    BufferedReader br = new BufferedReader(new FileReader(
            "DataStreamDemo.java"));
    // 封装目的地
    PrintWriter pw = new PrintWriter(new FileWriter("Copy.java"), true);

    String line = null;
    while((line=br.readLine())!=null){
        pw.println(line);
    }

    pw.close();
    br.close();

转换流

标准输入输出流

System类中的两个成员变量:
    public static final InputStream in “标准”输入流。
    public static final PrintStream out “标准”输出流。
    InputStream is = System.in;
    PrintStream ps = System.out;

    // 有这里的讲解我们就知道了,这个输出语句其本质是IO流操作,把数据输出到控制台。
    System.out.println("helloworld");
    // 获取标准输出流对象
    PrintStream ps = System.out;
    ps.println("helloworld");        
    ps.println();
    // ps.print();//这个方法不存在    
    // System.out.println();
    // System.out.print();

System.in 标准输入流。是从键盘获取数据的

键盘录入数据:

    A:main方法的args接收参数。
        java HelloWorld hello world java
    B:Scanner(JDK5以后的)
        Scanner sc = new Scanner(System.in);
        String s = sc.nextLine();
        int x = sc.nextInt()
    C:通过字符缓冲流包装标准输入流实现
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

代码实现:

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    System.out.println("请输入一个字符串:");
    String line = br.readLine();
    System.out.println("你输入的字符串是:" + line);

    System.out.println("请输入一个整数:");
    // int i = Integer.parseInt(br.readLine());
    line = br.readLine();
    int i = Integer.parseInt(line);
    System.out.println("你输入的整数是:" + i);

随机访问流:

RandomAccessFile类不属于流,是Object类的子类。实现了DataInput和DataOutput,Closeable接口
但它融合了InputStream和OutputStream的功能。
支持对文件的随机访问读取和写入。

public RandomAccessFile(String name,String mode):第一个参数是文件路径,第二个参数是操作文件的模式。
    模式有四种,我们最常用的一种叫"rw",这种方式表示我既可以写数据,也可以读取数据 

private static void read() throws IOException {
    // 创建随机访问流对象
    RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
    int i = raf.readInt();
    System.out.println(i);
    // 该文件指针可以通过 getFilePointer方法读取,并通过 seek 方法设置。
    System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
    char ch = raf.readChar();
    System.out.println(ch);
    System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
    String s = raf.readUTF();
    System.out.println(s);
    System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
    // 我不想重头开始了,我就要读取a,怎么办呢?
    raf.seek(4);
    ch = raf.readChar();
    System.out.println(ch);
}

private static void write() throws IOException {
    // 创建随机访问流对象
    RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
    // 怎么玩呢?
    raf.writeInt(100);
    raf.writeChar('a');
    raf.writeUTF("中国");
    raf.close();
}

SequenceInputStream

    // SequenceInputStream(InputStream s1, InputStream s2)
    // 需求:把ByteArrayStreamDemo.java和DataStreamDemo.java的内容复制到Copy.java中
    InputStream s1 = new FileInputStream("ByteArrayStreamDemo.java");
    InputStream s2 = new FileInputStream("DataStreamDemo.java");
    SequenceInputStream sis = new SequenceInputStream(s1, s2);
    BufferedOutputStream bos = new BufferedOutputStream(
            new FileOutputStream("Copy.java"));

    // 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
    byte[] bys = new byte[1024];
    int len = 0;
    while ((len = sis.read(bys)) != -1) {
        bos.write(bys, 0, len);
    }

    bos.close();
    sis.close();

实例二:

    // 需求:把下面的三个文件的内容复制到Copy.java中
    // ByteArrayStreamDemo.java,CopyFileDemo.java,DataStreamDemo.java
    // SequenceInputStream(Enumeration e)
    // 通过简单的回顾我们知道了Enumeration是Vector中的一个方法的返回值类型。
    // Enumeration<E> elements()
    Vector<InputStream> v = new Vector<InputStream>();
    InputStream s1 = new FileInputStream("ByteArrayStreamDemo.java");
    InputStream s2 = new FileInputStream("CopyFileDemo.java");
    InputStream s3 = new FileInputStream("DataStreamDemo.java");
    v.add(s1);
    v.add(s2);
    v.add(s3);
    Enumeration<InputStream> en = v.elements();
    SequenceInputStream sis = new SequenceInputStream(en);
    BufferedOutputStream bos = new BufferedOutputStream(
            new FileOutputStream("Copy.java"));
    // 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
    byte[] bys = new byte[1024];
    int len = 0;
    while ((len = sis.read(bys)) != -1) {
        bos.write(bys, 0, len);
    }
    bos.close();
    sis.close();

序列化流

详见《Thinking in Java》的18.12节
序列化流:把对象按照流一样的方式存入文本文件或者在网络中传输。对象 -- 流数据(ObjectOutputStream)
反序列化流:把文本文件中的流对象数据或者网络中的流对象数据还原成对象。流数据 -- 对象(ObjectInputStream)

private static void read() throws IOException, ClassNotFoundException {
    // 创建反序列化对象
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
            "oos.txt"));

    // 还原对象
    Object obj = ois.readObject();

    // 释放资源
    ois.close();

    // 输出对象
    System.out.println(obj);
}

private static void write() throws IOException {
    // 创建序列化流对象
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
            "oos.txt"));

    // 创建对象
    Person p = new Person("林青霞", 27);

    // public final void writeObject(Object obj)
    oos.writeObject(p);

    // 释放资源
    oos.close();
}

NotSerializableException:未序列化异常

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。
该接口居然没有任何方法,类似于这种没有方法的接口被称为标记接口。

异常分析:

java.io.InvalidClassException: 
cn.itcast_07.Person; local class incompatible: 
stream classdesc serialVersionUID = -2071565876962058344, 
local class serialVersionUID = -8345153069362641443

为什么会有问题呢?
    Person类实现了序列化接口,那么它本身也应该有一个标记值。
    这个标记值假设是100。
    开始的时候:
    Person.class -- id=100
    wirte数据: oos.txt -- id=100
    read数据: oos.txt -- id=100    
    现在:
   Person.class -- id=200
   wirte数据: oos.txt -- id=100
   read数据: oos.txt -- id=100

我们在实际开发中,可能还需要使用以前写过的数据,不能重新写入。怎么办呢?

回想一下原因是因为它们的id值不匹配。
每次修改java文件的内容的时候,class文件的id值都会发生改变。
而读取文件的时候,会和class文件中的id值进行匹配。所以,就会出问题。
但是呢,如果我有办法,让这个id值在java文件中是一个固定的值,这样,你修改文件的时候,这个id值还会发生改变吗?
不会。现在的关键是我如何能够知道这个id值如何表示的呢?
我们要知道的是:
    看到类实现了序列化接口的时候,要想解决黄色警告线问题,就可以自动产生一个序列化id值。
    而且产生这个值以后,我们对类进行任何改动,它读取以前的数据是没有问题的。

注意:

我一个类中可能有很多的成员变量,有些我不想进行序列化。请问该怎么办呢?
使用transient关键字声明不需要序列化的成员变量

部分代码:

public class Person implements Serializable {
   private static final long serialVersionUID = -2071565876962058344L;
   private String name;
   private transient int age;
}

Externalizable

1Externalizable代替实现Serializable接口——来对序列化过程进行控制。
Externalizable接口继承了Serializable,同时增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。

Externalizable于Serializable区别:
1,对于Serializable对象:对象完全以它存储的二进制位为基础来构造,而不调用构造器。
对于Externalizable对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用readExternal()。必须记住这一点,所有的默认构造器都会被调用,才能使Externalizable对象产生正确的行为。
2,实现Externalizable接口的类可以防止对象的敏感部分序列化,可以在writeExternal()内部找对所需部分进行显示的序列化
若为Serializable对象,那么所有序列化操作都会执行,为了能够控制,可以用transient关键字逐个字段的关闭序列化。

属性集合类 Properties

Properties:属性集合类。是一个可以和IO流相结合使用的集合类。

Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。 
是Hashtable的子类,说明是一个Map集合。
该类不是一个泛型类,在使用的时候就不能加泛型
    Properties prop = new Properties();

示例一:

    Properties prop = new Properties();
    // 添加元素
    prop.put("it002", "hello");
    prop.put("it001", "world");
    prop.put("it003", "java");
    // System.out.println("prop:" + prop);
    // 遍历集合
    Set<Object> set = prop.keySet();
    for (Object key : set) {
        Object value = prop.get(key);
        System.out.println(key + "---" + value);
    }

示例二:
特殊功能:

  • public Object setProperty(String key,String value):添加元素
  • public String getProperty(String key):获取元素
  • public Set stringPropertyNames():获取所有的键的集合
    // 创建集合对象
    Properties prop = new Properties();
    // 添加元素
    prop.setProperty("张三", "30");
    prop.setProperty("李四", "40");
    prop.setProperty("王五", "50");
    // public Set<String> stringPropertyNames():获取所有的键的集合
    Set<String> set = prop.stringPropertyNames();
    for (String key : set) {
        String value = prop.getProperty(key);
        System.out.println(key + "---" + value);
    }

这里的集合必须是Properties集合:

  • public void load(Reader reader):把文件中的数据读取到集合中
  • public void store(Writer writer,String comments):把集合中的数据存储到文件

案例:
模拟一个猜数字小游戏的程序,请写一个程序实现在测试类中只能用5次,超过5次提示:游戏试玩已结束,请付费。

public class PropertiesTest2 {
public static void main(String[] args) throws IOException {
    // 读取某个地方的数据,如果次数不大于5,可以继续玩。否则就提示"游戏试玩已结束,请付费。"
    // 把数据加载到集合中
    Properties prop = new Properties();
    Reader r = new FileReader("count.txt");
    prop.load(r);
    r.close();
    // 我自己的程序,我当然知道里面的键是谁
    String value = prop.getProperty("count");
    int number = Integer.parseInt(value);
    if (number > 5) {
        System.out.println("游戏试玩已结束,请付费。");
        System.exit(0);
    } else {
        number++;
        prop.setProperty("count", String.valueOf(number));
        Writer w = new FileWriter("count.txt");
        prop.store(w, null);
        w.close();
        GuessNumber.start();
    }
}
}

public class GuessNumber {
private GuessNumber() {
}
public static void start() {
    // 产生一个随机数
    int number = (int) (Math.random() * 100) + 1;
    // 定义一个统计变量
    int count = 0;
    while (true) {
        // 键盘录入一个数据
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入数据(1-100):");
        int guessNumber = sc.nextInt();
        count++;
        // 判断
        if (guessNumber > number) {
            System.out.println("你猜的数据" + guessNumber + "大了");
        } else if (guessNumber < number) {
            System.out.println("你猜的数据" + guessNumber + "小了");
        } else {
            System.out.println("恭喜你," + count + "次就猜中了");
            break;
        }
    }
}
}

nio

nio包在JDK4出现,提供了IO流的操作效率。目的在于提高速度。
JDK7的之后的nio:

Path:路径
Paths:有一个静态方法返回一个路径
        public static Path get(URI uri)
Files:提供了静态方法供我们使用
      public static long copy(Path source,OutputStream out):复制文件
      public static Path write(Path path,Iterable<? extends CharSequence> lines,Charset cs,OpenOption... options)

Java IO 的一般使用原则 :

一、按数据来源(去向)分类:

1 、是文件: FileInputStream, FileOutputStream, ( 字节流 )FileReader, FileWriter( 字符 )
2 、是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 字节流 )
3 、是 Char[]: CharArrayReader, CharArrayWriter( 字符流 )
4 、是 String: StringBufferInputStream, StringBufferOuputStream ( 字节流 )StringReader, StringWriter( 字符流 )
5 、网络数据流: InputStream, OutputStream,( 字节流 ) Reader, Writer( 字符流 )

二、按是否格式化输出分:

1 、要格式化输出: PrintStream, PrintWriter

三、按是否要缓冲分:

1 、要缓冲: BufferedInputStream, BufferedOutputStream,( 字节流 ) BufferedReader, BufferedWriter( 字符流 )

四、按数据格式分:

1 、二进制格式(只要不能确定是纯文本的) : InputStream, OutputStream 及其所有带 Stream 结束的子类
2 、纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer 及其所有带 Reader, Writer 的子类

五、按输入输出分:

1 、输入: Reader, InputStream 类型的子类
2 、输出: Writer, OutputStream 类型的子类

六、特殊需要:

1 、从 Stream 到 Reader,Writer 的转换类: InputStreamReader, OutputStreamWriter
2 、对象输入输出: ObjectInputStream, ObjectOutputStream
3 、进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter
4 、合并输入: SequenceInputStream
5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader

决定使用哪个类以及它的构造进程的一般准则如下(不考虑特殊需要):

首先,考虑最原始的数据格式是什么: 原则四
第二,是输入还是输出:原则五
第三,是否需要转换流:原则六第 1 点
第四,数据来源(去向)是什么:原则一
第五,是否要缓冲:原则三 (特别注明:一定要注意的是 readLine() 是否有定义,有什么比 read, write 更特殊的输入或输出方法)
第六,是否要格式化输出:原则二

猜你喜欢

转载自blog.csdn.net/qq_24140237/article/details/80078858