首页 > 技术文章 > 【Java】File类与 I/O 流的基本用法

xww0826 2019-01-31 14:09 原文

目    录(本篇字数:3346)

I/O 流

(1)流的家族

(2)流的分类

File 类

I/O流的用法

FileInputStream/FileOutputStream

BufferedInputStream/BufferedOutputStream

FileReader/FileWriter

BufferedReader/BufferedWriter

InputStreamReader/InputStreamWriter

PrintStream/PrintWriter

DataInputStream/DataOutputStream

ObjectInputStream/ObjectOutputStream

RandomAccessFile


 

  关于数据的存储,分为内部存储和外部存储。内部存储就像内存一样,是一种临时性的对数据进行储存的方式,这种方式存储的数据量不大,但是获取数据块。另一种就是持久性的存储,以文件的方式来保存数据,可以是数据库、文本文件、图片文件、视频文件等等,存储的数据量非常大。

  • I/O 流

    Java提供了一种对文件进行操作的API,就是I/O流。I/O流是Java中的一个非常庞大的家族,同时这个家族也非常强大。关于流的概念,我们可以这样的理解。水流,流的是水;电流,流的是电;I/O流,流的就是与计算机相关的二进制字节码、字符码。

    I/O(Input / Output)就是标准的输入和输出,加上流的话。那么就是InputStream / OutputStream。流这个家族成员有很多,下面我们来通过一个表格来看看常用的流。

(1)流的家族

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputSream OutputSream Reader Writer
访问文件 FileInputSream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputSream ByteArrayOutputSream CharArrayReader CharArrayWriter
访问管道 PipedInputSream PipedOutputSream PipedReader PipedWriter
缓冲流 BufferedInputSream BufferedOutputSream BufferedReader BufferedWriter
访问字符串     StringReader StringWriter
转换流     InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputSream    
特殊流 DataInputStream DataOutputStream    
打印流   PrintStream   PrintWriter
推回输入流 PushbackInputStream   PushbackReader  
过滤流 FilterInputStream FilterOutputStream FilterReader FilterWriter
随机存储文件的流(支持读写) RandomAccessFile

    整个流的家族差不多也就这些了。通过以上的表格,我们大致了解了流的家族成员,下面我们来看看关于流的分类。

(2)流的分类

根据流的流向不同 输入流 输出流
根据流动单位不同 字节流 字符流
根据流的功能不同 节点流 处理流
  •  输入流  、输出流的作用

        输入流,用于从 .txt文件中读取数据;输出流,向文件中写入数据

  •   字节流  、字符流的区别

        字节流,byte来接收数据,作用于任何类型的文件。

        字符流,char来接收数据。只能作用于纯文本(.txt)文件。如果是纯文本文件,读取速度比字节流更快。

  •   节点流  、 处理流  

        节点流,直接作用于文件上,如:new FileInputStream(File file);

         处理流 , 作用于流上,如:new BufferedInputStream(InputStream is),作用是加速流的读取/写入速度。

  • File 类

    介绍了整个流家族,我们还缺一个与流息息相关的 File 类。顾名思义,这是对文件进行创建、删除操作的一个类。而流则就是对文件的读取/写入操作的类。对于文件的操作,我们直接看代码例子比较鲜明,这也没什么理论好说的。

	@Test
	public void file() throws IOException {
		// 绝对路径:d:/FileTest/test ,新建文件夹
		File dirFile = new File("d:/FileTest/test");
		if (!dirFile.exists()) {
			dirFile.mkdirs();
		}
		// 相对路径:C:\Users\x\eclipse-workspace\HelloJava\Test.txt ,新建文件
		File helloFile = new File("Test.txt");
		if (!helloFile.exists()) {
			helloFile.createNewFile();
		}

		// 列出 d:/FileTest/test 文件夹下的所有文件名
		File nameFile = new File("d:/FileTest/test");
		String[] fileName = nameFile.list();
		for (String name : fileName) {
			System.out.println(name);
		}

		//判断文件的存在性
		System.out.println(helloFile.exists());
		//获取绝对路径
		System.out.println(helloFile.getAbsolutePath());
		//获取父目录
		System.out.println(helloFile.getParent());
		// 获取最后修改文件的时间
		System.out.println(new Date(helloFile.lastModified()));
	}

    注意点:例如,D:/FileTest/test ,使用 mkdir 与 mkdirs 的区别 mkdir 前提是 FileTest 存在的情况下才可以创建成功 mkdirs 不需要,若 FileTest 文件夹不存在,则一并创建。

  • I/O流的用法

  • FileInputStream/FileOutputStream

    这两个是节点流,可以直接作用于文件上。例如,从文本文件中读取数据打印到控制台上:

  • FileInputStream 实现读取文件
	@Test
	public void fisTest() {
		File file = new File("Hello.txt");
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			byte[] b = new byte[10];
			int length;
			while ((length = fis.read(b)) != -1) {
				String str = new String(b, 0, length);
				System.out.print(str);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

  比较难理解的就是while循环,它表达的是从 Hello.txt 文件中读取byte[10]这样长度的字节,然后打印到控制台。若已经读到结尾,则没有数据可以读了,就会返回 -1

  • FileOutputStream 实现写入文件
	@Test
	public void fosTest() {
		// 指定文件路径,若不存在,则会自动创建
		File file = new File("Hello.txt");
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(file);
			byte[] b = new String("hello Java 2").getBytes();
			fos.write(b, 0, b.length);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • FileInputStream/FileOutputStream 实现文件拷贝
	@Test
	public void copyTest() {
		long start = System.currentTimeMillis();

		File srcFile = new File("mv.mp4");
		File destFile = new File("mv3.mp4");
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			fis = new FileInputStream(srcFile);
			fos = new FileOutputStream(destFile);
			byte[] b = new byte[4 * 1024];
			int len;
			while ((len = fis.read(b)) != -1) {
				fos.write(b, 0, len);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		long end = System.currentTimeMillis();
		System.out.println("time:" + (end - start));
	}
  • BufferedInputStream/BufferedOutputStream

    这两个是作用于字节流的处理流,它可以加速文件的读取和写入,所以我就尝试了一下。通过对比,我认为是有快一点,决定因素更大的在于byte[]数组的缓冲区大小。据说4k的缓冲区是读写大文件的最快方式。BufferedInputStream/BufferedOutputStream用法也非常简单,其实流族的代码写法都差不太多。下面来看看代码的使用:

@Test
	public void copy2Test() {
		long start = System.currentTimeMillis();
		//备注:mv.mp4  测试文件大小 500M
		File file = new File("mv.mp4");
		File file2 = new File("mv2.mp4");
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		try {
			FileInputStream fis = new FileInputStream(file);
			FileOutputStream fos = new FileOutputStream(file2);
			bis = new BufferedInputStream(fis);
			bos = new BufferedOutputStream(fos);
			byte[] b = new byte[4 * 1024];// 4k缓冲区
			int len;
			while ((len = bis.read(b)) != -1) {
				bos.write(b, 0, len);
				bos.flush();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			//把 bis 和 bos 关闭,那么 fis 和 fos 也就自动被关闭
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		long end = System.currentTimeMillis();
		System.out.println("time:" + (end - start));
	}
  • FileReader/FileWriter

    这两个也是节点流,可以直接作用于文件上。但这个只能用于读取/写入纯文本文件(.txt),比如你新建的.doc文件也纯写文字,它也不能给你读取出来。它的用法类似,只是接收的缓冲区改为 char 型,代码如下:

/**
	 * 使用 FileReader 来读取文件 注意:只能读取文本文件,如.txt
	 */
	@Test
	public void frTest() {
		FileReader fr = null;
		try {
			File file = new File("Hello.txt");
			fr = new FileReader(file);
			char[] c = new char[10];
			int len;
			while ((len = fr.read(c)) != -1) {
				String str = new String(c, 0, len);
				System.out.println(str);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fr != null) {
				try {
					fr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • FileReader/FileWriter实现纯文本文件的拷贝
@Test
	public void txtCopyTest() {
		FileReader fr = null;
		FileWriter fw = null;
		try {
			File file = new File("Hello.txt");
			File file2 = new File("Test.txt");
			fr = new FileReader(file);
			fw = new FileWriter(file2);

			char[] c = new char[10];
			int len = 0;
			while ((len = fr.read(c)) != -1) {
				fw.write(c, 0, len);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fw != null) {
				try {
					fw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (fr != null) {
				try {
					fr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • BufferedReader/BufferedWriter

    这两个是作用于字符流的处理流,目的也是加速文件的读取和写入速度。具体用法:

@Test
	public void txtCopy2Test() {
		BufferedReader br = null;
		BufferedWriter bw = null;
		try {
			File file = new File("Hello.txt");
			File file2 = new File("Test.txt");
			FileReader fr = new FileReader(file);
			FileWriter fw = new FileWriter(file2);
			br = new BufferedReader(fr);
			bw = new BufferedWriter(fw);
			String str = null;
			while ((str = br.readLine()) != null) {
				bw.write(str);
				bw.newLine();
				bw.flush();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • InputStreamReader/InputStreamWriter

    这是一个转换流,例如从键盘上输入一条字符串,本质是二进制的字符码,我们可以将它转为能看的懂的字符。代码如下:

/**
	 * 系统的输入和输出,利用InputStreamReader(转换流)来转二进制码
	 */
	@Test
	public void systemOut() {
		InputStreamReader isr = null;
		try {
			System.out.println("请输入字符串:");
			InputStream is = System.in;
			// 用到转换流
			isr = new InputStreamReader(is);
			char[] c = new char[10];
			int len;
			while ((len = isr.read(c)) != -1) {
				String str = new String(c, 0, len);
				System.out.println(str);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (isr != null) {
				try {
					isr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • PrintStream/PrintWriter

    这两个是打印流,用于打印数据。也可以直接作用文件上,例如,向文本文件中打印数据:

	/**
	 * PrintStream 和 PrintWriter 用法一致,只不过流的单位不同
	 */
	@Test
	public void printIOTest() {
		PrintStream ps = null;
		try {
			File file = new File("Hello2.txt");
			ps = new PrintStream(file);
			ps.append("Java");
			ps.append("Android");
			ps.append("Python");
			ps.append("kotlin");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} finally {
			if (ps != null) {
				ps.close();
			}
		}
	}
  • DataInputStream/DataOutputStream

    这两个流比较特殊,它只能用于读取/写入基本数据类型(byte、char、short、int、long、float、double、boolean),额外还可以写入 String (readUTF)。但是写入的基本数据类型会形成字节码,我们只能通过 DataInputStream来读取。代码例子:

/**
	 * DataOutputStream 只能用于写入基本数据类型,反之也只能用DataInputStream来读取,否则读到的就是乱码
	 */
	@Test
	public void dataIOTest() {
		DataInputStream dis = null;
		DataOutputStream dos = null;
		try {
			dis = new DataInputStream(new FileInputStream(new File("Hello2.txt")));
			dos = new DataOutputStream(new FileOutputStream(new File("Hello2.txt")));
			dos.writeUTF("Java");
			dos.writeChar('c');
			dos.writeLong(4564114564l);
			dos.writeBoolean(false);
			dos.write(22);

			System.out.println(dis.readUTF());
			System.out.println(dis.readChar());
			System.out.println(dis.readLong());
			System.out.println(dis.readBoolean());
			System.out.println(dis.read());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (dos != null) {
				try {
					dos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (dis != null) {
				try {
					dis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

    使用 FileInputStream 来读取的话,只能读到乱码文件:

    使用DataInputStream来都,则正常显示:

  • ObjectInputStream/ObjectOutputStream

    这两个是对象处理流,主要是把对象持久性的存储。比如我有一个Student类,我创建了2个Student类的对象并对它进行了赋值,然后我想存储持久性这个对象,就需要这两个处理流。

  •     序列化:允许把内存中的Java对象转换为二进制流来持久性的储存,通过网络可以传输序列化对象(二进制流)。
  •     反序列化:可以通过网络接收到的序列化对象(二进制流)来恢复原来的Java对象。

    需注意:处理流在存储对象的时候,比如一个类,这个类必须实现序列化接口(Serializable 或 Externalizable)。如果被 static 或 transient 修饰的变量,不可被序列化,接收到的就会是空值。例子如下:

  • ObjectOutputStream 序列化对象
         /**
	 * 序列号过程
	 */
	@Test
	public void objIOTest() {
		Student stu1 = new Student("001", "张三");
		Student stu2 = new Student("002", "李四");
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream(new File("student.txt")));
			oos.writeObject(stu1);
			oos.flush();
			oos.writeObject(stu2);
			oos.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • ObjectInputStream 反序列化
	/**
	 * 反序列化
	 */
	@Test
	public void objIOTest2() {
		ObjectInputStream ois = null;
		try {
			ois = new ObjectInputStream(new FileInputStream(new File("student.txt")));
			Student get_stu1 = (Student) ois.readObject();
			System.out.println(get_stu1);
			Student get_stu2 = (Student) ois.readObject();
			System.out.println(get_stu2);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • RandomAccessFile

    这个流属于比较特殊,它自身可以充当输入流,也可以充当输出流。原因是它的构造器可以传入一种 mode,这种 mode 共分为4种情况:

  • r      只读
  • rw    读和写
  • rwd  读和写,并且同步更新内容
  • rws  读和写,并且同步更新内容和元数据

    例子:通过RandomAccessFile对文本文件的插入操作,RandomAccessFile(以及所有流)在写入的时候,都会将文件中的数据覆盖。所以,通过seek();方法将光标往后移动,保存后面的字符数据后再插入,最后将字符数据加到后面即可。

	/**
	 * RandomAccessFile 实现在文本中插入数据
	 */
	@Test
	public void randomAccessTest() {
		RandomAccessFile raf_rw = null;
		try {
			raf_rw = new RandomAccessFile(new File("Hello3.txt"), "rw");
			// 光标移动到要插入的位置
			raf_rw.seek(5);
			// 保存后面的所有字符串
			StringBuffer buf = new StringBuffer();
			String str;
			while ((str = raf_rw.readLine()) != null) {
				buf.append(str + "\n");
			}
			System.out.println(buf.toString());
			raf_rw.seek(5);
			raf_rw.writeUTF("Java" + buf.toString());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				raf_rw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

©原文链接:https://blog.csdn.net/smile_Running/article/details/86713230

@作者博客:_Xu2WeI

@更多博文:查看作者的更多博文

推荐阅读