Java 读文件:FileInputStre…

来源:互联网 发布:张馨予坐台知乎 编辑:程序博客网 时间:2024/05/17 01:03
原文地址:读文件:FileInputStream">Java 读文件:FileInputStream作者:晓强

[声明]:本文为本博主(gulibia)原创。欢迎转载。联系方式: yixiaoqiang@gmail.com

 

[简介]:

无论二进制文件还是文本文件,都可以用文件输入流java.io.FileInputStream 字节的方式进行读取操作。如果想以字符的形式读取文本文件,则应该使用java.io.FileReader 。

[注:从本质上说,文件都是以字节码(即所谓的二进制码)的形式存在的。如果这些字节码表示的是某种字符集的一种编码格式,如ASCII,UTF8, GB2312 等等,则习惯上把这个文件称为文本文件。 java.io.FileReader除了提供文件读取功能外,还内置了解码功能,因此说,如果读取文本文件,java.io.FileReader 更方便些。(关于FileReader 的使用,请参见本站相关文章。)]

java.io.FileInputStream  类继承和重写了抽象类java.io.InputStream。他们两个都是从最早的JDK-1.0就已经存在的类。

 

[父类 InputStream ]:

抽象类 InputStream是所有字节输入流的基类,它共定义了一个构造方法和九个方法。构造方法什么也没做。在九个方法中,有,

  • 三个 read 方法:

public abstract int read() throws IOException,这个方法读取字节流中下一个字节。读取的字节值是作为 int 类型返回的。实际返回值的范围是 0 ~255 ,而不同于基本数据类型 byte 的范围,-128 ~+127。如果所有数据都读完了(即所谓的到达流终点),再调用这个方法,则返回-1。这是判定流中所有数据读取完毕的唯一方式。对文件输入流来说,返回-1意味着整个文件已经读取完毕了。这个方法是阻塞的,直到输入端有数据可读、到达流终端、或者有异常抛出。调用这个方法时,需要捕获和处理IOException。

这个方法是InputStream 类唯一的抽象方法,需要具体类实现。具体如何使用可参看下面示例程序。

public int read(byte[] b) throws IOException,与上面方法不同的是,这个方法一次可以读取多个字节,并放在缓存数组 b 中。需要注意的是,数组 b一定是(由用户)已经创建好的对象实例。否则,这个方法调用失败,并抛出NullPointerException。

这个方法的返回值是实际的读取字节个数或-1。-1表示到达流终点,这一点和上面的方法类似,这也是用于判定是否读完的唯一方式。不同的是在以前的每次读取中,返回值代表实际读取的长度,用户应该对这个长度进行判断或累加,来获取实际读取的总数据长度,否则可能出现把缓冲中原有的数据误认为这次读取数据的情况。一般来说,如果用长度为m 的缓存来读取长度为 n 个字节的文件,则总读取次数为n/m + 2次,最后一次返回-1,倒数第二次返回值是n%m,而倒数第三次直至正数第一次返回值是m。这个方法是阻塞的,直到输入端有数据可读、到达流终端、或者以异常抛出。InputSteam中这个方法的实现是调用下面的read方法:read(b, 0, b.length)。

public int read(byte[] b, int off, int len)throwsIOException,这个方法与上面方法类似,但用户可以指定读取的长度和缓存的起始位置。最多读取不是数组的长度了,而是len。如果缓存的起始位置off加上 读取的长度 len 超过了缓存的长度, 则调用不成功并抛出IndexOutOfBoundsException 异常。如果off 或 len 小于0, 也抛出这个异常。同样,数组 b一定是(由用户)已经创建好的对象实例。否则,这个方法调用失败,并抛出NullPointerException。

这个方法的返回值是实际的读取字节个数或-1。同上面方法一样。

InputStream 中这个方法的实现是调用int read()方法。具体子类应该重写这个方法,提供更有效率的实现。

FileInputStream 重写了这三个 read 方法。

 

下面用一个例程演示 InputStream.read() 和 InputStream.read(byte[] b)的使用:

 

// TestFileInputStream.java

 

import java.io.*;

public class TestFileInputStream
{
  public static voidtestRead()
  {
    try{
     FileInputStream fi = newFileInputStream("TestFileInputStream.java");

     int i = fi.read();
     int allRead = 0;

     while(i != -1) // 判断文件读完的条件
     {
       System.out.print((char)i); // 注意:这里简单地把读到的字节转为字符输出,不适用于所有情况。
       allRead ++;
       i = fi.read();
     }
     System.out.println();
     fi.close(); // 注意:如果用户忘记关闭打开的文件,系统可以通过 finalyze方法在垃圾回收的时候替用户关闭这个流。
                 // 虽然如此,用户应该显示的调用这个方法。
     System.out.println(allRead  + " read in all.");
   }catch(IOException e)
    {
     e.printStackTrace();
    }
  }
 
  public static void testReadBytes()
  {
    try{
     FileInputStream fi = newFileInputStream("TestFileInputStream.java");

     byte[] buffer = new byte[256]; //必须用户自己创建一个buffer。
     int read = fi.read(buffer);
     int allRead = 0;

     while(read != -1) // 判断文件读完的条件
     {
       allRead += read;
       System.out.println(read + " read ... and available is " +fi.available()); 
       read = fi.read(buffer);
     }
     fi.close();
     System.out.println(allRead  + " read inall.");
  
   }catch(IOException e)
    {
     e.printStackTrace();
    }
  }
 
  public static void main(String[]args)
  {
   testRead();
   testReadBytes();
  }
}

  • 三个与标记(mark)有关的方法:

public void mark(int readlimit) 和 publicvoid reset() throws IOException

在用户读取流数据的过程中,可以使用 mark 方法来做一个标记,标记之后的数据可以被重复读取。何时来重复读取取决于 reset方法。mark 中的参数用于限定最大重复读取的字节数。

标记使用的一般过程:如果 markSupported 返回 true,输入流将通过某种方式“记住”mark方法调用之后用户使用read方法读取的字节,并在reset 方法调用后,把“记住”的这些子节提供给用户(用户仍然继续使用read方法)。如果“记忆”的字节超过了 readlimit 则超出的部分不会被记忆。

mark 没有定义异常,即使标记不成功用户也不会知道。但reset定义了IOException异常,如果调用这个方法前没有做过标记或者标记已经无效了,则抛出这个异常。标记无效的具体情况由子类定义。

 

public boolean markSupported()

并不是所有的输入流都支持标记。用户应该首先调用这个方法来判断一下,只有支持的情况下,调用mark 或 reset方法才有意义。

InputStream 的实现中,这个方法返回false,不支持标记!

 

java.io.FileInputStream没有重写这三个方法,意味着文件输入流也不支持标记功能。因此,关于标记,这里暂不举例,可以参考本站其他相关文章。

  • 其余三个方法: 

public int available() throws IOException 

这个流中还有多少子节没有读?可以用这个方法获取。需要注意的是,针对“阻塞流”,这个方法返回的是“直到下一次阻塞,流中可以读取的字节”。

InputStream中这个方法的实现总是返回0。具体子类应该重写这个方法。java.io.FileInputStream 重写了这个方法。

上面的例子里演示了这个方法的使用。

 

public void close() throws IOException

这个方法用于关闭流。虽然,如果用户忘记调用这个方法,系统可以通过 finalyze 方法在该 FileInputStream对象垃圾回收的时候替用户关闭这个流。但用户应该养成良好的习惯显式的调用这个方法!

InputStream中这个方法的实现什么也没做。具体子类应该重写这个方法。java.io.FileInputStream 重写了这个方法。 用于真正关闭相应的文件资源。


public longskip(long n) throwsIOException

之所以称为流,是因为数据是顺序读取的。如果用户想跳过一定数量的字节,则可以通过这个方法。一般情况下,返回值应该和输入值相同,即代表实际跳过的字节数,但如果达到文件尾,实际的跳过值可能小于输入值。

InputStream通过内部使用一个数组来缓存跳过的数组来实现这个方法。具体子类应该重写这个方法,提供更高效率的实现。java.io.FileInputStream 重写了这个方法。 

 

[FileInputStream 的其他方法]: 

java.io.FileInputStream 除了重写上述 InputStream 9个方法中 6 个方法之外,还提供了额外三个方法:

 

protected void finalize() throwsIOException 。这个方法其实是重写了 Object 的这个方法(Object是所有Java 类(包括数组类型)的“根级”父类)。这个方法是被 Java 虚拟机中的垃圾回收器调用的。用于 Java对象已经不存在了但它占有的资源没有释放的情况,即最后的释放资源的机会。FileInputStream 实现中,这个方法主要是调用了close() 方法。上面例子中,如果用户没有调用 close()方法,系统就通过这个方法在垃圾回收的时候关闭文件。但写程序时应该养成显式关闭文件的习惯。

 

public final FileDescriptor getFD() throws IOException。 这个方法返回 FileDescriptor 对象,这个对象表示这个文件流对应的文件系统中的实际文件。

关于 FD 的使用,参见下面示例程序。

 

public FileChannel getChannel() 这是在 JDK1.4 中引入的方法。用于支持New IO的特性。这个方法返回 FileChannel 对象,这个对象表示这个文件对应的文件系统中的通道。关于Channel 和 NewIO,请参阅本站其它文章。

 

[关于 FileInputStream 的构造]:

FileInputStream 提供了三个构造方法:

FileInputStream(File file) throws FileNotFoundException FileInputStream(String name) throwsFileNotFoundException 都是通过实际文件路径(或其标识的File对象)来创建文件流。需要注意的是,这两个构造方法中有打开文件的操作,因此,如果文件不存在,则抛出FileNotFoundException 异常。如果文件由于安全保护而不允许读取,则抛出SecurityException 异常。

 

publicFileInputStream(FileDescriptor fdObj)这是用已经打开的文件来创建一个新的文件流,因此这个构造方法里没有打开文件操作。输入的 FD对应的一定是一个已经打开的文件。

下面的示例代码演示了用 FileDescriptor 构造文件流对象,以及它与上两种方式的不同。

 

// TestFileDescriptor

 

import java.io.*;

public class TestFileDescriptor
{
 public static void main(String[]args)
 {
  test1();
  test2();
  test3();
 }
 public static void test1()
 {
  try{
   FileInputStreamfi1 = new FileInputStream("TestFileDescriptor.java");
   FileInputStreamfi2 = new FileInputStream("TestFileDescriptor.java");
   FileDescriptorfd1 = fi1.getFD();
   FileDescriptorfd2 = fi2.getFD();  
   System.out.println("got" + fd1);  //结果证明:即使打开同一个文件创建文件流,他们的FileDescriptor对象也是不同的。事实上,在文件输入流的构造方法中创建了该流关联的FD。
   System.out.println("got" + fd2);  //
   if(fd1!=null)
   System.out.println("valid " +fd1.valid());  
    if(fd2!=null)
   System.out.println("valid " +fd2.valid());   // 都是有效的
   fi1.close();
   fi2.close();
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
 
 public static void test2()
 {
  try{
   FileInputStreamfi1 = new FileInputStream("TestFileDescriptor.java");
   FileInputStreamfi2 = new FileInputStream("TestFileDescriptor.java");
   int b1 =fi1.read();
   int b2 =fi2.read();
   System.out.println("b1= " + (char)b1);  // 这个例子演示对同一个文件创建的两个流是独立的,
   System.out.println("b2= " + (char)b2);  //互相不影响
   fi1.close();
   int b3 =fi2.read();                    //一个流关闭不影响另一个流的读取
   System.out.println("b3= " + (char)b3);
   fi2.close();
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
 public static void test3()
 {
  try{
   FileInputStreamfi1 = new FileInputStream("TestFileDescriptor.java");
   FileInputStreamfi2 = newFileInputStream(fi1.getFD());   // 用 FD 构造不同于其他两种构造方法,这种情况下,两个流共用一个 FD
   int b1 =fi1.read();
   int b2 =fi2.read();
   System.out.println("b1= " + (char)b1);
   System.out.println("b2= " +(char)b2);    //结果说明:两个流实际是一个流(流对象不同,FD 相同)。
   fi1.close();
   int b3 =fi2.read();                     // 这里会抛出异常,原因是 FD 标识的文件已关闭。
   System.out.println("b3= " + (char)b3);
   fi2.close();
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
}

[关于 FileDescriptor]:

通过上面示例,我们初步理解了实体文件,文件描述(FileDescriptor),和文件流之间的关系。文件描述其实是操作系统层面的一个概念。(而流更多的可以理解成 Java 层面的概念。)FD 是一个句柄(handle) 用于指代与平台相关的文件结构。它可以看作是文件操作中 Java 代码和底层 C代码之间指代文件的一个桥梁。一般来说这个对象由系统创建并维护,Java 程序员不该直接实例化这个类。

 

FD 不仅用于输入流的创建,也可用于输出流的创建。关于它的使用还有一些有趣的示例,将在本站输出流的文章中给出。

 

说明:

本文提到的Java API 版本是Jdk 5.0;相关的实现指的是Jdk 5.0 API在windows 系统上的实现。


0 0
原创粉丝点击