Java基础--IO

java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。

java.io包中最重要的就是5个类和一个接口。5个类分别是File,OutputStream,InputStream,Writer,Reader,一个接口指的是Serializable。

Java的IO主要包括三个部分:

  1. 流式部分--IO的主体部分;
  2. 非流式部分--主要包含一些辅助流式的类,如:File类、RandomAccessFile类和FileDescriptor等类;
  3. 其他类--文件读取部分的与安全相关的类,如SerializablePermission类,以及与本地操作系统相关的文件系统的类,如FileSystem类和Win32FileSystem类等。

Java的IO大概可以分为以下几类:

  • 磁盘操作:File
  • 字节操作:InputStream和OutputStream
  • 字符操作:Reader和Writer
  • 对象操作:Serializable
  • 网络操作:Socket
  • 新的输入输出:NIO

接着我们来看看Java中IO操作的过程:

  1. 使用File类打开一个文件
  2. 通过字节流和字符流的子类,指定输出位置
  3. 进行读写操作
  4. 关闭输入/输出

File

File类通过抽象的方式代表文件名和目录路径名,这个类主要用于获取文件和目录的属性,文件和目录的创建、查找、删除、重命名等,但不能进行文件的读写操作。File对象代表磁盘中实际存在的文件和目录。

创建一个File对象的方法如下:(摘自Java 8 API

File(File parent, String child)

从父抽象路径名和子路径名字符串创建新的 File实例。

File(String pathname)

通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

File(String parent, String child)

从父路径名字符串和子路径名字符串创建新的 File实例。

File(URI uri)

通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。

注意:
1.在各个操作系统中,路径的分隔符是不一样的,例如:Windows中使用反斜杠:"\",Linux|Unix中使用正斜杠:"/"。在使用反斜杠时要写成"\\"的形式,因为反斜杠要进行转义。如果要让Java保持可移植性,应该使用File类的静态常量File.pathSeparator。
2.构建一个File实例并不会在机器上创建一个文件。不管文件是否存在,都可以创建任意文件名的File实例。可以调用File实例上的exists()方法来判断这个文件是否存在。通过后续的学习我们会知道,当把一个输出流绑定到一个不存在的File实例上时,会自动在机器上创建该文件,如果文件已经存在,把输出流绑定到该文件上则会覆盖该文件,但这些都不是在创建File实例时进行的。

创建File对象成功后,我们可以通过Java 8 API中的方法使用这个对象。下面我举几个比较常用的方法:

booleandelete()

删除由此抽象路径名表示的文件或目录。

voiddeleteOnExit()

请求在虚拟机终止时删除由此抽象路径名表示的文件或目录。

booleanequals(Object obj)

测试此抽象路径名与给定对象的相等性。

booleanexists()

测试此抽象路径名表示的文件或目录是否存在。

StringgetName()

返回由此抽象路径名表示的文件或目录的名称。

StringgetParent()

返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。

FilegetParentFile()

返回此抽象路径名的父,或抽象路径名 null如果此路径名没有指定父目录。

StringgetPath()

将此抽象路径名转换为路径名字符串。

booleanisAbsolute()

测试这个抽象路径名是否是绝对的。

booleanisDirectory()

测试此抽象路径名表示的文件是否为目录。

booleanisFile()

测试此抽象路径名表示的文件是否为普通文件。

booleanmkdir()

创建由此抽象路径名命名的目录。

booleanmkdirs()

创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。

从上边的方法我们也可以早知道File类中有两个方法来创建目录:

  • mkdir():该方法创建一个文件夹,返回一个boolean类型。若返回false则表明该对象指定的路径已存在(即目录已存在),或者路径中有某个父文件夹不存在,该文件夹无法被创建。
  • mkdirs():该方法创建一个文件夹和它的所有父文件夹。

删除文件和目录我们则可以通过delete()方法来实现,调用delete()方法时,即使目录不为空,也会执行删除操作。如果目录下存在文件,则需要先删除文件再重新删除目录。

RandomAccessFile

RandomAccessFile不同于File,他是一个独立的类,直接继承自Object类,它提供了对文件内容的访问,可以读写文件且支持随机访问文件的任意位置。RandomAccessFile读写使用到文件指针,它的初始位置为0,可以使用getFilePointer()方法获取文件指针的位置。详情请查找Java 8 API

摘自https://www.jianshu.com/p/751bd3cbe5a7

流是Java IO一个很重要的概念,在Java程序中所有数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来时,就要使用输出流完成。流中保存的实际上都是字节文件,流设计的领域很广:标准输入输出,文件的操作,网络上的数据流,字符串流等等。

流具有方向性,输入流还是输出流则是一个相对的概念,一般以程序为参考,如果数据的流向是程序到设备,则是输出流,又设备到程序则是输入流。

我们来补充一个Java IO流的类层次图:

 

(摘自https://www.jianshu.com/p/751bd3cbe5a7

首先要理解编码和解码的概念:

  • 编码:编码就是把字符转换为字节
  • 解码:解码就是将字节重新组合成字符

在编码和解码的过程中,一定要使用相同的编码方式,一旦使用了不同的编码方式,就会出现乱码。编码方式主要有以下三种

  1. GBK编码中,中文字符占2个字节,英文字符占1个字节
  2. UTF-8编码中,中文字符占3个字节,英文字符占1个字节
  3. UTF-16be编码中,中文字符和英文字符各占2个字节。

UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。

了解了编码和解码的概念,我们就可以开始来了解字符流和字节流。

字符流构建在字节流的基础上,字符流专门用于读取文本文件,字节流则适用于所有文件,但在文本文件上方便性不及字符流。

字符流

Java中的字符流处理的最基本的单元是2字节的Unicode码元(char),它通常用来处理文本数据,如字符、字符数组或者字符串等。所谓Unicode码元,也就是一个Unicode代码单元,范围时0x0000~0xFFFF。在以上范围内的每个数字都与一个字符相对应。Java中的String类型默认就把字符以Unicode规则编码而后存储在内存中。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式,使用不同的编码方式,相同的字符会有不同的二进制表示。因此字符流实际上是这样工作的:

  • 输出字符流(Writer):把写入的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列,然后再写入到文件。
  • 输入字符流(Reader):把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列)从而可以存在内存中。

也就是说,所有文件再硬盘火灾传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成。

字符输入流Reader

从上边的类层次图我们可以了解到:

  1. Reader是所有的输入字符流的父类,它是一个抽象类。
  2. CharArrayReader、StringArrayReader是两种基本的介质流,它们分别从Char数组、String对象中读取数据。Pipe的Reader是从与其它线程公用的管道中读取数据。
  3. BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。
  4. FilterReader是所有自定义具体装饰流的父类,其子类PushbackReader对Reader对象进行装饰,会增加一个行号。
  5. InputStreamReader是一个连接字符流和字节流的桥梁,它将字节流转变为字符流。FileReader可以说是第一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。

Reader有三个主要的读取方法:

  1. public int read() throws IOException; 读取一个字符,返回值为读取的字符 
  2.  public int read(char cbuf[]) throws IOException; 读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量
  3. public abstract int read(char cbuf[],int off,int len) throws IOException; 读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现

字符输出流Writer

从上边的类层次图我们可以了解到:

  1. Writer是所有的输出字符流的父类,它是一个抽象类。
  2. CharArrayWriter、StringWriter是两种基本的介质流,他们分别向Char数组、String对象中写入数据。
  3. BufferedWriter是一个装饰器为Writer提供缓冲功能。
  4. PrintWriter和PritStream极其类似,功能与使用也非常相似。
  5. OutputStreamWriter是OutputStream到Writer转换的桥梁,它的子类FileWriter其实就是一个实现此功能的具体类。

Writer主要的写入方法有以下五种:

  1. public void write(int c) throws IOException; 将整型值c的低16位写入输出流 
  2.  public void write(char cbuf[]) throws IOException; 将字符数组cbuf[]写入输出流 
  3.  public abstract void write(char cbuf[],int off,int len) throws IOException; 将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流 
  4.  public void write(String str) throws IOException; 将字符串str中的字符写入输出流 
  5. public void write(String str,int off,int len) throws IOException; 将字符串str 中从索引off开始处的len个字符写入输出流 

字节流

Java中的字节流处理的最基本单位是单个字节(byte),它通常用来处理二进制数据。如果要得到字节对应的字符需要强制类型转换。

输入字节流InputStream

从上边的类层次图我们可以了解到:

  1. InputStream是所有输入字节流的父类,它是一个抽象类。
  2. ByteArrayInputStream、StringBufferInputStream(上图的StreamBufferInputStream)、FileInputStream是三种基本的介质流,它们分别从Byte数组、StringBuffer和本地文件中读取数据。
  3. PipedInputStream是从与其它线程公用的管道中读取数据。
  4. ObjectInputStream和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。

InputStream有三个基本的读取方法

abstract int read():读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。

int read(byte[] b):将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,则表示读到了输入流的末尾。

int read(byte[] b,int off,int len):将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。off指定在数组b中存放数据的其实偏移位置,len指定读取的最大字节数。

通过这三个方法我们也可以知道判断流结束的标志就是read的返回值为-1或者readLine的返回值为null。

还有一些常用的方法:

      long skip(long?n):在输入流中跳过n个字节,并返回实际跳过的字节数。
      int available() :返回在不发生阻塞的情况下,可读取的字节数。
      void close() :关闭输入流,释放和这个流相关的系统资源。
      voidmark(int?readlimit) :在输入流的当前位置放置一个标记,如果读取的字节数多于readlimit设置的值,则流忽略这个标记。
      void reset() :返回到上一个标记。
      booleanmarkSupported() :测试当前流是否支持mark和reset方法。如果支持,返回true,否则返回false

输出字节流OutputStream

从上边的类层次图我们可以了解到:

  1. OutputStream是所有输出字节流的父类,它是一个抽象类。
  2. ByteArrayOutputStream、FileOutputStream是两种基本的介质流,他们分别向Byte数组和本地文件写入数据。PipedOutputStream是向与其它线程共用的管道中写入数据。
  3. ObjectOutputStream和所有FilterOutputStream的子类都是装饰流。

OutputStream有三个基本的写入方法:

     abstract void write(int?b):往输出流中写入一个字节。
     void write(byte[]?b) :往输出流中写入数组b中的所有字节。
     void write(byte[] b, int off, int len) :往输出流中写入数组b中从偏移量off开始的len个字节的数据。

OutputStream有两个很重要的方法:

     void flush() :刷新输出流,强制缓冲区中的输出字节被写出。
     void close() :关闭输出流,释放和这个流相关的系统资源。

要特别注意,flush()方法用来刷新缓冲区的,刷新后可以再次写出(字节缓冲流内置缓冲区,如果没有读取出来,可以使用flush()刷新出来)。而close()方法用来关闭流释放资源的,如果是带缓冲区的流对象的close()方法,会在关闭流之前刷新缓冲区,关闭流之后不能再次写出。

字节流和字符流的区别

字节流与字符流的使用非常相似,两者除了操作代码上的不同之处外是否有其他的不同?
实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件。

什么是缓冲区呢?我们可以把缓冲区理解为一段特殊的内存,某些情况下,如果一个程序频繁地操作一个资源(如文件或者数据库,则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。

在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。上文也讲述过,如果想在不关闭时将字符流的内容全部输出,可以使用Writer类中的flush()方法来完成。

因此字节流与字符流除了读写对象不同之外(字节流为byte对象,字符流为char对象),最大的区别就在于字节流没有缓冲区而字符流有缓冲区。

 

本文主要讲了File、InputStream、OutputStream、Writer和Reader这五个IO中最重要的类,其派生类有兴趣的可以查看JavaAPI进行了解。由于目前对NIO的相关知识没有足够的了解,后期完善知识点后会补充NIO的相关内容。

参考:

  1. https://www.jianshu.com/p/751bd3cbe5a7
  2. https://www.cnblogs.com/ylspace/p/8128112.html
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值