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流的总结应该是非常全面了,后续如果有其他内容再补充。

原文地址:https://www.cnblogs.com/KangYh/p/9984664.html