Java 输入输出流学习总结


前言:为什么要有输入输出流

为什么 Java 要有输入输出流?我们先来看看用现有手段写一个简单的输入输出程序:

package big;

import java.util.Scanner;

public class Simlpe_input_and_output {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根

		Scanner read = new Scanner (System.in);
		System.out.println("请输入你想输入的内容:");
		String a = read.next();
		System.out.println("接下来打印这句话:");
		System.out.println(a);
	}

} 

你看看这多麻烦,要想从键盘上输入还得先导入 util 包里的 Scanner 类,在一开始学 Java 的时候差点因为这个导致厌学(C语言直接 scanfprintf 不好吗?为啥 Java还得三行代码?) 。
接下来还是看看课本上的介绍吧:Java 语言的输入输出功能是十分强大而灵活的(说实话,我现在都不信🥱🥱🥱),对于数据的输入和输出操作以 “ 流 ”(stream)的方式进行。JDK 提供了各种各样的 “ 流 ” 类,用以获取不同种类的数据,它们都定义在 java.io 包中。程序中通过标准的方法输入或输出数据。

嗯~~那下面我们就来看看这 Java 输入输出流到底咋回事吧,这里主要还是对课本中的示例代码进行复现来学习的,所以课本看不懂的小伙伴可以试试我的这篇学习笔记。


一、Java 的标准输入输出

标准输入输出功能是通过 Java 的 System 系统类来实现的。System 类在 java.lang 包中,是一个最终类,里面的大多数方法都是静态方法,所以在程序中可以直接调用它们(包括标准输入输出)。

什么?不知道 java.lang 是啥?老师上课讲过的,language 嘛,语言的缩写。java.lang包是java语言的核心,它提供了java中的基础类。包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类。也就是说,一个 Java 程序必备 java.lang 包(默认就存在的吧,我也没见课本示例程序里写啊,是吧?)

标准输入 System.in

System.in 作为 InputStream 类的对象实现标准输入,可以调用它的 read 方法(有以下三种格式)来读取键盘数据:

  1. public abstract int read()
  2. public int read (byte[] b)
  3. public int read (byte[] b, int off, int len)

输入流结束,返回 -1。

标准输出 System.out

System.out 作为 PrintStream 打印流类的对象实现标准输出,可以调用它的 ptintprintlnwrite 方法来输出各种类型的数据。

printprintln 都不用介绍了,目前用的最多的代码就是写这个的。还是说一下 wtite 方法吧,它用来输出字节数组,注意一点:write 方法在输出单个字节时并不能立即显示出来,还需调用 flushclose 方法来强制回调(??不懂)。

程序 exp8_3:简单的输入输出

package big;

import java.io.IOException;

public class exp8_3_simple_inputoutput {

	public static void main(String[] args) throws java.io.IOException {  
		//这里必须继承 IOException 异常,因为输入时可能会发生 I/O 错误

		byte buffer[] = new byte[40];
		System.out.println("从键盘上输入不超过40个字符,按回车键结束输入:");
		int count = System.in.read(buffer);  //读取标准输入流
		System.out.println("保存在缓冲区的元素个数为" + count);
		System.out.println("输出 buffer 元素值:");
		for (int i = 0; i < count; i ++) {
			System.out.print(" " + buffer[i]);
		}
		System.out.println();
		System.out.println("输出 buffer 字符元素:");
		System.out.write(buffer, 0, buffer.length);
	}

}

输出如下:
在这里插入图片描述
下面做个小实验,对该程序做几点改变,看看有什么变化:

  1. 最后一行 write 方法中最后一个参数不应该是 buffer.length-1 吗?结果没有任何改变,看来没什么好说的了,我认为 Java 不严谨,支持我吗,同志们?
  2. 我不想在输出 buffer 元素值时看到的是 ASCII 码,我要看我输入的字母。简单,安排!这只是在 for 循环里面的输入加上一个强制转换符就行了System.out.print(" " + (char)buffer[i]);。问题是,多了两行空格,这怎么解释?🤔🤔🤔🤨🤨🤨
    在这里插入图片描述

如此简单一个程序,我以为搞懂了,可是回看一下课本上的说明,还是有问题滴:

  1. 我们在键盘上输入7个字符,可是控制台却显示保存在缓冲区的元素个数为 9,这是因为还有回车符 “ \r ” 占用了两个元素,且元素值为 ASCII 码值。(会不会那两行空格就是因为这个原因?)
  2. 对程序中的第一条注释,原来课本里给出了说明:本例用到了 read(byte[] b) 方法,因其会产生输入异常,可以放在 try···catch 块中执行,或令 main 方法将异常上交(即在声明语句中加入 throws java.io.IOException ),这样才能通过编译。
  3. 课本上说:本例使用了 write 方法直接输出了字节数组的内容,如果使用 println 方法可将字节数组的内容转换为字符串,否则不能正常显示。于是把System.out.write(buffer, 0, buffer.length) 改成了下面这样,但不行耶,老师怎么把字节数组里的内容转换成字符串啊?(我做了什么?最后为什么会有好多空格和 null 这几个字符输出?) :
String str = null;  //如果不赋初始值 null 的话,将会报错,原因是局部变量 str 尚未初始化
for (int i = 0; i < count; i ++) {
	str += (char)buffer[i];
}
System.out.println(str);

在这里插入图片描述
PS:这个问题已得到解决,后面的程序(第三小节输入输出流类的应用里最后一个实验)中会把字节数组转换成字符串,这里写好了就不动了吧,主要是懒,哈哈~~

二、输入输出流框架

课本上哇哇哇说了一大堆,实在没什么可讲的,就直接放图吧:

数据流以输入流的形式被程序获取,再以输出流的形式将数据输出到其它设备。图 1 为输入流模式,图 2 为输出流模式:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输入输出流总框架图:
在这里插入图片描述
最后提一下关于输入输出流的分类,了解即可:

数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。

  1. 按照流的方向主要分为输入流和输出流两大类;
  2. 数据流按照数据单位的不同分为字节流和字符流;
  3. 按照功能可以划分为节点流和处理流;
  4. 依据类的作用,可以分为文件流、缓冲流、数据流、管道流、转换流、打印流、对象流等。

三、输入输出流类的应用

课本程序 exp8.4

例 8.4:创建两个 File 类的对象,分别判断两个文件是否存在;如果不存在,则新建。然后从键盘输入字符数据存入数组 b 里,通过文件输出流,把数组里的字符写入到 hello1.txt 文件,再从 hello1.txt 中读取数据,写到文件 hello2.txt 中。以下是源代码。

package big;

import java.io.*;

public class stream_exp8_4 {

	public static void main(String[] args) {
		
		int len;byte b[] = new byte[20];  //定义一个长度为20的字节型数组
		
		//创建文件对象(这里只是在程序中实例出对象,其实并不存在)
		File file1 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello1.txt");
		File file2 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello2.txt");
		FileOutputStream fos = null;  //先定义,后面会在下面字节流应用中再实例化对象(new)的
		
		try {  //如果文件不存在,就创建对象(经过这里的操作才是真的在 java 文件夹里创建了文件)
			if (!file1.exists())
				file1.createNewFile();
			if (!file2.exists());
				file2.createNewFile();
				
			//字节流应用
			System.out.println("请输入你想存入file1文件的内容:");
			len = System.in.read(b);  //从键盘输入的字符存入内存的 b 数组里的个数(一边实现了键盘输入,一遍统计了字符个数)
			fos = new FileOutputStream(file1, true);  //创建文件输出流到到 file1
			fos.write(b, 0, len);  //通过文件输出流 fos 把 b 数组中数据写入到 file1 文件尾部
			
			//字符流与缓冲流应用
			FileReader in = new FileReader(file1);  //打开字符文件输入流
			BufferedReader bin = new BufferedReader(in);  //传送数据到缓冲区(这一句代码就是装饰流,作用是加速读的速度)
			FileWriter out = new FileWriter(file2, true);  //打开字符文件输出流
			String str;
			System.out.println("完美复制到file2文件中的内容:");
			while ((str = bin.readLine()) != null) {  //将缓冲区中数据一行一行地送到字符串变量字符串变量 str 中
				System.out.println(str);  //输出 str 中数据到控制台
				out.write(str+"\n");  //将 str 中数据写入文件
			}
				out.close();in.close();fos.close();  //关闭流资源
		}
			
			catch(IOException e) {e.printStackTrace();}
	}

}

运行结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
看着没问题是吧,再试试重复运行输入一遍。由此我们会发现打印在控制台中的有两个 “ 相信中国!” ,打开文件后发现 hello1.txt 里有两个 “ 相信中国!” ,文件 hello2.txt 里有三个 “ 相信中国!” 。仔细想一想,也就该是这样哈!
在这里插入图片描述
在这里插入图片描述

改写 exp8.4(1)

OK,接下来,我们再来做些实验,首先有一个问题是装饰类只是加快了读的速度,其实没它照样可以完成任务,我们这样来改写程序:

FileReader in = new FileReader(file1);
//BufferedReader bin = new BufferedReader(in);  //删掉装饰流
FileWriter out = new FileWriter(file2, true);
for (int i = 0; i < len; i ++) {
	System.out.print((char)b[i]);
	out.write(b[i]);
}

这样一来,运行结果会变成怎样呢?结果是识别不了汉字了(英文倒是可以),另外就是不管运行几遍,控制台打印的都只会是本次运行时键盘输入的内容,文本文件中也只会增加本次输入的内容,大家可以对比着来看看:
在这里插入图片描述
不明白,为啥啊???

改写 exp_8.4(2)

再来一个实验:在示例中,我们输出流用的是字节流,输出流用的是字符流,统一一下不好吗?我先来试试统一字节流输入输出好了。
终于,我成功了,就像写高考数学题,写的时候真难,写完了发现好简单😂😂😂

package big;

import java.io.*;

public class exp8_4_zijieliushurushuchu {
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		int len;byte b[] = new byte[20];
		File file1 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello1.txt");
		File file2 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello2.txt");
		FileOutputStream fos = null;
		
		try {
			if (!file1.exists())
				file1.createNewFile();
			if (!file2.exists());
				file2.createNewFile();
				
			System.out.println("请输入你想存入file1文件的内容:");
			len = System.in.read(b);
			fos = new FileOutputStream(file1, true);
			fos.write(b, 0, len);
			
			FileInputStream in = new FileInputStream(file1);
			BufferedInputStream bin = new BufferedInputStream(in);
			FileOutputStream out = new FileOutputStream(file2, true);
//			for (int i = 0; i < len; i ++) {
//				out.write(b[i]);
//			}
			out.write(b);  //上面三行 for 循环其实就是这一句话的事情,算是了解一下 write 方法吧
			String str = new String(b);
			System.out.println(str);
			out.close();in.close();fos.close();
		}
			
			catch(IOException e) {
				e.printStackTrace();
		}		
	}

}

就在这个程序里,我把字节数组转换成了字符串,顺利地在控制台(屏幕)上打印出来了(这也是为什么课上有同学问 “ 为什么可以用字节数组输出汉字 ” 问题的答案了,都转换成了字符串,那为什么还不能输出汉语呢,是吧?)。大家看那输入输出流框架图都是一愣一愣的,不过呢,这里有篇文章很好的总结了字节输入输出流的框架和方法,我就是照着这篇文章完成这个实验的,分享一下。

???还有统一字符输入输出流???再说吧,累了。

四、RandomAccessFile 类

课上老师讲:RandomAccessFile 类是一个随机存取文件类。虽然不明白这句话什么意思,但下一句确实醍醐灌顶:这个类可以实现 Java 输入输出流的任意操作。我了个乖乖,相比较框架图里那么多类,这样子只用一个类就方便多了呀!

例 8.5 :通过 RandomAccessFile 类从文件的不同位置读写不同长度的数据,讲字符串数据添加在文件尾部。

package big;

import java.io.*;

public class exp8_5 {

	public static void main(String[] args) {

		String str[] = {"First lint\n", "second line\n", "Last line\n"};
		
		try {
			//用 RandomAccessFile 类实例化出 rf 对象,参数表示可以对文件 demo.txt 进行读写操作
			RandomAccessFile rf = new RandomAccessFile("demo.txt", "rw"); 
			System.out.println("\n光标的位置为:" + rf.getFilePointer());  //获取文件指针位置方法
			System.out.println("文件的长度为:" + rf.length());
			rf.seek(rf.length());  //把光标移到文件末尾(用的是 seek 方法)
			System.out.println("光标现在的位置为:" + rf.getFilePointer());
			
			for (int i = 0; i < 3; i ++)
				rf.writeBytes(str[i]);  //将字符串数组 str 里的三个字符串依次写入 rf 对象中
			rf.seek(0);  //把光标移到文件起始位置
			System.out.println("\n文件现在的内容:");
			String s;
			while ((s = rf.readLine()) != null)  //分行读取
				System.out.println(s);  //分行打印
			rf.close();  //关闭流资源
		}
		
	catch (FileNotFoundException fnoe) {}
	catch (IOException ioe) {}
	}	
	
}

运行结果和课本里的一样:在这里插入图片描述

以下摘自课本中的说明:

  1. 从本例看,随机存取文件类的的使用方法,也是先建立文件流通道,打开文件,然后进行读写操作,最后关闭文件通道。只是不用分输入流和输出流。
  2. 可以用于多线程下载或多个线程同时写数据到文件。

怎么说?其实没什么,我打一遍代码试着运行也是为了看看到底有多方便,也确实比原始输入输出流分开写简单许多。

其实还有一个小节讲的是 “ 对象序列化与对象流类 ” ,课上老师好像也是一笔带过(是不是啊?我没有认真听这点内容,感觉还没讲就过去了,连示例程序都没啥印象)


输入输出流总结

我看课本上的小结讲的是使用输入输出流的参考步骤和流程,但我就 get 不到,没有感觉。看来还是对 Java 不熟悉,对输入输出流不熟悉( c++ 里不也是输入输出流 cin、cout 吗?感觉就像一个在天上,一个在地下。。。)

在最后我还是说一下输入输出和读写的关系吧,我上机的时候就把输入输出流的意思给搞反了。记住一点:输入输出都是以程序为参照物的

  • 输入流:用于从键盘或文件(数据源)获得数据传递给程序,只能读不能写;
  • 输出流:用于将数据从程序传递给数据接收者(宿点),如内存、显示器屏幕、打印机或文件,只能写不能读。

Java 中一切名词皆为类,一切类都要实例化对象,一切对象都有属性和方法,Java 就是靠着对象的属性和方法来完成编程解决问题的,这就是我学习 Java 以来的深刻体会,这就是面向对象编程!!!

  • 39
    点赞
  • 244
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值