zoukankan      html  css  js  c++  java
  • Java IO

     Java的IO可以说对于初学者是非常不友好了,内容多,名词容易混。这里就由浅入深详细介绍下。

    内容较多,新手请从头看起,如有基础则直接点击下面的链接跳转阅读!

    目录:

    1.IO是什么?

    2.IO的体系与命名方法

    3.纯文本文件(字符流)的读写(FileReader,FileWriter)

    |--3.1 FileReader

    |--3.1.1 简单的读取

    |--3.1.2 使用字符数组读取数据

    |--3.2 FileWriter

    |--3.2.1 简单的写入

    |--3.2.2 文件的续写

    |--3.3 IO异常

    |--3.4 小应用-文本文件的复制

    |--3.5 缓冲区技术(Buffered)

    |--3.6 小应用-使用缓冲区复制文件

    |--3.7 为输出标行号

    4.非纯文本(字节流)的读写(FileInputStream,FileOutputStream)

    |--4.4.1 从键盘输入

    |--4.4.2 流的转换

    |--4.4.3 流的重定向

    5.文件(File)

    |--5.1 文件的概述

    |--5.2 文件(夹)对象的定义

    |--5.3 文件的创建、删除、判断与获取文件信息

    |--5.3.1创建

    |--5.3.2 删除

    |--5.3.3 判断

    |--5.3.4 获取信息

    |--5.4 递归获取、删除文件

    6.PrintWriter

    7.对象的持久化存储

    8.管道流

    9.RandomAccessFile

    10.DataStream


    1.IO是什么?

    我们免不了会对设备间的数据进行读取与写入(内存,硬盘等等都算设备),Java对数据的操作定义为了“流”。对流的操作都封装在了Java的IO包中(java.io)。对于流,我们可以有如下划分:

    按照流向分:输入流(InputStream),输出流(OutputStream)

    按照数据类型分:字节流,字符流

    流向分比较好理解,这里简单说下字节流与字符流。

    字节流是对文件的基本操作流(byte),这个文件可以是音频,可以是文档,因为字节就是我们最小的基本数据类型(字不是基本数据类型,java不认),用字节就可以处理所有类型的文件。

    而字符流是对字节流的进一步说明,是对于纯文本文件(txt)的简单操作。是用来处理文字的流。

    也就是说,所有流本质上都是都是字节,只不过人们为了方便,使用字符流来处理文字。

    2.IO的体系与命名方法

    乍一看很乱,其实很简单,掌握规律就好了。

    java.lang.object

      |--InputStream   

      |--OutputStream  这两个是字节流的抽象基类

      |--Reader

      |--Writer   这两个是字符流的抽象基类

    由这两对抽象的基类可以派生出很多子类,规律就是:

    父类名都是子类的后缀,换句话说,后缀名说明了这个类的操作对象是字节流还是字符流。前缀名是这个类的功能增强。

    举两个例子:FileInputStream,后缀名说明了这是一个操作输入字节流的类,前缀说明其是对文件进行操作的

    再者,BufferedReader,后缀名表明这是操作字符流的,前缀说明为这个流加了一个缓冲区功能(后面会说到缓冲区)。

    3.纯文本文件(字符流)的读写(FileReader,FileWriter)

    3.1 FileReader

    3.1.1 简单的读取

    我们首先看下比较简单的纯文本文件的读写。

     1  public static void main(String[] args) throws IOException
     2     {
     3         FileReader fileReader = new FileReader("D:/Demo.txt");
     4         int ch;
     5         while ((ch = fileReader.read()) != -1)
     6         {
     7             System.out.print((char) ch);
     8         }
     9         fileReader.close();
    10     }

    首先我们定义了一个文件读取流对象,构造函数传入的是我们指定目录下的文件。


    (题外话,文件路径相关)

    1.相对路径与绝对路径

    相对路径是从当前路径开始的路径。比如我们现在在D:/program,那么相对路径就会从D盘开始。我们要是想去找D:/program/Dict.exe,就可以直接表示为./Dict.exe,或者直接Dict.exe。只不过前面的写法比较严谨。“.”表示当前路径,“..”表示上一层路径(父路径)。

    绝对路径就是不能偷懒,必须从头写起,也就是D:/program/Dict.exe。

    2.“/”和“”

    在linux中,使用“/”来表示文件路径,但是在win中“/”和“”没什么不同,唯一需要注意一点的是,还有转义字符这一层含义,我们如果有这么一个路径:D: ext.txt,这里 就成回车了。所以说,我们需要将进行转义,也就是“\”来表示路径:

    D:\next.txt。但是我们要是用/就没事儿了:D:/next.txt。所以我比较喜欢/。


     

    然后fileReader对象的read()方法会读取一个字符,但要注意返回值是int类型,我们打印的时候要转换为char。read()每执行一次就会向后移动一个单位,如果到达文件结尾会返回-1。我们使用while来遍历整个文件,结束条件当然就是返回值为-1的时候。

    最重要的一点,在对流操作完毕之后,一定要关闭流。

    3.1.2 使用字符数组读取数据

     1 public static void main(String[] args) throws IOException
     2     {
     3         FileReader fileReader = new FileReader("D:/Demo.txt");
     4         int num;
     5         char str[] = new char[1024];
     6         while ((num = fileReader.read(str) ) != -1)
     7         {
     8             System.out.println(new String(str,0,num));
     9         }
    10     }

    .read()有一个重载.read(char cbuf[]),可以将数据读入字符数组,返回的是读取到的字符数,若读到文件结尾返回-1。我们定义了一个字符数组且大小为1024(定义时最好定义为1024的整数倍),打印时我们在里面建立了一个字符串对象,指定了字符数组、开始位置与结束位置,也就是有效数据长度,这样就可以避免数据在最后不足1024位而打印多余数据的问题。

    3.2 FileWriter

    3.2.1 简单的写入

    我们将一些文字写入到指定文件:

    1  public static void main(String[] args) throws IOException
    2     {
    3         FileWriter fileWriter = new FileWriter("D:/Demo.txt");
    4         fileWriter.write("衬衫的价格是九磅十五便士");
    5         fileWriter.flush();
    6         fileWriter.close();
    7     }

    同样创建了一个写入流对象,传入的是我们指定目录下的目标文件。然后调用.write(String str),写入了指定字符串。这里有一个重载,也可以传入一个int数字(java会自动转换为char类型,如超过char范围,会在文件中出现一个?字符,表示这个识别不了)。

    与读取不同的是,这里有一句.flush()。这句话用来刷新流对象中的缓存中的数据,将数据刷新到目的地中。我们要是操作写入流就一定要去刷新。

    其实,.close在结束流之前会做一次强制刷新。所以说,我们上面那句.flush是可以不要的。

    3.2.2 文件的续写

    我们如果将上面的String修改后再执行一次,会发现文件内容是新String,说明默认的写入方式是覆盖写入。我们若想续写,需要FileWRriter的另一个构造方法:FileWriter(String filename,Boolean append)也就是在路径后加入true表示续写。

    3.3 IO异常

    所有与IO有关的操作基本上都会抛出IOException。就那上面的读写来说,如果读取一个不存在的文件,或者写入时空间已满,都会抛出IOException或者他的子类。我们对这种异常的操作如下:

     1  public static void main(String[] args) 2     {
     3         FileWriter fileWriter = null;
     4         try
     5         {
     6             fileWriter = new FileWriter("Z:/Demo.txt", true);
     7             fileWriter.write("hahahaha");
     8         } catch (IOException e)
     9         {
    10             System.out.println("文件打开失败!");
    11 
    12         } finally
    13         {
    14             try
    15             {
    16                 if (fileWriter != null)
    17                     fileWriter.close();
    18 
    19             } catch (IOException e)
    20             {
    21                 System.out.println("文件关闭失败!");
    22             }
    23         }
    24 
    25     }

    首先需要说明的是,文件流对象要定义在try-catch外面,因为代码块内算一个整体。在者,我们的.close()也会抛出异常,而.close是无论文件打开与否一定会执行的操作,所以说把这句话放在finally中并且对其捕获异常。

    下面的代码示例中,为了简洁,我统一用throws的方式处理。这只是为了演示简洁,实际中一定不可以这样。

     3.4 小应用-文本文件的复制

    思路很简单:创建两个流,一个读文件一个写文件。将数据读入到字符数组,然后写入流从数组中读取并写文件。

     1  public static void main(String[] args) throws IOException
     2     {
     3         FileReader fileReader = new FileReader("D:/Demo.java");
     4         FileWriter fileWriter = new FileWriter("D:/copy.txt");
     5         char str[] = new char[1024];
     6         int num=0;
     7         while ((num = fileReader.read(str)) != -1)
     8         {
     9             fileWriter.write(str,0,num);
    10         }
    11         fileReader.close();
    12         fileWriter.close();
    13     }

     我们通过对比两个文件的字节大小就知道这俩是一模一样的了:

                                                                                     

    3.5 缓冲区技术(Buffered)

     为了提高读取写入效率,我们使用了缓冲区技术。边读边写是很慢的,而且还伤硬盘。缓冲区是内存的一片区域,我们知道内存的读写速度很快,使用了缓冲区技术,我们的效率会成倍增加。

    使用方法也很简单:

     1 public static void main(String[] args) throws IOException
     2 {
     3     FileReader fileReader = new FileReader("D:/Demo.java");
     4     BufferedReader bufferedReader = new BufferedReader(fileReader);
     5     String str=null;
     6     while ((str = bufferedReader.readLine()) != null)
     7     {
     8         System.out.println(str);
     9     }
    10     fileReader.close();
    11 }

    BufferedReader接受一个字符输入流,然后将其存入缓冲区。buffered.readline()是一个对字符输入流装饰后的新方法,可以做到一次读取一行,但是这一行并不带有任何行终止符,也就是最后的回车换行是读不到的。


    装饰设计模式:可以在原有的功能基础上新增技能,降级继承带来的耦合性。

    具体实现:将想增强的类作为参数传入,然后加入新内容。


     我们在使用缓冲之后,就不用去关闭流了,直接关缓冲区就行了,因为这俩已经相关联了。

    3.6 小应用-使用缓冲区复制文件

     1 public static void main(String[] args) throws IOException
     2 {
     3     long start=System.currentTimeMillis();
     4     BufferedReader bufferedReader = new BufferedReader(new FileReader("D:/Demo.java"));
     5     BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:/copy.txt"));
     6     String str=null;
     7     while ((str = bufferedReader.readLine()) != null)
     8     {
     9         bufferedWriter.write(str);
    10         bufferedWriter.newLine();
    11         bufferedWriter.flush();
    12     }
    13     bufferedReader.close();
    14     bufferedWriter.close();
    15     long end = System.currentTimeMillis();
    16     System.out.println(end-start);
    17 }

    因为bufferedWriter.write(str);不获取行终止符,我们就只能手动去加一行,也就是用bufferedWriter.newLine();因为数据并不是一次性读取完的,所以每次读取都要bufferedWriter.flush();刷新下。

    3.7 为输出标行号

     1  public static void main(String[] args) throws IOException
     2  {
     3      FileReader fileReader = new FileReader("D:/Demo.java");
     4      LineNumberReader lineNumberReader = new LineNumberReader(fileReader);
     5      String str=null;
     6      while ((str = lineNumberReader.readLine()) != null)
     7      {
     8          System.out.println(lineNumberReader.getLineNumber()+" "+str);
     9      }
    10      fileReader.close();
    11  }

    LineNumberReader是一个专门跟踪行号的类,他需要接收一个字符流。然后我们调用它的.getLineNumber()就可以接收到行号了。默认编号从0开始,如想自定义,可以使用setLineNumber(int num)来手动改变。

    4.非纯文本(字节流)的读写(FileInputStream,FileOutputStream)

    4.1 简单的读取

    如果你已经掌握了上述内容,那么字节流对你来说就很简单了。

    前面说过,我们可以对任何格式的文件使用字节流操作。所以现在仍使用文本文件进行演示。

     1 public static void main(String[] args) throws IOException
     2 {
     3     FileInputStream fileInputStream = new FileInputStream("D:/Demo.java");
     4     byte b[] = new byte[fileInputStream.available()];
     5     int num=0;
     6     while ((num=fileInputStream.read(b))!=-1)
     7     {
     8         System.out.println(new String(b,0,num));
     9     }
    10     fileInputStream.close();
    11 }

    代码与前面的基本没有差异,说明两点不同:

    1.因为是字节流,所以字符数组变成了字节数组;

    2.fileInputStream.available()可以返回文件的(估计)大小,这样我们就可以定义一个正好大小的数组不会浪费。如若文件比较大,这种方式显然不妥。

    4.2 复制一张图片

     1 public static void main(String[] args) throws IOException
     2 {
     3     FileInputStream fileInputStream = new FileInputStream("D:/1.jpg");
     4     FileOutputStream fileOutputStream = new FileOutputStream("D:/2.jpg");
     5     byte b[] = new byte[1024];
     6     int num=0;
     7     while ((num = fileInputStream.read(b)) != -1)//返回了读取的字节数
     8     {
     9         fileOutputStream.write(b,0,num);
    10     }
    11     fileInputStream.close();
    12     fileOutputStream.close();
    13 }

    代码与字符流的基本一样。在第九行我们还是使用开始与结束参数来保证写入的都是有效数据。

    4.3 字节流的缓冲区

     1 public static void main(String[] args) throws IOException
     2 {
     3     BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("D:/1.jpg"));
     4     BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("D:/2.jpg"));
     5     int b=0;
     6     while ((b = bufferedInputStream.read()) != -1)
     7     {
     8         bufferedOutputStream.write(b);
     9     }
    10     bufferedInputStream.close();
    11     bufferedOutputStream.close();
    12 }

    这里特意使用了一个字节一个字节读取的方式来掩饰,是为了说明这种方式也是可以的。

    需要强调的一点:类名。前面的是功能增强(Buffered),后缀名是操作对象(InputStream)。可千万别整混了!

    4.4 键盘的读取、输出与流的重定向

    4.4.1 从键盘输入

    既然涉及到数据,就免不了键盘的输入。还是用一个小栗子来演示下:

     1 public static void main(String[] args) throws IOException
     2 {
     3    InputStream inputStream=System.in;
     4     StringBuilder stringBuilder=new StringBuilder();
     5     while (true)
     6     {
     7         int m = inputStream.read();
     8         if (m == '
    ')
     9         {
    10             continue;
    11         }
    12         if (m == '
    ')
    13         {
    14             String str=stringBuilder.toString();
    15             stringBuilder.delete(0, stringBuilder.length());
    16             System.out.println(str);
    17         }
    18         else
    19         stringBuilder.append((char)m);
    20     }
    21 }

    换行问题:

    在windows当中,换行是用两个字符来表示的: ,而在linux中,是用 来表示的。而我们的回车键敲下去表示的是 ,所以上面的例子才会在 那里继续循环。


    从键盘输入数据我们使用的是System.in。这是一个标准字节输入流,返回的是输入流(InputStream)。所以我们需要一个输入流对象来接收。然后我们使用了一个StringBuilder来不断的向字符串后面添加我们从键盘获取到的字符,等我们按下回车时,清空数据(delete)。很好理解,不再多说。

    4.4.2 流的转换

    上面的例子是输入一行输出一行,说白了就是读一行打印一行。自然而然我们就可以想到readLine()方法,但是这个是缓冲字符流的方法,我们现在是字节流,那有没有办法转换一下呢?

    我们可以使用InputStreamReader来将字节流转换为字符流。

     1 public static void main(String[] args) throws IOException
     2 {
     3     InputStream inputStream=System.in;
     4     InputStreamReader inputStreamReader= new InputStreamReader(inputStream);
     5     BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
     6     String str=null;
     7     while ((str = bufferedReader.readLine()) != null)
     8     {
     9         System.out.println(str);
    10     }
    11 }

    InputStreamReader接收一个字节流对象,并将其转换为字符流。InputStreamReader是字节流到字符流的桥梁。

    上面这么写太罗嗦,我们一般是这么写:

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

    数据从硬盘(或者其他设备,本例就是键盘)中读入内存时,顺序是,文件流->转换流->缓冲流;(字节->字符)
    所以读入的时候,转换流是字节流到字符流的桥梁;

    我们看上面那句话的顺序也就应该是--------->

    与之相对的,我们也有OutputStreamWriter。

     1 public static void main(String[] args) throws IOException
     2 {
     3     BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
     4     BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
     5     String str=null;
     6     while ((str = bufferedReader.readLine()) != null)
     7     {
     8         bufferedWriter.write(str);
     9         bufferedWriter.newLine();
    10         bufferedWriter.flush();
    11     }
    12 }

    OutputStreamWriter接受一个输出字节流对象,也就是System.out。与上面相反,OutputStreamWriter是字符流转换为字节流的桥梁。

    数据从内存中写入硬盘(或者屏幕)时,顺序是,缓冲流->转化流->文件流;(字符->字节)
    而缓冲流是字符流,本例中的文件流out是字节流,所以是字符流到字节流的桥梁;

    顺序应该是

    new BufferedWriter(new OutputStreamWriter(System.out));

                          <------------------

    4.4.3 流的重定向

     上面的System.in与System.out是从键盘读入,屏幕输出其实都是默认的,我们可以进行重定向,让它从文件读入或者写入到文件。

    输出定向到文件:

    1 System.setOut(new PrintStream("D:/text.txt"));

    在你的代码前面加上一句这个就可以了。.setOut接收一个PrintStream对象,我们直接new一个就行。

    输入重定向到文件:

    1 System.setIn(new FileInputStream("D:/123.txt"));

    .setIn接受一个输入流,我们传入的是InputStream的已实现子类FileInputStream。

    这样我们的输入源就变成了文件,也就是从文件中读取。但其实这种方法的使用场景并不多。

    5.文件

    5.1 文件的概述

    上面介绍完了各种流,这些流或多或少都跟文件相关。在Java中,万物基于对象,文件也不例外。当然就有一个File类将文件or文件夹封装为了对象。FIle类与上面的流向配合食用,效果更佳哦。

    5.2 文件(夹)对象的定义

    1 File file = new File("D:/Demo.txt");

    一目了然,最简单的方式就是在File的构造函数中传入文件路径,这个文件可以是存在的,也可以是未出现的。

    同理,这个参数也可以是一个文件夹:

    1 File file = new File("D:/abc");

    还有一种方式,就是将路径的文件名与其父路径分开,作为两个参数:

    1 File file = new File("D:/","Demo.txt");//第一个参数是父路径,是字符串

    或者,将封装了父绝对路径的File对象传入第一个参数中:

    1 File file1 = new File("D:/");
    2 File file2 = new File(file1, "Demo.txt");//与上面那个有点区别,这个的第一个参数是File对象

    提示:上面说过,Windows与Linux的文件分隔符是不一样的,所以我们在打印路径的时候就要区别对待。使用File.separator可以输出与系统有关的分隔符。静态成员,无需对象。

    5.3 文件的创建、删除、判断与获取文件信息

    5.3.1创建

    1  public static void main(String[] args) throws IOException
    2  {
    3      File file = new File("D:/Demo.txt");
    4      Boolean isCreate = file.createNewFile();
    5      System.out.println("文件创建:" + isCreate);
    6  }

    将我们想操作的文件封装为对象之后,我们就可以对其进行操作了。.createNewFile()可以创建新文件,并返回是否创建成功。如果文件在创建前已经存在,则会创建失败。他是不会覆盖的。

    将程序运行两次:

                

    创建目录也差不多:

    1  public static void main(String[] args) throws IOException
    2  {
    3      File file = new File("D:/123");
    4      Boolean isCreate = file.mkdir();
    5      System.out.println("目录创建:"+isCreate);
    6  }

    使用mkdir()创建一级目录。也就是说,我们不可以创建嵌套目录,这个方法创建的文件路径深度只能为1级。

    要想创建多层目录,只需使用mkdirs()就可以了。

    5.3.2 删除

    删除主要有两个方法:delete(),deleteOnExit()。

    1 public static void main(String[] args) throws IOException
    2 {
    3     File file = new File("D:/Demo.txt");
    4     System.out.println(file.delete());
    5 }

    这个不需要多解释,返回值为是否删除成功。删除成功的前提是这个文件存在。

    1 public static void main(String[] args) throws IOException
    2 {
    3     File file = new File("D:/Demo.txt");
    4     file.deleteOnExit();
    5     System.out.println(file.exists());
    6     
    7 }

    file.deleteOnExit()是在虚拟机退出时删除文件。可以看到,我在最后一句加上了file.exists(),这句话返回文件是否存在。打印结果为true,说明当时文件并没有被删除。

    5.3.3 判断

    file.exists():判断文件(路径)是否存在。

    isFile():判断一个File对象封装的是否为文件

    isDirectory():判断一个File对象封装的是否为文件路径

    但是要注意,若文件(路径)不存在时,isFile(),isDirectory()也会返回false。所以在判断类型前要判断下是否存在。

    isAbsolute():判断路径是否为绝对路径。

    isHidden():判断文件是否为隐藏文件。

    canExecute():判断文件是否可执行。

    canRead():判断文件是否可读。

    canWrite():判断文件是否可写。

     一个小例子演示下几个方法。

     1 public static void main(String[] args) throws IOException
     2 {
     3     File file = new File("D:/123.txt");
     4     System.out.println("fileExist:" + file.exists());
     5     System.out.println("isFile:" + file.isFile());
     6     System.out.println("isDirectory:" + file.isDirectory());
     7     System.out.println("isHidden:" + file.isHidden());
     8     System.out.println("canExecute:" + file.canExecute());
     9     System.out.println("canRead:" + file.canRead());
    10     System.out.println("canWrite:" + file.canWrite());
    11 }

    输出和文件属性:

     

    5.3.4 获取信息

     getName():获取文件(路径)名,返回值String

     getParent():获取文件(路径)的父路径,返回值String。若为相对路径,返回null

     getPath():获取文件(路径)的路径(定义的时候为抽象则返回抽象,绝对则为绝对),返回值String

     getAbsolutePath():获取文件(路径)的绝对路径,返回值String

     getAbsoluteFile():获取文件(路径)的绝对路径,返回值File对象。也就是说,这个方法将返回来的路径封装为了对象。

     length():获取文件的大小。若为路径,则返回值不确定。返回值long。

     lastModified():获取文件最后一次被修改的时间,返回值long。

     1 public static void main(String[] args) throws IOException
     2 {
     3     File file = new File("D:/123.txt");
     4     System.out.println("getName:" + file.getName());
     5     System.out.println("getParent:" + file.getParent());
     6     System.out.println("getPath:" + file.getPath());
     7     System.out.println("getAbsolutePath" + file.getAbsolutePath());
     8 
     9     File file1 = file.getAbsoluteFile();
    10     System.out.println("file1IsExists:" + file1.exists());
    11 
    12     System.out.println("length:" + file.length());
    13     System.out.println("lastModified:" + new Date(file.lastModified()));
    14 }

     list():获取指定路径的文件列表,返回值String[]。该方法必须对路径进行操作。

     listRoots():获取可用的文件系统根(翻译为人话就是C盘,D盘等等),返回值为File[]。

     1 public static void main(String[] args) throws IOException
     2 {
     3     File file=new File("D:/");
     4     String str[]=file.list();
     5     for (String s : str)
     6     {
     7         System.out.println(s);
     8     }
     9     File file1[] = file.listRoots();
    10 
    11     for (File f : file1)
    12     {
    13         System.out.println(f);
    14     }
    15 }

     list(FileNameFilter filter):有过滤器的list(),返回值String[]。

    这个方法加一点说明,首先先上代码:

     1 public static void main(String[] args) throws IOException
     2 {
     3     File file = new File("D:/");
     4     String str[] = file.list(new FilenameFilter()
     5     {
     6         @Override
     7         public boolean accept(File dir, String name)
     8         {
     9             return name.contains("txt");
    10         }
    11     });
    12     for (String s : str)
    13     {
    14         System.out.println(s);
    15     }
    16 }

    这个方法里面有一个过滤器FileNameFilter,他是一个接口,所以我们使用匿名内部类来实现他,并重写其accept(File dir,String name)方法。其中,dir是文件的目录,name是文件的名称。

    换句话说,这个过滤器会检查每个文件(夹),并将其路径和文件名传入dir和name中。在方法体内你可以加入自己的判断。返回值为boolean,true则进入String数组,false则不。我这里保留的是文件名包含"txt"的文件(夹)。

    listFiles():获取路径下的所有文件。返回值File[]。若不是路径的对线,则返回null。

    listFiles(FileNameFilter filter):有过滤器的listFiles(),返回值File[]。

    这两个的使用与上面两个无二,不同的就是一个返回String,一个返回File[]。

    5.4 递归获取、删除文件

    先贴代码:

     1 public static void main(String[] args) throws IOException
     2 {
     3     File file = new File("C:/");
     4     ShowList(file);
     5 }
     6 
     7 public static void ShowList(File file)
     8 {
     9     File file1[] = file.listFiles();
    10     if(file1==null)
    11         return;
    12     //这两句很重要
    13     for(int i=0;i<file1.length;i++)
    14     {
    15         if (file1[i].isFile())
    16         {
    17             System.out.println(file1[i]);
    18         }else
    19             ShowList(file1[i]);
    20     }
    21 }

    首先获取文件路径下的文件列表并将其存在数组中,然后遍历每个数组元素,若为文件,则打印路径,否则继续递归获取路径下的文件列表。这个递归比较好理解,不再赘述,重点说下注释的两句:

    系统中的一些文件是不让Java访问的,如果访问了就会返回null,程序就会抛出空指针异常(NullPointerException)。所以我们要避开这些目录或者文件。

    和这个很相似,我们可以去删除一个路径下的指定规则的文件。

     1 public static void main(String[] args) throws IOException
     2 {
     3     File file = new File("C:/");
     4     RemoveList(file);
     5 }
     6 
     7 public static void RemoveList(File file)
     8 {
     9     File file1[] = file.listFiles();
    10     /*if(file1==null)
    11         return;*/
    12     //这两句很重要
    13     for(int i=0;i<file1.length;i++)
    14     {
    15         if (file1[i].isFile())
    16         {
    17 
    18             System.out.println(file1[i].toString()+file1[i].delete());
    19         }else
    20             RemoveList(file1[i]);
    21     }
    22 }

    这个就不执行了,因为Java删除文件是不进入回收站的,直接删。。

    6 PrintWriter

    PrintWriter是一个很神奇很强大的类,这个类可以将各种数据类型都原样打印,而不是以字节的形式表现的。这个类不会抛出IO异常。

    这个方法的构造函数可以接收四种参数:

    1.file对象

    2.字符串路径。(String)

    3.字节输出流。(OutputStream)

    4.字符输出流。(Writer)

    既然是一个打印流,那么我们就简单的输入一点数据,然后输出在控制台上。

     1 public static void main(String[] args)throws IOException
     2 {
     3     BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
     4
    1 PrintWriter printWriter = new PrintWriter(System.out,true);
     5     String string=null;
     6     while ((string = bufferedReader.readLine()) != null)
     7     {
     8         printWriter.write(string);
     9         printWriter.flush();
    10     }
    11     printWriter.close();
    12     bufferedReader.close();
    13 }

    几点说明:

    1.第三行是我们前面说过的转换流,标准格式。转换完之后就是字符流了。

    2.第四行传入的是标准输出流,输出到屏幕。

    3.既然有缓冲字符流了,那就用readLine()最方便。因为每输入一次谁会在缓冲区中,所以要刷新一次。

    我们也可以自己手动指定自动刷新的参数,方法就是在构造函数中加上true:

    1 PrintWriter printWriter = new PrintWriter(System.out,true);

    但是注意,要是自动刷新的话前一个参数必须是OutputStream及其子类(FileOutputStream,FileWriter),而且写入方法只能是print,println,format

    我们也可以将输入的数据输出到文件而不是屏幕:

     1  public static void main(String[] args)throws IOException
     2  {
     3      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
     4      PrintWriter printWriter = new PrintWriter(new FileOutputStream("D:/123.txt"),true);
     5                                          //这里写FileWriter也可以。
     6      String string=null;
     7      while ((string = bufferedReader.readLine()) != null)
     8      {
     9          if (string.equals("exit"))
    10          {
    11              break;
    12          }
    13          printWriter.println(string);
    14      }
    15      printWriter.close();
    16      bufferedReader.close();
    17  }

    7.对象的持久化存储

      我们每运行一个程序,就会有一些对象创建并存放于堆内存中,随着程序的结束,这些对象也被从内存中清空。通过流的形式,我们可以将对象存放在文件中。

     对象的持久化存储也叫做对象的序列化。

     下面还是用代码说明下。

     1 package Practice;
     2 
     3 import java.io.*;
     4 
     5 public class IODemo
     6 {
     7     public static void main(String[] args)throws IOException
     8     {
     9         ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/123.obj"));
    10         Student student=new Student(12,"KangYH");
    11         objectOutputStream.writeObject(student);
    12         objectOutputStream.close();
    13     }
    14 }
    15 
    16 class Student implements Serializable
    17 {
    18     private int age;
    19     private String name;
    20 
    21     public Student(int age, String name)
    22     {
    23         this.age = age;
    24         this.name = name;
    25     }
    26 }

    ObjectOutputStream的构造函数接收一个OutputStream,因为Object不是纯文本文件,所以这里用的是FileOutputStream。然后我们简单的创建了一个对象,并使用.writeObject(Object obj)来创建对象文件。

    需要注意的是,并不是随便一个对象都可以被持久化,这个对象所属类必须实现Serializable接口(这个接口没有任何方法,只是起一个标记的作用)才行,否则抛出NotSerializableException异常。

    与之对应的,ObjectInputStream就是读取对象的方法了。此时抛出的异常不再是IO异常而是ClassNotFoundException,因为文件中可能根本没有对象的信息。

     public static void main(String[] args)throws Exception
    2 {
    3     ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:/123.obj"));
    4     Student student = (Student) objectInputStream.readObject();
    5     System.out.println(student);
    6     objectInputStream.close();
    7 }

    为了保持类与对象的唯一关联,在创建文件时会有一个serialVersionUID 的东西加进去,在读取对象文件时若当前类计算出的UID与文件的UID不一致,则会抛出异常。

    比如,我把上面的类中的一个成员的权限修饰符进行改动,那么我就读不出来对象信息了,因为类被改动了,UID变了。

    注意,静态是不能被序列化的。若想不序列化某一个非序列化成员,只需加一个关键字transident。

    1 transient private int age;

    8.管道流

    以前我们操作读写流的时候并不能将读写直接进行对接,而是需要一个第三方文件来中转一下。为了能让读写直接对接,我们可以使用管道流。

    管道流与多线程配合使用。

     1 class Read implements Runnable
     2 {
     3     private PipedInputStream pipedInputStream;
     4 
     5     public Read(PipedInputStream pipedInputStream)
     6     {
     7         this.pipedInputStream = pipedInputStream;
     8     }
     9 
    10     @Override
    11     public void run()
    12     {
    13         try
    14         {
    15             System.out.println("开始读取");
    16             byte b[] = new byte[1024];
    17             int len = pipedInputStream.read(b);
    18             String str = new String(b, 0, len);
    19             System.out.println(str);
    20         } catch (IOException e)
    21         {
    22         }
    23     }
    24 }
    25 
    26 class Write implements Runnable
    27 {
    28     public Write(PipedOutputStream pipedOutputStream)
    29     {
    30         this.pipedOutputStream = pipedOutputStream;
    31     }
    32 
    33     private PipedOutputStream pipedOutputStream;
    34 
    35     @Override
    36     public void run()
    37     {
    38         try
    39         {
    40             System.out.println("开始写入");
    41             pipedOutputStream.write("1234".getBytes());
    42             pipedOutputStream.close();
    43         } catch (IOException e)
    44         {
    45         }
    46     }
    47 }
    48 
    49 public class PipedStreamDemo
    50 {
    51     public static void main(String[] args) throws IOException
    52     {
    53         PipedOutputStream pipedOutputStream = new PipedOutputStream();
    54         PipedInputStream pipedInputStream = new PipedInputStream();
    55         pipedInputStream.connect(pipedOutputStream);
    56         new Thread(new Read(pipedInputStream)).start();
    57         new Thread(new Write(pipedOutputStream)).start();
    58     }
    59 
    60 }
    管道流

    既然是将读写进行联通,就要将输入管道流与输出管道流进行连接。使用pipedInputStream.connect(pipedOutputStream);当然,反过来也一样。

    两个线程谁先执行并不重要,因为read()是阻塞式方法,没有数据时他会一直等待,知道有数据为止。

    管道流并不复杂,不再赘述。

    9.RandomAccessFile

    这个类中封装了byte数组与文件指针,我们可以通过指针来对文件进行读写。

    1 public static void main(String[] args)throws IOException
    2 {
    3     RandomAccessFile randomAccessFile = new RandomAccessFile("D:/123.txt", "rw");
    4     randomAccessFile.write("hahaha哈哈哈哈".getBytes());
    5     randomAccessFile.write(67);
    6     randomAccessFile.close();
    7 }

    先看构造函数:第一个是文件,可以是File对象也可以是字符串路径。第二个是模式,rw表示为有读写权限,r表示为只读。若想对文件进行修改就一定注意权限要是rw。

    传int型数据时,数据只向最低8位存放,其余的三个字节是空的,打印时数据可能在最低八位溢出(超出了byte的有效数据域),导致错误的结果。所以如果要存入特定类型的数据,我们要使用特定的方法:

    writeInt();writeChar();writeBoolean()等等。

    其次是写入write(),因为是以byte数组的形式存放的,所以要往里面传byte数组。在传入汉字的时候,可能会出现乱码的情况,因为一个汉字是占两个字节,读取的时候如果读了一半就会出现错误的输出。

    为了避免这种情况,我们可以去手动调整指针,让存入的数据大小作为一个周期,换句话说就是每个数据占固定的字节数:

    1 randomAccessFile.seek(8 * 2);

    参数就是从文件开始向后偏移的字节数。我们通过这个操作,不仅可以向后添加数据,还可以更改现有的数据(每次都会从文件开头计算偏移量)。

    我们也可以跳过指定字节数:

    1 randomAccessFile.skipBytes(8 * 2);

    每执行一次,指针就会向后偏移指定字节数。

    10.DataStream

    DataStream是专门用来读写特定数据的流对象。

     1 public static void main(String[] args) throws IOException
     2 {
     3     DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("D:/123.txt"));
     4     DataInputStream dataInputStream = new DataInputStream(new FileInputStream("D:/123.txt"));
     5     dataOutputStream.writeInt(789);
     6     dataOutputStream.writeBoolean(true);
     7     dataOutputStream.writeDouble(3.14159);
     8 
     9     System.out.println(dataInputStream.readInt());
    10     System.out.println(dataInputStream.readBoolean());
    11     System.out.println(dataInputStream.readDouble());
    12 }

    要注意输出的顺序问题。如果将上面的输出调换顺序,会读出错误的结果。这也很好理解,数据是按照一定的字节数存入的,读取是当然也会按照规则读取。


     对于IO流的总结应该是非常全面了,后续如果有其他内容再补充。

  • 相关阅读:
    “王者对战”之 MySQL 8 vs PostgreSQL 10
    PostgreSQL 进程结构
    Linux core dump 诊断进程奔溃退出
    linux下core dump--转载
    2.4 等比数列
    2.3 等差数列的前n项和
    2.2 等差数列
    1.1.1 三角形正弦定理
    调整颜色
    去括号法则
  • 原文地址:https://www.cnblogs.com/KangYh/p/9984664.html
Copyright © 2011-2022 走看看