张孝祥java视频学习笔记(八)

来源:互联网 发布:中国传统价值观 知乎 编辑:程序博客网 时间:2024/05/16 06:18

第七讲 IO/输入输出(


OutputStream(二进制格式)

程序可以从中连续写入字节的对象叫Java中,用OutputStream类来描述所有输出流的抽象概念。只描述出了输出时的一些共性,

1)void  writeint b)【不支持直接写入字符串,要通过get byte()返回字节数组】最低字节写入流中,最高字节被舍弃。为什么不用byte类型,因为即使使用,它也会转换成int类型的结果,必须要强制转换成byte类型

2)Void writebyte[] b)将所用的字节全都写入到输出流中

3)Void  writebyte[] b,int off, int len)将自节数组从off开始写入到

4)Void flush()将缓冲设备中的字节彻底清空,并输出到IO设备中

5)多学两招: 

6)计算机访问外部设备,要比直接访问内存慢得多,如果我们每一次write方法的调用都直接写到外部设备(如直接写入硬盘文件),CPU就要花费更多的时间等待外部设备;如果我们开辟一个内存缓冲区,程序的每一次write方法都是写到这个内存缓冲区中,只有这个缓冲区被装满后,系统才将这个缓冲区的内容一次集中写到外部设备。使用内存缓冲区有两个方面的好处,一是有效地提高了CPU的使用率,二是write并没有马上真正写入到外设,我们还有机会回滚部分写入的数据。使用缓冲区,能提高整个计算机系统的效率,但也会降低单个程序自身的效率,由于有这么一个中间缓冲区,数据并没有马上写入到目标中去,例如在网络流中,就会造成一些滞后。对于输入流,我们也可以使用缓冲区技术。在程序与外部设备之间到底用不用缓冲区,是由编程语言本身决定的,我们通常用的C语言默认情况下就会使用缓冲区,而在Java语言中,有的类使用了缓冲区,有的类没有使用缓冲区,我们还可以在程序中使用专门的包装类来实现自己的缓冲区。 

7)flush方法就是用于即使在缓冲区没有满的情况下,也将缓冲区的内容强制写入到外设,习惯上称这个过程为刷新。可见,flush方法不是对所有的OutputStream子类都起作用的,它只对那些使用缓冲区的OutputStream子类有效。如果我们调用了close方法,系统在关闭这个流之前,也会将缓冲区的内容刷新到硬盘文件的。 

8) 作者开发过一个邮件服务器程序,需要7*24小时不间断工作,这个服务器程序要面对internet上各种可能的非法格式的数据输入和攻击,而我的程序正好又没考虑到某种非法格式的数据,一旦碰到这样的情况,程序就会崩溃。有经验的人都知道,为了找出服务器程序崩溃的原因,我们可以将程序每次接收到的数据都记录到一个文件中,当服务器程序崩溃后,我们便打开这个记录文件,查看最后记录的那条数据,这个数据就是让我的程序毙命的罪魁祸首,然后拿着这条数据一步步测试我们的程序,就很容易找出程序中的问题了。遗憾的是,我每次用最后记录的这条数据测试我的程序,程序均安然无恙。最后,我发现就是因为有缓冲区的原因,缓冲区的内容还没来得及刷新到硬盘文件,程序就崩溃了,所以,文件中并没有记录最后接收到的那些数据,我在文件中看到的最后以条记录并不是真正最后接收到的那条数据。发现了这个原因,我修改程序,在每一次调用write语句后,都立即调用flush语句,这样,我就终于找到了肇事元凶,并修复了程序的这个漏洞。

9)Void close()

FileInputStream与FileOutputStream类分别用来创建磁盘文件的输入流和输出流的对象,通过他们的构造函数来指定文件路径和文件名

创建FileInputStream实例对象时,指定文件应当是存在和可读的。创建FileOutputStream实例对象时,如果指定文件已经存在,这个文件中原来的内容将被覆盖清除。

对于同一个磁盘文件创建FileInputStream对象的两种方式:

1FileInputSteam inOne = new FileInputStream(“hello.test”);

2File f = new File(“hello.test”);

   FileInputStream inTwo = new FileInputStream(f);允许把文件在与输入流链接时,先进行文件的判断,文件是否存在,可写

创建FileOutputStream实例对象时,可以指定还不存在的文件名,不能指定一个已被其他程序打开了的文件。

思考:A文件写入到B文件中,是用输出流对象还是输入流来链接A文件并完成操作输入输出类是相对程序而言的,而不是代表文件的,所以我们应该创建一个输入类来完成对A文件的操作,创建一个输出类来完成对B文件的操作。

ReaderWriter类(文本格式)

ReaderWriter是所有字符流的抽象基类,用于简化对字符串的输入与输出编程,即用于读写文本数据

二进制文件与文本文件的区别

文件中的所有文件都是二进制文件,不是0-255之间的字符都可以对应相应的字节,如果一个文件的的每个字节或每相邻的几个字节都可以表示成字符我们就可以将文件称之为文本文件。文本文件是二进制文件的一种特例,把只用来存储字符的文件我们称之为文本文件,其他的称之为二进制文件

 

 

计算机访问外部设备,要比直接访问内存慢得多。使用内存缓冲区有两个好处,一是有效地提高了CPU的使用率,而是write并没有马上真正写到外设,我们还有机会回滚部分写入的数据。C语言默认情况下都会使用缓冲区,在Java语言中,有的类使用缓冲区,有的不使用。

FileWriter创建实例对象后,该对象使用write方法后要先再使用close方法才会真正把内容写到磁盘文件中,因为使用close()方法关闭文件前会先调用flush方法,flush方法就是用于在缓冲区还没有满的情况下,也将缓冲区的内容强制写入到外部设备包括磁盘文件中,习惯上称这个过程为刷新。FileOutputStream则不需使用close方法(),说明FileOutputStream没有使用缓冲区。Reader在读取内容的时候不知道到哪里结束,在以后使用包装类的时候需要运用到此种方法。

 

PipedInputStreamPipedOutputStream

PipeInputStreamPipeOutputStream用于应用程序中创建管道通信,主要用于线程之间的通信【PipedWritePipedRead类】

使用管道流类可以实现各个程序模块之间的松耦合通信,这样我们就可以将我们所需要的输出流和输入流拼装成我们所需要的应用程序,而不需要对某个模块内部进行修改。使用管道流进行通信的模块具有‚强内聚,弱耦合‛的特点,一个模块被替换,或被拆卸不会影响其他模块。

 ByteArrayInputStream与ByteArrayOutputStream (缓冲区就是字节缓冲数组)

提供了StringReaderStringWriter來以字符IO流的方式处理字符串,这两个方法分别对应

ByteArrayInputStream与ByteArrayOutputStream用于以IO流的方式来完成对字节数组内容的读写,来支持类似内存虚拟器文件或内存虚拟映像文件的功能

ByteArrayInputStream是输入流的一种实现,它有两个构造函数,每个构造函数都需要一个字节数组来作为数据源: 

ByteArrayInputStream(byte[] buf)  

ByteArrayInputStream(byte[] buf, int offset, int length) 第二个构造函数指定仅使用数组buf中的从offset开始的length个元素作为数据源。

 ByteArrayOutputStream两个构造函数。

ByteArrayOutputStream() 创建一个32字节的缓冲区

ByteArrayOutputStream(int) 根据参数指定的大小创建缓冲区,

缓冲区的大小在数据过多时能够自动增长,像虚拟文件一样写入内容,内容当做字节数组来返回

这两个流的作用在于,用IO流的方式来完成对字节数组内容的读写。爱思考的读者一定有过这样的疑问:对数组的读写非常简单,我们为什么不直接读写字节数组呢?我在什么情况下该使用这两个类呢? 

有的读者可能听说过内存虚拟文件或者是内存映像文件,它们是把一块内存虚拟成一个硬盘上的文件,原来该写到硬盘文件上的内容会被写到这个内存中,原来该从一个硬盘文件上读取内容可以改为从内存中直接读取。如果程序在运行过程中要产生一些临时文件,就可以用虚拟文件的方式来实现,我们不用访问硬盘,而是直接访问内存,会提高应用程序的效率。 

假设有一个别人已经写好了的压缩函数,这个函数接收两个参数,一个输入流对象,一个输出流对象,它从输入流对象中读取数据,并将压缩后的结果写入输出流对象中。我们的程序要将一台计算机的屏幕图像通过网络不断地传送到另外的计算机上,为了节省网络带宽,我们需要对一副屏幕图像的像素数据进行压缩后,再通过网络发送出去的。如果没有内存虚拟文件,我们就必须先将一副屏幕图像的像素数据写入到硬盘上的一个临时文件,再以这个文件作为输入流对象去调用那个压缩函数,接着又从压缩函数生成的压缩文件中读取压缩后的数据,再通过网络发送出去,最后删除压缩前后所生成的两个临时文件。可见这样的效率是非常低的。我们要在程序分配一个存储数据的内存块,通常都用定义一个字节数组来实现的, JDK中提供了ByteArrayInputStreamByteArrayOutputStream这两个类可实现类似内存虚拟文件的功能,我们将抓取到的计算机屏幕图像的所有像素数据保存在一个数组中,然后根据这个数组创建一个ByteArrayInputStream流对象,同时创建一个用于保存压缩结果的ByteArrayOutputStream流对象,将这两个对象作为参数传递给压缩函数,最后从ByteArrayOutputStream流对象中返回包含有压缩结果的数组。 

我们在编写相关的IO流时一定要重视起程序代码的重复使用性

System.in对应键盘,是InputStream类型的,程序使用System.in可以读取从键盘上输入的数据。System.out对应显示器,是PrintStream类型的,PrintStreamOutputStream的一个子类,程序使用System.out可以将数据输出到显示器上。不论是输出流还是输入流都有一个底层物理设备用什么方式实现输入流的结束。当我们把键盘作为输入文件处理时,在 Windows下,我们可以按下ctrl+z组合键来输入-1作为文件结束标记,在linux下,我们可以按下ctrl+d组合键来输入-1

缓冲区是堆内存中一个连续的块?

如果程序要在运行过程中产生一些临时的文件,采用程序虚拟文件,这样程序就不用访问硬盘,而是直接访问内存,程序运行的效率会极大的提高。

要在内存中产生临时文件,就需要使用字节数组来模拟输入和输出流。

内存虚拟文件或内存映像文件是把一块内存虚拟成一个硬盘上的文件,原来该写到硬盘文件上的内容会被写到这块内存中,原来该从一个硬盘文件上读取内容可以改为从内存中直接读取。

经验:要编程从键盘上连续读取一大段数据时,应尽量将读取数据的过程放在函数中完成,使用-1来作为键盘输入的结束点。在该函数中编写的程序代码不应直接使用system.in读取数据,而是用一个InputStream类型的形式参数对象来读取数据,然后将system.in作为实参传递给InputStream类型的形式参数来调用该函数。这样,我们以后要从某个文件中读取数据,来代替手工键盘输入时,我们可以直接使用这个函数,程序就不用作太多的修改了。

Windows下,按下Ctrl+z组合键可以产生键盘输入流的结束标记,在linux下,则是按下ctrl+D组合键来产生键盘输入流的结束标记。

字符编码

字符与数字相对应的编码规则通称为ASCII(美国标准数字交换码)

ASCII的最高bit位都为0范围是0-127也就是说ASCII能表示128个字符。

中国大陆将每一个中文字符都用两个字节的数字来表示,中文字符的每个字节的最高位bit都为1,中国大陆为每个中文字符指定的编码规则称为GB2312(国标码)

GB2312的基础上,对更多的中文字符(包括繁体)进行了编码,新的编码规则称为GBK

在中国大陆使用的计算机系统上,GBKGB2312就被称为该系统的本地字符集

Unicode编码

ISO(国际标准化组织)将全世界的符号进行了统一编码,称之为Unicode编码。不区分地区与国家。“中”字这个符号,在全世界的任何角落始终对应的都是一个十六进制的数字4e2d

Unicode编码的字符都占用两个字节大小,对于ASCII码所表示的字符,只是简单地在ASCII码原来占用的一个字节前面,增加一个所有bits0的字节。Unicode在全世界范围所表示的字符个数不会超过216次方(65536

Java中的字符使用的都是Unicode编码,Java在通过Unicode保证跨平台特性的前提下,也支持本地平台字符集。

UTF-8编码(使用两到三个字符来表示)的优点:不出现内容为0x00字节;便于应用程序检测数据在传输过程中是否发生了错误;直接处理使用ASCII码的英文文档。缺点:对于某些字符,需要用3个字节来表示。

通常使用EF BB BF作为文件开头

UTF-16编码(将0xD800-0xDFFF之间的数值保留起来了(要么两个字节要么四个字节)Unicode基础上进行了一些细节上的扩充,增加了对Unicode编码包括的那些字符的表示方式。UTF-16Unicode的扩充并没有影响Unicode编码所包括的那些字符。UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节 (2字节储存,但UTF-16却无法兼容于ASCII编码。如果文件以0xFE 0xFF这两个字节开头,则表明文本的其余部分是Big EndianUTF-16编码;如果文件以0xFF 0xFE这两个字节开头,则表明文本的其余部分是Little EndianUTF-16编码。

length函数:对于String类实例对象,要写成length();对于byte数组,只需写成length

write:写字节时不会刷新缓冲区,但是写字节数组时会刷新缓冲区。

0 0
原创粉丝点击