什么是 Java 输入输出流?流的用法大全

4 篇文章 2 订阅

博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主威威喵  |  博客主页https://blog.csdn.net/smile_running

  •   I/O 流

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

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

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

(1)流的家族

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputSreamOutputSreamReaderWriter
访问文件FileInputSreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputSreamByteArrayOutputSreamCharArrayReaderCharArrayWriter
访问管道PipedInputSreamPipedOutputSreamPipedReaderPipedWriter
缓冲流BufferedInputSreamBufferedOutputSreamBufferedReaderBufferedWriter
访问字符串  StringReaderStringWriter
转换流  InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputSream  
特殊流DataInputStreamDataOutputStream  
打印流 PrintStream PrintWriter
推回输入流PushbackInputStream PushbackReader 
过滤流FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
随机存储文件的流(支持读写)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();
			}
		}
	}

 IO流的大致使用都过了一遍,这些仅仅是的基本用法。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值