JavaSE 输入流详解

本文详细解析了文件操作中的字符与字节转换原理,通过实例展示了如何利用Scanner类简化文件内容读取过程,强调了理解底层原理的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在复习JSP(Java server page  用过Java语言实现动态网站的技术),复习到通过前端页面输入文件的标题和内容,点击提交后将文件保存在本地的小实例。发现IT行业的技术都是一环扣一环的。如果能够扎实的掌握了基本功和内部原理,那么后面的学习会更加的轻松,自己也会有更高的成就感。相反,如果前面的基础知识掌握的不够牢固,后面学习更多的技术,只会给自己带来更大的打击。


blob.png


后端业务逻辑代码:

 

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<%@ page import="java.io.*"%>

<%

         String path = request.getContextPath();

         String basePath = request.getScheme() + "://"

                            + request.getServerName() + ":" + request.getServerPort()

                            + path + "/";

%>

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<base href="<%=basePath%>">

 

<title>My JSP 'saveFileSuccessful.jsp' starting page</title>

 

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="expires" content="0">

<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">

<meta http-equiv="description" content="This is my page">

<!--

         <link rel="stylesheet" type="text/css" href="styles.css">

         -->

<style type="text/css">

.contentArea {

         width: 50%;

         margin: 20px auto;

         text-align: left;

         border: 1px solid red;

         padding: 10px;

}

</style>

</head>

 

<body>

         <%

                   request.setCharacterEncoding("utf-8");

                   String fileName = request.getParameter("fileName");

                   String fileType = request.getParameter("fileType");

                   String content = request.getParameter("content");

                   String myPath = this.getServletContext().getRealPath("/") + "\\"

                                     + fileName + "." + fileType;

                   File file = new File(myPath);

                   OutputStream output = new FileOutputStream(file);

                   output.write(content.getBytes());

                   output.close();

         %>

         <center>

                   <h4>写入成功</h4>

                   <hr>

                   <h4><%=myPath%></h4>

                   <%

                            Scanner sc = new Scanner(file);

                            sc.useDelimiter("\n");

                            StringBuffer buf = new StringBuffer();

                   %>

                   <%

                            while (sc.hasNext()) {

                                     buf.append(sc.next()).append("<br>");

                            }

                            sc.close();

                   %>

                   <div>

                            <%=buf%>

                   </div>

                   <a href="ServletContext_inputFileInfoAndSave.jsp" target="_self">返回</a>

         </center>

 

</body>

</html>


在后端业务逻辑代码中发现了之前复习JavaSE部分时,关于如何通过输入输出流向文件中写入数据和从文件中读取数据的知识在动态网站的开发过程中也会完整的用到。

而且学习一门技能不应该浅尝辄止的学了皮毛,就好像学武术,不能只学习那一招一式,还应该理解这些招式在什么情况下应用。只有懂得其中的原理道理,才会在日后信手拈来,游刃有余。

那么我就给大家详细的解释其中的原理!李老师讲堂开讲啦。。。。。。。

 

首先回到JavaSE部分,给大家一个简单的实例:


整数与字符之间的转换

public class IntCharTransform {

         public static void main(String[] args) {

                   char c = 'a';

                   int ascii;

                   System.out.println("直接输出字符:" + c);

                   ascii = (int) c;

                   System.out.println("得到aASCII编码:" + ascii);

System.out.println("通过ASCII码的强制类型转换,直接输出字符:"+(char) 97);

         }

}

运行结果:

直接输出字符:a

得到aASCII编码:97

通过ASCII码的强制类型转换,直接输出字符:a

 

通过这个小例子可以发现,字符类型其实和整数类型之间有着一一对应的关系,那么连接这个关系的东西就是字符的ASCII编码。计算机原理中用7位二进制数来表示键盘上每个字符的ASCII编码,而一个字节是8位二进制数。所以在计算机中,用一个字节就可以表示英语世界的所有字符。而中文则比较复杂,本身中文的文字就比较多,所以一个字节就不够用,那么伟大的中华民族就用2个字节来表示我们祖先创造的伟大的汉字。

那么问题就出现了,在代码中,如果程序是按照一个字节一个字节的读取文件中的数据,那么不做任何的加工,读取出来的内容就会是乱码。请看下面的例子:

 

我在本地事先保存了一个文本文件,内容如下:

blob.png


第一行是中文,第二行是短横线字符,其他是中文字符。

 

那么我用之前微信里发布的程序来读取中文,就会有问题,

 

package wechat;

 

import java.io.File;

import java.io.FileInputStream;

import java.io.InputStream;

 

public class ReadContentFormFile {

         public static void main(String[] args) throws Exception {

                   // File.separator会自动识别程序所运行的操作系统中的文件路径分隔符。因为windowsLinux的文件分割符不同,为了使得Java程序能够在不修改的情况下跨平台运行,就要调用File类中的separator属性

                   File file = new File("d:" + File.separator + "info.txt");

                   // 实例化一个字节输入流FileInputStream,用于从文件中读取内容

                   InputStream in = new FileInputStream(file);

                   // 定义一个整形变量,用于接收读取出来的字符的ASCII

                   int temp;

                   while ((temp = in.read()) != -1) {

                            System.out.print((char) temp);

                   }

                   //记得关闭输入流,节省系统资源(CPU、内存)

                   in.close();

         }

}

 

 

运行结果:

D:\info.txt

-----------------------------------------------------------

???????ò?±?????????á?????????°

?°???°????????______?????????????ü???????¤×÷?±

?????????ò?±????×??????????????????????÷?????ù??????????????·?·??????????????á×?×???????????????×÷?????í???????????????????????????á?????????????°???????????????????????ò?????????????????????????????????????????????????ù??????????????

?°______??????×÷?????????????????????????±

???ù?????????????????????ò??????????????????????????°ü?¨?????ó?à·?????????±??????????????§

 

 

从结果中我们发现,只有字符和英文可以正常读取出来,但是中文就变成了乱码。这是什么原因呢?

 

问题就在输入流的read()方法。

 

这是关于InputStream字节输入流的read()方法的介绍

Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned. This method blocks until input data is available, the end of the stream is detected, or an exception is thrown.


有图有真相!!!(为了看的清楚,我认为的给每个字节的ASCII码中间加入了空格)

绘图1.jpg


Read()方法读取的文件中的每一个字节,那么等这个方法第一次运行时,指针会指向第一个字节,读取出这个字节的ASCII码,读取出来这个ASCII码后这个指针就会自动的移动到后一个字节上去,以此类推。那么对于英文,正好每次读取一个英文字母的ASCII码,对于字符也是如此,对于中文,每次其实读取的是半个中文字符。所以当读取完全部的ASCII码后,如果不做任何处理,则系统也不知道是拼接哪两个字节的ASCII码,所以就会产生乱码。当指针读取完最后一个字符后,在运行read()方法,指针就会指向一个空的位置上,读不到任何数据,则返回-1,表示指针已经移动到了尾部,数据全部读取完毕。

 

所以在程序中的while循环的判断条件中,以read()方法的返回值不等于-1为继续循环的条件。

这种按照字节读取信息的方式处理字节很合适,InputStream和它的子类FileInputStream本身就是字节输入流,他们的对象的read()方法就是按照字节进行读取的。但是处理大量中文则显得太麻烦,Java还提供了一个字符流的类


Reader和它的子类FileReader

package wechat;

 

import java.io.File;

import java.io.FileReader;

import java.io.Reader;

 

public class FileReaderDemo {

         public static void main(String[] args) throws Exception {

                   // File.separator会自动识别程序所运行的操作系统中的文件路径分隔符。因为windowsLinux的文件分割符不同,为了使得Java程序能够在不修改的情况下跨平台运行,就要调用File类中的separator属性

                   File file = new File("d:" + File.separator + "info.txt");

                   Reader reader = new FileReader(file);

                   int temp;

                   while ((temp = reader.read()) != -1) {

                            System.out.print((char) temp);

                   }

//记得关闭输入流,节省系统资源(CPU、内存)

                   reader.close();

         }

}

 

 

程序运行结果:

D:\info.txt

-----------------------------------------------------------

杰出程序员永远都不会说的几句话

我习惯于使用______,但我不知道它是如何工作

杰出的程序员源于自己的好奇心和不断的探索,这样才掌握了一项项非凡的技能。他们会仔仔细细研究事物运作的原理,即使这些信息可能永远也不会派上用场。目前我们还不知道这种深入挖掘是一种选择意识还是强迫行为,但是这似乎已经成为了所谓人才的特质。

“______可以运作,但是我不知道该如何解释

之所以形容他们杰出不但是因为他们知道如何解决问题,也包括他们大多非常乐意给别人讲解如何起效的原因和过程。有时候,即使旁人对此毫无兴趣,他们依然会如此喋喋不休。甚至于越是才华出众能力卓绝,就越是时间讲得久。

 

这下大家满意了吧!

 

下面是字符流Reader中read()方法的简介:

Reads a single character. This method will block until a character is available, an I/O error occurs, or the end of the stream is reached. Subclasses that intend to support efficient single-character input should override this method.

Returns: The character read, as an integer in the range 0 to 65535 (0x00-0xffff), or -1 if the end of the stream has been reached

 

从介绍里我们能够看到,这个read()方法的指针不再指向一个个的字节了,而是一个个的字符,无论是英文还是中文字符。而且返回int整形值,但是返回的范围是0~65535,65535正好是216-1的值,注意,16次方就是表示16位二进制数,也就是2个8位,也就是2字节,正好可以满足英文和中文的ASCII码。

 

那么还有没有什么其他的方式,能更加简单快捷的把文件的内容全部读取出来,并输出呢?有,程序的世界很丰富,你的需求早已有人想到并帮你解决了,只要我们会利用即可。

 

用Scanner类进行读取文件内容


package wechat;

import java.io.File;

import java.util.Scanner;

 

public class ScannerReadFileContent {

         public static void main(String[] args) throws Exception {

                   File file = new File("d:" + File.separator + "info.txt");

                   Scanner scan = new Scanner(file);

                   /*

                    * System.out.println(scan.next()); System.out.println(scan.nextLine());

                    * 这两种方式不行。因为Scanner对象在读取内容时的默认结束符为回车(换行)。而且next()方法在读取的时候碰到空格和回车都会中断读取;

                    * nextLine()虽然读取一整行内容,但是当一行结束时就会碰到回车换行,也会中断读取。所以如果不加判断直接输出,顶多读取一行的内容

                    */

                   while (scan.hasNext()) {

                            System.out.println(scan.nextLine());

                   }

 

         }

}

 

程序运行结果:

D:\info.txt

-----------------------------------------------------------

杰出程序员永远都不会说的几句话

我习惯于使用______,但我不知道它是如何工作

杰出的程序员源于自己的好奇心和不断的探索,这样才掌握了一项项非凡的技能。他们会仔仔细细研究事物运作的原理,即使这些信息可能永远也不会派上用场。目前我们还不知道这种深入挖掘是一种选择意识还是强迫行为,但是这似乎已经成为了所谓人才的特质。

“______可以运作,但是我不知道该如何解释

之所以形容他们杰出不但是因为他们知道如何解决问题,也包括他们大多非常乐意给别人讲解如何起效的原因和过程。有时候,即使旁人对此毫无兴趣,他们依然会如此喋喋不休。甚至于越是才华出众能力卓绝,就越是时间讲得久。


按照下面这个图来解释一下Scanner类的hasNext()方法和nextLine()方法

图中的\n是我自己加上去的,表示回车换行。在文件中不会显示,但是在文件的编码里面是存在的,这个换行也有字符编码。


绘图2.jpg


当while循环第一次循环开始的时候,scan对象的指针其实指向的是这个文件内容的开头的前面,在判断条件中有scan.hasNext(),顾名思义,这个指针的下一个位置有内容,返回true,循环条件满足,指针马上指向了文件内容的第一行。进入循环体后执行scan.nextLine(),读取一整行的内容,遇到回车换行结束读取。

第一次循环结束后,进行第二次循环,这时指针仍然指向第一行,进行了hasNext()判断后,发现第二行是有内容的,就是这个\n,然后指针来到第二行,进入循环体后nextLine()方法读取第二行的内容。

第三次循环类似。循环完后指针停留在第三行。

当第四次循环开始时,hasNext()发现指针的下一行没有内容,则返回false,循环停止,退出循环。


以上就是Scanner类的操作实例。利用Scanner类进行内容的读取,让我们避开了字节、字符的烦扰,直接根据文章内容的排版结构,按照行的形式读取,更快捷,更好理解。

 

通过自己的思考和总结,也确实对字节,字符,段落,字节输入流,字符输入流有了更深的理解。文章没有白看,学习一门技术,不光要掌握表面的东西,不仅仅会用,会耍那几个把式。更要理解其中的道理和缘由,只有这样才能融会贯通,领悟其中的精髓,成为这个技能领域的行家里手!

 

谨以此篇总结,与大家共勉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值