Java学习笔记09——输入输出流

输入输出流

  • 1、File类
  • 2、输入、输出流的基本概念
  • 3、文件字节输入流、输出流
    • 文件字节输入流 FileInputStream
    • 文件字节输出流 FileOutputStream
  • 4、文件字符输入流、输出流
  • 5、缓冲流
  • 6、随机流 RandomAccessFile
  • 7、其他流
    • 数组流
      • 字节数组流
      • 字符数组流
    • 数据流
    • 对象流
      • 序列化与对象克隆
  • 8、其他
    • 使用Scanner类解析文件
    • 文件锁

1、File类

在程序运行期间,可能需要读取文件属性或创建新文件等,这时就需要使用File类。File类的对象主要用于获取文件本身的信息(属性),而不涉及对文件的读写操作(内容)

File对象的构造方法:

File(String pathname);              // 通过将给定的路径名字符串转换为抽象路径名来创建一个新的File实例。
File(String parent, String child);  // 从父路径名字符串和子路径名字符串创建一个新的File实例
File(File parent, String child);    // 从父抽象路径名和子路径名字符串创建一个新的File实例。
File(URI uri);                      // 通过将给定的File: URI转换为抽象路径名来创建一个新的File实例。

不同平台路径分隔符的区别:Windows平台使用\作为路径分隔符,在Java字符串中需要用\\表示一个\(第一个\为转义字符)。Unix平台使用/作为路径分隔符。

File类的常用方法:

  1. 文件相关
    • public boolean exists(); 文件是否存在
    • public boolean createNewFile(); 文件不存在时,创建文件
    • public String getName(); 获取文件名
    • public boolean canRead(); 文件是否可读
    • public boolean canWrite(); 文件是否可被写入
    • public boolean isFile(); 判断是否是文件(非目录)
    • public long length(); 文件的字节长度
    • public String getAbsolutePath(); 获取文件的绝对路径
    • public boolean isHidden(); 判断文件是否隐藏
    • public boolean isAbsolute(); 判断文件路径是否为绝对路径
    • public long lastModified(); 获取文件的最后修改时间
    • public boolean delete(); 文件无子目录时,删除文件
  2. 目录相关
    • public boolean mkdir(); 目录不存在时,创建目录
    • public boolean isDirectory(); 判断是否是目录
    • public String getParent(); 获取文件的父目录
    • public String[] list(); 用字符串数组返回目录下的全部文件
    • public File[] listFiles(); 以File对象数组返回目录下的所有文件
    • public String[] list(FilenameFilter filter); 用字符串数组返回符合filter条件的目录下的全部文件
    • public File[] listFiles(FilenameFilter filter) 用File对象数组返回符合filter条件的目录下的全部文件
    • public File[] listFiles(FileFilter filter) 用File对象数组返回符合filter条件的目录下的全部文件

2、输入、输出流的基本概念

当程序需要从外部存储媒介或程序中读取数据时,就需要使用输入流;当程序需要将处理的结果写入存储媒介或者传送给其他程序时,就需要使用输出流。

java.io包(I/O流库)提供大量的流类用于传输数据,所有输入流都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类。所有输入流都是抽象类OuputStream(字节输出流)或抽象类Writer(字符输出流)的子类。

关于字节流和字符流的区别可以参考我的文章:字符串的使用>字符串与字符数组、字节数组。

使用输入流通常包括四个基本步骤:

  1. 设定输入流的源。
  2. 创建指向源的输入流。
  3. 让输入流读取源中的数据。
  4. 关闭输入流。

使用输出流同样包括四个基本步骤:

  1. 给出输出流的目的地。
  2. 创建指向目的地的输出流。
  3. 让输出流把数据写入到目的地。
  4. 关闭输出流。

流类最常用的方法为:

类名(String name);   // 构造方法,通过文件名指定源或目的地
类名(File file);     // 构造方法,通过File对象指定源或目的地
read();  // 输入流读取数据。
write(); // 输出流写入数据。
close(); // 关闭流。

流类的构造方法会抛出FileNotFoundException异常,例如文件不存在、是目录而不是常规文件,或者由于其他原因无法打开读取。因此程序必须在try-catch语句中创建流。

3、文件字节输入流、输出流

文件字节输入流 FileInputStream

文件字节输入流的 read 方法以字节为单位读取源中的数据。

  • int read()方法以整数形式返回单个字节的数据,未读出字节就返回-1。
  • int read(byte[] b)方法试图读取b.length个字节到字节数组b中,返回读取到的总字节个数,达到文件末尾就返回-1。
  • int read(byte[] b, int off, int len)方法中参数off指定从字节数组b的某个位置开始存放数据,读取的长度为len。
public static void main(String[] args) {
   File file = new File("D:\\Desktop\\新建 Text 文件.txt"); // Windows平台中使用 "\\" 做路径分隔符
   int len = (int) file.length();   // 获取文件长度并强转为int型
   byte[] bytes = new byte[len];    // 用字节数组于存放将读取的数据
   try { // 必须在try-catch语句中创建流。
      FileInputStream fileInputStream = new FileInputStream(file);
      fileInputStream.read(bytes);
   } catch (IOException e) {
      e.printStackTrace();
   }
   System.out.println(Arrays.toString(bytes));  // 输出结果为字节数组形式
}

文件字节输出流 FileOutputStream

文件字节输出流相比输入流的构造方法多了一个boolean类型的参数append,如果指定append为true则输出流将字符写在文件末尾而不是开头。如果不指定则默认append为为false。

文件字节输出流以字节为单位向文件写入内容:

  • void write(int n)方法向目的地写入单个字节。
  • void write(byte[] b)方法向目的地写入一个字节数组。
  • void write(byte[] b, int off, int len)将字节数组中起始于偏移量off处取len个字节写入目的地。

4、文件字符输入流、输出流

使用字节为单位读写字符时,由于各种字符编码方式的不同,处理不当可能出现乱码问题;因此,处理字符文件时,更常使用字符输入输出流。文件字符输入流:FileReader;文件字符输出流:FileWriter。

在使用时,文件字符输入输出流与文件字节输入输出流基本相同,但也存在一些区别:

  1. 字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
  2. 字节输出流没有缓冲区。而字符输出流带有缓冲区,write()方法会先将数据写入缓冲区,当缓冲区溢出或使用flush方法时,才会将缓冲区内的内容写入到目的地。(缓冲的作用是提高效率)

5、缓冲流

在没有缓冲的情况下,每次读写可能都要调用底层系统从磁盘中进行文件读写,这样的结果是效率较低。缓冲的意思是先在内存中提供一块缓冲区,先将数据存入缓冲区再进行读写以提高效率。Java给每种输入输出流都提供了缓冲流,对应的构造方法为:

/* 其中参数sz和size都为缓冲区大小,是可选参数 */
BufferedReader(@NotNull Reader in, int sz); 
BufferedWriter(@NotNull Writer out, int sz);
BufferedInputStream(@NotNull InputStream in, int size);
BufferedOutputStream(@NotNull OutputStream out, int size);

缓冲流的常用方法:

  • BufferedReader新增了readLine()方法,即每次读取一行文本。
  • BufferedWriter新增了newLine()方法,此方法能写入换行符。
  • BufferedOutputStream新增了flush()方法,使用此方法立刻将缓冲区内的数据写入目的地。

6、随机流 RandomAccessFile

随机流既可以读文件又可以写文件。它并不是InputStream或OutoutStream的子类。如果想对一个文件中的数据同时进行读写操作时,可以考虑使用随机流。其构造参数为:

RandomAccessFile(String name, String mode);
RandomAccessFile(File file, String mode);

其中mode参数支持四个值“r”、“rw”、“rwd”和“rws”:

  1. “r” 仅供读。
  2. “rw” 开放读和写。如果该文件不存在,则会尝试创建它。
  3. “rwd” 与“rw”一样,允许读写,并且还要求对文件内容的每次更新都同步写入底层存储设备。
  4. “rws”和“rwd”相似,但使用“rwd”只需要更新文件的内容写入存储;使用“rws”需要更新文件的内容和要写入的元数据。

RandomAccessFile类中有一个方法void seek(long pos)用来设置流的读写位置,参数pos即读写位置距离文件开头的字节个数;也可以使用long getFilePointer()方法获取当前读写位置。除了支持字节与字符读写,随机流还支持各种基本类型的读写,显然随机流对文件的读写更加灵活。

需要注意的是,随机流的readLine()方法只支持ASCII字符的编码,如果需要读取存在非ASCII字符的文件时,可以使用 ISO_8859_1 编码将读取到的字符串转为字节数组再转回字符串

RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\Desktop\\新建 Text 文件.txt", "rw");
String s = randomAccessFile.readLine(); // 使用随机流读取地第一行
System.out.println(s);  // 输出 ä½ å¥½ï¼ŒJava。
byte[] b = s.getBytes(StandardCharsets.ISO_8859_1); // 使用 ISO_8859_1 编码转为字节数组
s = new String(b);      // 将字节数组转回字符串
System.out.println(s);  // 输出 你好,Java。

7、其他流

数组流

字节数组流

字节数组输入流ByteArrayInputStream和字节数组输出流ByteArrayInptyStream可以使用字节数组作为源和目的地。

字符数组流

与字节数组流相对应,字符数组流分为CharArrayReader类和CharArrayWriter类。字符数组流以字符作为源和目的地。

数据流

数据流分为DataInputStream类和DataOutputStream类,和随机流类似,它们允许以Java基本数据类型为单位进行读写。这样做的好处是,当我们读取一个数值时,不用考虑这个数值需要多少个字节。

对象流

对象流分为ObjectInputStream类和ObjectOutputStream类,它们可以以对象为单位进行读写。需要注意的是,当使用对象流读写对象时,要保证对象是序列化的,这是为了保证能对对象正确的进行读写。

一个类如果实现了Serializable接口,那么这个类创建的对象就可以称为是序列化的对象。同时,类中的成员对象也必须是序列化的。(Serializable接口中的方法不可见,由JVM实现)

序列化与对象克隆

对于一个引用类型的对象,使用"="只能复制其引用。有时想复制一个对象引用的实体,就可以通过对象序列化进行复制。

/* 通过序列化实现对象克隆 */
public static Object objectClone(Object object) throws IOException, ClassNotFoundException {
   /* 将对象序列化转为字节流 */
   ByteArrayOutputStream outByte = new ByteArrayOutputStream();
   ObjectOutputStream outObject = new ObjectOutputStream(outByte);
   outObject.writeObject(object);
   outObject.close();
   /* 将字节流转回为对象 */
   ObjectInputStream inObject = new ObjectInputStream(new ByteArrayInputStream(outByte.toByteArray()));
   Object newObject = inObject.readObject();
   inObject.close();
   return newObject;
}

如果不希望对象中的某些变量值序列化,可以使用transient关键字进行修饰。

8、其他

使用Scanner类解析文件

有时需要对文件中的内容进行解析,一般的方法是:将文件内容全部读入内存,再通过字符串处理的方式进行解析,这样做的优点是在内存中进行解析速度较快;但当文件内容较多将消耗比较大的内存,这时可以考虑通过Scanner对文件内容进行解析。
Scanner类是Java中一个可以使用正则表达式解析字符串的类,它的构造法方法需要传入被解析的源,这个源可以是字符串、文件路径、输入流等。使用Scanner的优点是内存占用较小;但速度相对较慢。

文件锁

和多线程类似,当出现多个程序同时处理一个文件的情况时,可能导致文件处理错误。这时可以通过FileLock和FileChannel类对文件加锁。
使用文件锁的步骤如下:

  1. 使用流建立指向文件的流对象,该对象的属性应为可读可写。
  2. 通过流调用 getChannel() 方法获得与底层文件相连FileChannel(信道)对象。
  3. 信道调用tryLock()lock()方法获得一个FileLock(文件锁)对象。
  4. 文件锁对象产生后,将禁止任何文件对文件进行加锁操作。被锁住的文件不允许被读写,如果希望解除锁,可以使用lock.relase()方法释放锁。
RandomAccessFile randomAccessFile = new RandomAccessFile("Example.java", "rw");  // 建立指向文件的流对象
FileChannel fileChannel = randomAccessFile.getChannel(); // 获得与底层文件相连FileChannel(信道)对象
FileLock fileLock = fileChannel.tryLock();   // 获得一个FileLock(文件锁)对象
fileLock.release();  // 释放锁

你可能感兴趣的:(Java学习,java,学习)