二、输入与输出

本文详细介绍了Java中的输入输出流概念,包括字节流和字符流的使用,探讨了如何以二进制和文本格式读写数据,以及如何处理Unicode编码。此外,还深入解析了对象序列化机制,包括序列化对象的保存与加载,以及如何修改默认的序列化机制。

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

 

如何访问文件与目录,以及如何以二进制格式和文本格式来读写数据,对象序列化机制

unicode、utf-8

各种数据类型到byte[]转换

1 输入、输出流

   可以从其中读入一个字节序列的对象称为输入流,而可以向其中写入一个字节序列的对象称作输出流。抽象类InputStreamOutputStream构成了I/O类层次结构的基础。

  因为面向字节的流不便于处理以Unicode形式存储的信息,所以从抽象类Reader和Writer中继承出来了一个专门用于处理Unicode字符的单独的类层次结构。这些类拥有的读入和写出操作都是基于Unicode码元的,而不是基于byte值的

1.1 读写字节

InputStream api:

    • Modifier and TypeMethod and Description
      intavailable()

      返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。

      voidclose()

      关闭此输入流并释放与流相关联的任何系统资源。

      voidmark(int readlimit)

      标记此输入流中的当前位置。

      booleanmarkSupported()

      测试这个输入流是否支持 markreset方法。

      abstract intread()

      从输入流读取数据的下一个字节。遇到结尾时返回1

      intread(byte[] b)

      从输入流读取一些字节数,并将它们存储到缓冲区 b

      intread(byte[] b, int off, int len)

      从输入流读取最多 len字节的数据到一个字节数组。

      voidreset()

      将此流重新定位到上次在此输入流上调用 mark方法时的位置。

      longskip(long n)

      跳过并丢弃来自此输入流的 n字节数据。

  非抽象的两个read方法底层调用了read()方法。所以,子类只需要覆盖read()方法。

OutputStream api:

    • Modifier and TypeMethod and Description
      voidclose()

      关闭此输出流并释放与此流相关联的任何系统资源。

      voidflush()

      刷新此输出流并强制任何缓冲的输出字节被写出。

      voidwrite(byte[] b)

      b.length字节从指定的字节数组写入此输出流。

      voidwrite(byte[] b, int off, int len)

      从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。

      abstract voidwrite(int b)

      将指定的字节写入此输出流。

 read和write方法在未完成时会阻塞

  available方法使我们可以去检查当前的读入的字节数量,下面的方法就不可能被阻塞:

int byteAvailable=in.available();
if(buteAvailable>0){
    byte[] data=new byte[byteAvailable];
    in.read(data);
}

  关闭一个输出流的同时还会冲刷该输出流的缓冲区。所有被临时放置在缓冲区中,以便用更大的包的形式传递的字节在关闭输出流时都将被送出。还可以使用flush方法来认为的冲刷这些输出。

1.2 完整的流家族

DataInputStream和DataOutputStream可以以二进制格式读写所有的基本JAVA类型。

另一方面,对于Unicode文本,可以使用抽象类Reader和Writer的子类。

api和inputStream和OutputStream类似。

java.io.Closeable api:

    • Modifier and TypeMethod and Description
      voidclose()

      关闭此流并释放与之相关联的任何系统资源。

java.lang.Readable api:

    • intread(CharBuffer cb)

      尝试将字符读入指定的字符缓冲区。

java.lang.Appendable api:

java.io.Flushable api:

    • and TypeMethod and Description
      voidflush()

      通过将任何缓冲的输出写入底层流来刷新流。

java.lang.CharSequence api

    • charcharAt(int index)

      返回 char指定索引处的值。

      default IntStreamchars()

      返回一个 int的流,从这个序列中零扩展 char值。

      default IntStreamcodePoints()

      从此序列返回码流值。

      intlength()

      返回此字符序列的长度。

      CharSequencesubSequence(int start, int end)

      返回一个 CharSequence ,这是这个序列的一个子序列。

      StringtoString()

      以与此顺序相同的顺序返回包含此序列中的字符的字符串。

1.3 组合输入、输出流过滤器

所有在java.io中的类都将相对路径名解释为以用户工作目录为开始,可以这样的搭配这个信息:

String b=System.getProperty("user.dir");

在Windows风格中,路径名用\分开。字符串中用\\.也可以用/,

java使用Decorator模式类实现流职责的管理,FilterInputStream和FilterOutputStream这些类的子类用于向处理字节的输入/输出流添加额外的功能。可以通过嵌套过滤器来实现这种功能

                String b=System.getProperty("user.dir");
		//System.out.println(b);
		String c=b+"\\1.txt";
		//一种方法,直接写。各种数据转换成字节还是挺费劲的。
		/*OutputStream fin=new FileOutputStream(c,true);
		String str="World";
		fin.write(str.getBytes());
		//组合输出过滤器
		DataOutputStream din=new DataOutputStream(fin);
		din.writeBytes(str);*/
		//读文件
		DataInputStream din=new DataInputStream(new FileInputStream(c));
		String s=din.readLine();
		System.out.println(s);

有时当多个输入流链接在一起时,你需要跟踪各个中介输入流。例如,当读入输入时,你经常需要预览下一个字节,以了解它是否是你想要的值。JAVA提供了用于此目的的PushBackInputStream

java.io.PushBackInputStream api

    • Modifier and TypeMethod and Description
      intavailable()

      返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。

      voidclose()

      关闭此输入流并释放与流相关联的任何系统资源。

      voidmark(int readlimit)

      标记此输入流中的当前位置。

      booleanmarkSupported()

      测试这个输入流是否支持 markreset方法,而不是。

      intread()

      从该输入流读取下一个数据字节。

      intread(byte[] b, int off, int len)

      从该输入流读取最多 len字节的数据为字节数组。

      voidreset()

      将此流重新定位到最后在此输入流上调用 mark方法时的位置。

      longskip(long n)

      跳过并丢弃来自此输入流的 n字节的数据。

      voidunread(byte[] b)

      将一个字节数组复制回推回缓冲区的前端。

      voidunread(byte[] b, int off, int len)

      通过将字节数组复制到推回缓冲区的前端来推回一部分数组。

      voidunread(int b)

      通过将其复制到推回缓冲区的前端来推回一个字节。

2 文本输入与输出

在保存数据时,可以选择二进制和文本格式。在存储文本字符串时,需要考虑字符编码方式。

OutputStreamWriter使用选定的编码方式,把Unicode码元的输出流转换为字节流。InputStreamReader将包含字节的输入流转换为可以产生Unicode码元的读入器。

InputStreamReader api:

    • Modifier and TypeMethod and Description
      voidclose()

      关闭流并释放与之相关联的任何系统资源。

      StringgetEncoding()

      返回此流使用的字符编码的名称。

      intread()

      读一个字符

      intread(char[] cbuf, int offset, int length)

      将字符读入数组的一部分。

      booleanready()

      告诉这个流是否准备好被读取。

OutputStreamWriter api:

    • Modifier and TypeMethod and Description
      voidclose()

      关闭流,先刷新。

      voidflush()

      刷新流。

      StringgetEncoding()

      返回此流使用的字符编码的名称。

      voidwrite(char[] cbuf, int off, int len)

      写入字符数组的一部分。

      voidwrite(int c)

      写一个字符

      voidwrite(String str, int off, int len)

      写一个字符串的一部分。

2.1 如何写出文本输出

PrintWriter类,用于文本输出

    • Constructor and Description
      PrintWriter(File file)

      使用指定的文件创建一个新的PrintWriter,而不需要自动的线路刷新。

      PrintWriter(File file, String csn)

      使用指定的文件和字符集创建一个新的PrintWriter,而不需要自动进行线条刷新。

      PrintWriter(OutputStream out)

      从现有的OutputStream创建一个新的PrintWriter,而不需要自动线路刷新。

      PrintWriter(OutputStream out, boolean autoFlush)

      从现有的OutputStream创建一个新的PrintWriter。

      PrintWriter(String fileName)

      使用指定的文件名创建一个新的PrintWriter,而不需要自动执行行刷新。

      PrintWriter(String fileName, String csn)

      使用指定的文件名和字符集创建一个新的PrintWriter,而不需要自动线路刷新。

      PrintWriter(Writer out)

      创建一个新的PrintWriter,没有自动线冲洗。

      PrintWriter(Writer out, boolean autoFlush)

      创建一个新的PrintWriter。

    • Modifier and TypeMethod and Description
      PrintWriterappend(char c)

      将指定的字符附加到此作者。

      PrintWriterappend(CharSequence csq)

      将指定的字符序列附加到此作者。

      PrintWriterappend(CharSequence csq, int start, int end)

      将指定字符序列的子序列附加到此作者。

      booleancheckError()

      如果流未关闭,请刷新流并检查其错误状态。

      protected voidclearError()

      清除此流的错误状态。

      voidclose()

      关闭流并释放与之相关联的任何系统资源。

      voidflush()

      刷新流。

      PrintWriterformat(Locale l, String format, Object... args)

      使用指定的格式字符串和参数将格式化的字符串写入此写入程序。

      PrintWriterformat(String format, Object... args)

      使用指定的格式字符串和参数将格式化的字符串写入此写入程序。

      voidprint(boolean b)

      打印布尔值。

      voidprint(char c)

      打印一个字符

      voidprint(char[] s)

      打印字符数组。

      voidprint(double d)

      打印双精度浮点数。

      voidprint(float f)

      打印浮点数。

      voidprint(int i)

      打印一个整数。

      voidprint(long l)

      打印一个长整数。

      voidprint(Object obj)

      打印一个对象。

      voidprint(String s)

      打印字符串。

      PrintWriterprintf(Locale l, String format, Object... args)

      使用指定的格式字符串和参数将格式化的字符串写入该writer的方便方法。

      PrintWriterprintf(String format, Object... args)

      使用指定的格式字符串和参数将格式化的字符串写入该writer的方便方法。

      voidprintln()

      通过写入行分隔符字符串来终止当前行。

      voidprintln(boolean x)

      打印一个布尔值,然后终止该行。

      voidprintln(char x)

      打印一个字符,然后终止该行。

      voidprintln(char[] x)

      打印字符数组,然后终止行。

      voidprintln(double x)

      打印双精度浮点数,然后终止行。

      voidprintln(float x)

      打印一个浮点数,然后终止该行。

      voidprintln(int x)

      打印一个整数,然后终止该行。

      voidprintln(long x)

      打印一个长整型,然后终止行。

      voidprintln(Object x)

      打印一个对象,然后终止该行。

      voidprintln(String x)

      打印一个字符串,然后终止行。

      protected voidsetError()

      表示发生错误。

      voidwrite(char[] buf)

      写入一个字符数组。

      voidwrite(char[] buf, int off, int len)

      写一个字符数组的一部分。

      voidwrite(int c)

      写一个字符

      voidwrite(String s)

      写一个字符串

      voidwrite(String s, int off, int len)

      写一个字符串的一部分。

  println方法在行中添加了对于目标系统来说恰当的行结素福。就是通过调用

System.getProperty("line.separator")

  而获得的字符串。

  如果写出其设置为自动冲刷模式,那么只要println被调用,缓冲区中的所有字符都会被发送到它们的目的地。

  PrintStream把所有Unicode截断成ASCII字符。PrintWriter使用默认主机编码方式编码。

  PrintStream可以write(byte[])方法输出原生字节。

2.2 如何读入文本输入

    最简单的方法就是Scanner类,可以从任何输入流中构建Scanner对象。

   

 

BufferedReader api

    • Constructor and Description
      BufferedReader(Reader in)

      创建使用默认大小的输入缓冲区的缓冲字符输入流。

      BufferedReader(Reader in, int sz)

      创建使用指定大小的输入缓冲区的缓冲字符输入流。

    • Modifier and TypeMethod and Description
      voidclose()

      关闭流并释放与之相关联的任何系统资源。

      Stream<String>lines()

      返回一个 Stream ,其元素是从这个 BufferedReader读取的行。

      voidmark(int readAheadLimit)

      标记流中的当前位置。

      booleanmarkSupported()

      告诉这个流是否支持mark()操作。

      intread()

      读一个字符

      intread(char[] cbuf, int off, int len)

      将字符读入数组的一部分。

      StringreadLine()

      读一行文字。

      booleanready()

      告诉这个流是否准备好被读取。

      voidreset()

      将流重置为最近的标记。

      longskip(long n)

      跳过字符

2.3 以文本格式存储对象

/**
 * @version 1.14 2016-07-11
 * @author Cay Horstmann
 */
public class TextFileTest
{
   public static void main(String[] args) throws IOException
   {
      Employee[] staff = new Employee[3];

      staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

      // save all employee records to the file employee.dat
      try (PrintWriter out = new PrintWriter("employee.dat", "UTF-8"))
      {         
         writeData(staff, out);
      }
      
      // retrieve all records into a new array
      try (Scanner in = new Scanner(
            new FileInputStream("employee.dat"), "UTF-8"))
      {
         Employee[] newStaff = readData(in);

         // print the newly read employee records
         for (Employee e : newStaff)
            System.out.println(e);
      }
   }

   /**
    * Writes all employees in an array to a print writer
    * @param employees an array of employees
    * @param out a print writer
    */
   private static void writeData(Employee[] employees, PrintWriter out) throws IOException
   {
      // write number of employees
      out.println(employees.length);

      for (Employee e : employees)
         writeEmployee(out, e);
   }

   /**
    * Reads an array of employees from a scanner
    * @param in the scanner
    * @return the array of employees
    */
   private static Employee[] readData(Scanner in)
   {
      // retrieve the array size
      int n = in.nextInt();
      in.nextLine(); // consume newline

      Employee[] employees = new Employee[n];
      for (int i = 0; i < n; i++)
      {
         employees[i] = readEmployee(in);
      }
      return employees;
   }
   
   /**
    * Writes employee data to a print writer
    * @param out the print writer
    */
   public static void writeEmployee(PrintWriter out, Employee e)
   {
      out.println(e.getName() + "|" + e.getSalary() + "|" + e.getHireDay());
   }

   /**
    * Reads employee data from a buffered reader
    * @param in the scanner
    */
   public static Employee readEmployee(Scanner in)
   {
      String line = in.nextLine();
      String[] tokens = line.split("\\|");
      String name = tokens[0];
      double salary = Double.parseDouble(tokens[1]);
      LocalDate hireDate = LocalDate.parse(tokens[2]);
      int year = hireDate.getYear();
      int month = hireDate.getMonthValue();
      int day = hireDate.getDayOfMonth();
      return new Employee(name, salary, year, month, day);
   }   
}

2.4 字符编码方式

 输入和输出流都是用于字节序列的,但是在许多情况下,我们希望操作的是文本,即字符序列。

  java针对字符使用的是Unicode标准。

  有两种形式的UTF-16.低位优先和高位优先。使用字节顺序标记判断 16位数值

  UTF-8编码的文件开头处可能存在一个字节顺序标记。应该先把它剥离掉。

  StandardCharsets类具有类型为CharSet的静态变量,用于表示JAVA虚拟机都必须支持的字符编码方式。

java.nio.Charset

在不指定编码方式时,有些方法会使用默认的平台编码方式,而其它方法会使用UTF-8.

3 读写二进制数据

 文本格式方便,但是不高效。

3.1 DataInput和DataOutput接口

存储整数和浮点数两种不同的方法:高位优先和低位优先。java中高位优先,独立于平台。

DataInput和DataOutput接口分别由DataInputStream和DataOutputStream实现。

java.io.DataInput:

    • Modifier and TypeMethod and Description
      booleanreadBoolean()

      读取一个输入字节,并返回 true如果该字节不为零, false如果该字节是零。

      bytereadByte()

      读取并返回一个输入字节。

      charreadChar()

      读取两个输入字节并返回一个 char值。

      doublereadDouble()

      读取八个输入字节并返回一个 double值。

      floatreadFloat()

      读取四个输入字节并返回一个 float值。

      voidreadFully(byte[] b)

      从输入流读取一些字节,并将它们存储到缓冲区数组 b

      voidreadFully(byte[] b, int off, int len)

      从输入流读取 len个字节。

      intreadInt()

      读取四个输入字节并返回一个 int值。

      StringreadLine()

      从输入流读取下一行文本。

      longreadLong()

      读取八个输入字节并返回一个 long值。

      shortreadShort()

      读取两个输入字节并返回一个 short值。

      intreadUnsignedByte()

      读取一个输入字节,将其扩展到类型 int ,并返回结果,因此在 0255

      intreadUnsignedShort()

      读取两个输入字节,并返回 065535int值。

      StringreadUTF()

      读取已使用 modified UTF-8格式编码的字符串。

      intskipBytes(int n)

      尝试从输入流中跳过 n字节的数据,丢弃跳过的字节。

java.io.DataOutput:

    • Modifier and TypeMethod and Description
      voidwrite(byte[] b)

      将输出流写入数组 b中的所有字节。

      voidwrite(byte[] b, int off, int len)

      从阵列 b写入 len字节,以输出流。

      voidwrite(int b)

      向输出流写入参数 b的八个低位。

      voidwriteBoolean(boolean v)

      boolean值写入此输出流。

      voidwriteByte(int v)

      向输出流写入参数 v的八个低位位。

      voidwriteBytes(String s)

      将一个字符串写入输出流。

      voidwriteChar(int v)

      将两个字节组成的 char值写入输出流。

      voidwriteChars(String s)

      写入每一个字符在字符串中 s ,到输出流中,为了,每个字符使用两个字节。

      voidwriteDouble(double v)

      double值(由8个字节组成)写入输出流。

      voidwriteFloat(float v)

      float值写入输出流,该值由四个字节组成。

      voidwriteInt(int v)

      int值(由四个字节组成)写入输出流。

      voidwriteLong(long v)

      long值(由八个字节组成)写入输出流。

      voidwriteShort(int v)

      将两个字节写入输出流以表示参数的值。

      voidwriteUTF(String s)

      将两个字节的长度信息写入输出流,其后是 字符串 s中每个字符的 s

3.2 随机访问文件

  RandomAccessFile类可以在文件中的任何位置查找或写入数据。磁盘文件都是可随机访问的,但是与网络套接字通信的输入输出流并不是。r用于访问,ew用于读写访问。

java.io.RandomAccessFile:

    • Constructor and Description
      RandomAccessFile(File file, String mode)

      创建一个随机访问文件流从File参数指定的文件中读取,并可选地写入文件。

      RandomAccessFile(String name, String mode)

      创建随机访问文件流,以从中指定名称的文件读取,并可选择写入文件。

    • Modifier and TypeMethod and Description
      voidclose()

      关闭此随机访问文件流并释放与流相关联的任何系统资源。

      FileChannelgetChannel()

      返回与此文件关联的唯一的FileChannel对象。

      FileDescriptorgetFD()

      返回与此流关联的不透明文件描述符对象。

      longgetFilePointer()

      返回此文件中的当前偏移量。

      longlength()

      返回此文件的长度。

      intread()

      从该文件读取一个字节的数据。

      intread(byte[] b)

      从该文件读取最多 b.length字节的数据到字节数组。

      intread(byte[] b, int off, int len)

      从该文件读取最多 len个字节的数据到字节数组。

      booleanreadBoolean()

      从此文件读取一个 boolean

      bytereadByte()

      从此文件中读取一个带符号的八位值。

      charreadChar()

      从此文件中读取一个字符。

      doublereadDouble()

      从此文件读取 double

      floatreadFloat()

      从此文件读取一个 float

      voidreadFully(byte[] b)

      从此文件读取 b.length字节到字节数组,从当前文件指针开始。

      voidreadFully(byte[] b, int off, int len)

      从此文件中读取 len个字节到字节数组,从当前文件指针开始。

      intreadInt()

      从该文件读取一个带符号的32位整数。

      StringreadLine()

      从此文件中读取下一行文本。

      longreadLong()

      从该文件中读取一个带符号的64位整数。

      shortreadShort()

      从此文件中读取一个已签名的16位数字。

      intreadUnsignedByte()

      从此文件中读取一个无符号的八位数字。

      intreadUnsignedShort()

      从该文件中读取一个无符号的16位数字。

      StringreadUTF()

      从该文件读取字符串。

      voidseek(long pos)

      设置文件指针偏移,从该文件的开头测量,发生下一次读取或写入。

      voidsetLength(long newLength)

      设置此文件的长度。

      intskipBytes(int n)

      尝试跳过 n字节的输入丢弃跳过的字节。

      voidwrite(byte[] b)

      从指定的字节数组写入 b.length个字节到该文件,从当前文件指针开始。

      voidwrite(byte[] b, int off, int len)

      从指定的字节数组写入 len个字节,从偏移量 off开始写入此文件。

      voidwrite(int b)

      将指定的字节写入此文件。

      voidwriteBoolean(boolean v)

      boolean写入文件作为一个字节值。

      voidwriteByte(int v)

      byte写入文件作为单字节值。

      voidwriteBytes(String s)

      将字符串作为字节序列写入文件。

      voidwriteChar(int v)

      char写入文件作为两字节值,高字节为先。

      voidwriteChars(String s)

      将一个字符串作为字符序列写入文件。

      voidwriteDouble(double v)

      双参数传递给转换 long使用 doubleToLongBits方法在类 Double ,然后写入该 long值到该文件作为一个八字节的数量,高字节。

      voidwriteFloat(float v)

      浮子参数的转换 int使用 floatToIntBits方法在类 Float ,然后写入该 int值到该文件作为一个四字节数量,高字节。

      voidwriteInt(int v)

      int写入文件为四个字节,高字节 int

      voidwriteLong(long v)

      long写入文件为八个字节,高字节为先。

      voidwriteShort(int v)

      short写入文件作为两个字节,高字节优先。

      voidwriteUTF(String str)

      以机器无关的方式使用 modified UTF-8编码将字符串写入文件。

//程序清单2-2
package randomAccess;

import java.io.*;
import java.util.*;
import java.time.*;

/**
 * @version 1.13 2016-07-11
 * @author Cay Horstmann
 */
public class RandomAccessTest
{  
   public static void main(String[] args) throws IOException
   {
      Employee[] staff = new Employee[3];

      staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

      try (DataOutputStream out = new DataOutputStream(new FileOutputStream("employee.dat")))
      {  
         // save all employee records to the file employee.dat
         for (Employee e : staff)
            writeData(out, e);
      }
         
      try (RandomAccessFile in = new RandomAccessFile("employee.dat", "r"))
      {
         // retrieve all records into a new array
            
         // compute the array size
         int n = (int)(in.length() / Employee.RECORD_SIZE);
         Employee[] newStaff = new Employee[n];

         // read employees in reverse order
         for (int i = n - 1; i >= 0; i--)
         {  
            newStaff[i] = new Employee();
            in.seek(i * Employee.RECORD_SIZE);
            newStaff[i] = readData(in);
         }
         
         // print the newly read employee records
         for (Employee e : newStaff) 
            System.out.println(e);
      }
   }

   /**
    * Writes employee data to a data output
    * @param out the data output
    * @param e the employee
    */
   public static void writeData(DataOutput out, Employee e) throws IOException
   {
      DataIO.writeFixedString(e.getName(), Employee.NAME_SIZE, out);
      out.writeDouble(e.getSalary());

      LocalDate hireDay = e.getHireDay();
      out.writeInt(hireDay.getYear());
      out.writeInt(hireDay.getMonthValue());
      out.writeInt(hireDay.getDayOfMonth());
   }

   /**
    * Reads employee data from a data input
    * @param in the data input
    * @return the employee
    */
   public static Employee readData(DataInput in) throws IOException
   {      
      String name = DataIO.readFixedString(Employee.NAME_SIZE, in);
      double salary = in.readDouble();
      int y = in.readInt();
      int m = in.readInt();
      int d = in.readInt();
      return new Employee(name, salary, y, m - 1, d);
   }  
}

package randomAccess;

import java.time.*;

public class Employee
{
   public static final int NAME_SIZE = 40;
   public static final int RECORD_SIZE = 2 * NAME_SIZE + 8 + 4 + 4 + 4;
   
   private String name;
   private double salary;
   private LocalDate hireDay;

   public Employee() {}

   public Employee(String n, double s, int year, int month, int day)
   {  
      name = n;
      salary = s;
      hireDay = LocalDate.of(year, month, day);
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public LocalDate getHireDay()
   {
      return hireDay;
   }

   /**
      Raises the salary of this employee.
      @byPercent the percentage of the raise
   */
   public void raiseSalary(double byPercent)
   {  
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   public String toString()
   {  
      return getClass().getName()
         + "[name=" + name
         + ",salary=" + salary
         + ",hireDay=" + hireDay
         + "]";
   }
}

package randomAccess;

import java.io.*;

public class DataIO
{
   public static String readFixedString(int size, DataInput in) 
      throws IOException
   {  
      StringBuilder b = new StringBuilder(size);
      int i = 0;
      boolean more = true;
      while (more && i < size)
      {  
         char ch = in.readChar();
         i++;
         if (ch == 0) more = false;
         else b.append(ch);
      }
      in.skipBytes(2 * (size - i));
      return b.toString();
   }

   public static void writeFixedString(String s, int size, DataOutput out) 
      throws IOException
   {
      for (int i = 0; i < size; i++)
      {  
         char ch = 0;
         if (i < s.length()) ch = s.charAt(i);
         out.writeChar(ch);
      }
   }
}

3.3 ZIP文档(这是个问题)

通用读ZIP文件代码序列:

ZipInputStream zin=new ZipInputStream(new FileInputStream(zipname));
		ZipEntry entry;
		while((entry=zin.getNextEntry())!=null) {
			InputStream in=zin.getInputStream(entry);
			//read
			zin.closeEntry();
		}
		zin.close();

通用写ZIP文件代码序列:

ZipOutputStream zout=new ZipOutputStream(new FileInputStream("test.zip"));
		for all files{
			ZipEntry z=new ZipEntry(filename);
			zout.putNextEntry(z);
			send data to zout
			zout.closeEntry();
		}
		zout.close();

java.util.zip.ZipInputStream:

    • Modifier and TypeMethod and Description
      intavailable()

      EOF到达当前条目数据后返回0,否则返回1。

      voidclose()

      关闭此输入流并释放与流相关联的任何系统资源。

      voidcloseEntry()

      关闭当前的ZIP条目,并定位流以读取下一个条目。

      protected ZipEntrycreateZipEntry(String name)

      为指定的条目名称创建一个新的 ZipEntry对象。

      ZipEntrygetNextEntry()

      读取下一个ZIP文件条目,并将流定位在条目数据的开头。

      intread(byte[] b, int off, int len)

      从当前ZIP条目读取到字节数组。

      longskip(long n)

      跳过当前ZIP条目中指定的字节数。

java.util.zip.ZipOutputStream:

    • Modifier and TypeMethod and Description
      voidclose()

      关闭ZIP输出流以及正在过滤的流。

      voidcloseEntry()

      关闭当前的ZIP条目,并定位流以写入下一个条目。

      voidfinish()

      完成编写ZIP输出流的内容,而不关闭底层流。

      voidputNextEntry(ZipEntry e)

      开始编写新的ZIP文件条目,并将流定位到条目数据的开头。

      voidsetComment(String comment)

      设置ZIP文件注释。

      voidsetLevel(int level)

      设置DEFLATED的后续条目的压缩级别。

      voidsetMethod(int method)

      设置后续条目的默认压缩方法。

      voidwrite(byte[] b, int off, int len)

      将一个字节数组写入当前的ZIP条目数据。

java.util.zip.ZipEntry:

    • Constructor and Description
      ZipEntry(String name)

      创建具有指定名称的新的zip条目。

      ZipEntry(ZipEntry e)

      创建一个新的zip条目,其中的字段从指定的zip条目中取出。

java.util.zip.ZipFile:

    • Constructor and Description
      ZipFile(File file)

      打开一个ZIP文件,读取指定的File对象。

      ZipFile(File file, Charset charset)

      打开一个ZIP文件,读取指定的File对象。

      ZipFile(File file, int mode)

      打开一个新的 ZipFile ,以 ZipFile方式读取指定的 File对象。

      ZipFile(File file, int mode, Charset charset)

      打开一个新的 ZipFile ,以指定的方式从指定的 File对象中读取。

      ZipFile(String name)

      打开一个zip文件进行阅读。

      ZipFile(String name, Charset charset)

      打开一个zip文件进行阅读。

 四、对象输入、输出与序列化

JAVA序列化机制的深入研究

序列号的作用

  需要存储相同类型的数据时,使用固定长度的记录格式是不错的选择。在面向对象程序中创建的对象很少全部都具有相同的类型。我们可以设计出一种数据格式存储多态集合,但是,JAVA语言支持一种称为对象序列化的机制,它可以将任何对象写出到输入流中,并在之后将其读回。

4.1 保存和加载序列化对象

  对希望在对象输入流中存储或从对象输入流中恢复的所有类都进行一下修改,这些类必须实现Serializable接口

  ObjectInputStream和ObjectOutputStream分别继承了DataInput和DataOutput,但是分别增加了私有 的readObject和writeObject

class Man implements Serializable{
	String name;
	int age;
	public Man(String name,int age) {
		this.name=name;
		this.age=age;
	}
}
public class Main{
	
	public static void main(String[] args) throws InterruptedException, ExecutionException, IOException, ClassNotFoundException {
		//写入对象
		ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("employee.txt"));
		Man man=new Man("wang",19);
		Man man1=new Man("li",20);
		out.writeObject(man);
		out.writeObject(man1);
		//读出对象
		ObjectInputStream in=new ObjectInputStream(new FileInputStream("employee.txt"));
		Man m2=(Man)in.readObject();
		Man m3=(Man)in.readObject();
		System.out.println(m2.name);
	}
	
}

  当一个对象被多个对象共享,对象流中的对象输入和输出就不对了。

  解决方法:每个对象都是用一个序列号保存的,这就是这种机制之所以称为对象序列化的原因。下面是其算法:

  • 对你遇到的每一个对象引用都关联一个序列号
  • 对于每个对象,当第一次遇到时,保存其对象数据到输出流中。
  • 如果某个对象之前已经保存过,那么只写出“与之前保存过的序列号为x的对象相同”。

  在读回时,整个过程反过来

  • 对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据初始化,然后记录顺序号和新对象之间的关联。
  • 当遇到 "与之前保存的序列号为x的对象相同“标记时,获取与这个顺序号相关联的对象引用。

 

//程序清单2-3
package objectStream;

import java.io.*;

/**
 * @version 1.10 17 Aug 1998
 * @author Cay Horstmann
 */
class ObjectStreamTest
{
   public static void main(String[] args) throws IOException, ClassNotFoundException
   {
      Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      carl.setSecretary(harry);
      Manager tony = new Manager("Tony Tester", 40000, 1990, 3, 15);
      tony.setSecretary(harry);

      Employee[] staff = new Employee[3];

      staff[0] = carl;
      staff[1] = harry;
      staff[2] = tony;

      // save all employee records to the file employee.dat         
      try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.dat"))) 
      {
         out.writeObject(staff);
      }

      try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.dat")))
      {
         // retrieve all records into a new array
         
         Employee[] newStaff = (Employee[]) in.readObject();

         // raise secretary's salary
         newStaff[1].raiseSalary(10);

         // print the newly read employee records
         for (Employee e : newStaff)
            System.out.println(e);
      }
   }
}

package objectStream;

public class Manager extends Employee
{
   private Employee secretary;

   /**
    * Constructs a Manager without a secretary
    * @param n the employee's name
    * @param s the salary
    * @param year the hire year
    * @param month the hire month
    * @param day the hire day
    */
   public Manager(String n, double s, int year, int month, int day)
   {
      super(n, s, year, month, day);
      secretary = null;
   }

   /**
    * Assigns a secretary to the manager.
    * @param s the secretary
    */
   public void setSecretary(Employee s)
   {
      secretary = s;
   }

   public String toString()
   {
      return super.toString() + "[secretary=" + secretary + "]";
   }
}

package objectStream;

import java.io.*;
import java.time.*;

public class Employee implements Serializable
{
   private String name;
   private double salary;
   private LocalDate hireDay;

   public Employee() {}

   public Employee(String n, double s, int year, int month, int day)
   {  
      name = n;
      salary = s;
      hireDay = LocalDate.of(year, month, day);
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public LocalDate getHireDay()
   {
      return hireDay;
   }

   /**
      Raises the salary of this employee.
      @byPercent the percentage of the raise
   */
   public void raiseSalary(double byPercent)
   {  
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   public String toString()
   {  
      return getClass().getName()
         + "[name=" + name
         + ",salary=" + salary
         + ",hireDay=" + hireDay
         + "]";
   }
}

4.2 修改默认的序列化机制

  某些数据域是不可以序列化的。例如,只对本地方法有意义的存储文件句柄或窗口句柄的整数值,这种信息在稍后重新加载对象或将其传送到其他机器上时是没有用处的。

  有一种机制防止这种域被序列化。transient

  序列化并不保存静态变量

  final变量将直接通过值参与序列化,所以将final变量声明为transient变量不会产生任何影响。

  序列化机制为单个的类提供了一种方式,去向默认的读写行为添加验证或任何其它想要的行为。可序列化的类可以定义具有下列签名的方法:

  例如:

class LabledPoint implements Serializable{
	String lable;
	transient int point;
	public LabledPoint(String lable,int x) {
		this.lable=lable;
		point=x;
	}
	//这个private改成public都不行
	private void writeObject(ObjectOutputStream out) throws IOException{
		out.defaultWriteObject();
		out.writeInt(point);
	}
	private void readObject(ObjectInputStream in)throws IOException {
		try {
			in.defaultReadObject();
			point=in.readInt();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
public class Main{
	public static void main(String[] args) throws InterruptedException, ExecutionException, IOException, ClassNotFoundException {
		//写入对象
		ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("employee.txt"));
		LabledPoint point1=new LabledPoint("my",9);
		out.writeObject(point1);
		//读出对象
		ObjectInputStream in=new ObjectInputStream(new FileInputStream("employee.txt"));
		LabledPoint m2=(LabledPoint)in.readObject();
		System.out.println(m2.point);
	}
	
}

  除了让序列化机制来保存和恢复对象数据,类还可以定义自己的机制。为了做到这一点,这个类必须实现Extenalizable接口。

    控制序列化字段还可以使用Externalizable接口替代Serializable借口。此时需要定义一个默认构造器,否则将为得到一个异常(java.io.InvalidClassException)。因为:在重构 Externalizable 对象时,先使用无参数的公共构造方法创建一个实例,然后调用 readExternal 方法。通过从 ObjectInputStream 中读取 Serializable 对象可以恢复这些对象。

Extenalizable接口:

Modifier and TypeMethod and Description
voidpublic readExternal(ObjectInput in)

该对象实现了readExternal方法来恢复其内容,方法是为对象,字符串和数组调用基本类型的DataInput方法和readObject。

voidpublic writeExternal(ObjectOutput out)

该对象实现了writeExternal方法来通过调用DataOutput的原始值或调用ObjectOutput的对象,字符串和数组的writeObject方法来保存其内容

例如:

class LabledPoint implements Externalizable{
	String lable;
	transient int point;
	public LabledPoint(String lable,int x) {
		this.lable=lable;
		point=x;
	}
	public LabledPoint() {
		
	}
	@Override
	public void readExternal(ObjectInput arg0) throws IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		lable=arg0.readUTF();
		point=arg0.readInt();
	}
	@Override
	public void writeExternal(ObjectOutput arg0) throws IOException {
		// TODO Auto-generated method stub
		arg0.writeUTF(lable);
		arg0.writeInt(point);
	}
}
public class Main{
	public static void main(String[] args) throws InterruptedException, ExecutionException, IOException, ClassNotFoundException {
		//写入对象
		ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("employee.txt"));
		LabledPoint point1=new LabledPoint("my",9);
		out.writeObject(point1);
		//读出对象
		ObjectInputStream in=new ObjectInputStream(new FileInputStream("employee.txt"));
		LabledPoint m2=(LabledPoint)in.readObject();
		System.out.println(m2.point);
	}
	
}

 

4.3 父类的序列化与 Transient 关键字

  情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。

  解决:要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就需要有默认的无参的构造函数。 在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取 父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都 是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

  Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

4.4 序列化单例和类型安全的枚举

我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:

大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.EnumvalueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplacereadResolve等方法。

在序列化和反序列时,如果目标对象是唯一的,那么必须加倍小心,这通常会在实现单例和类型安全的枚举时发生。
如果你使用Java 语言的enum结构,那么你就不必担心序列化,它能够正常工作。但是,假设你在维护遗留代码,其中包含下面这样的枚举类型:

public class Orientation{
    public static final Orientation HORIZONTAL = new Orientation(1);
    public static final Orientation VERTICAL = new Orientation(2);
    private int value;
    private Orientation(int v){ value = v;}
}

这种风格在枚举被添加到Java 语言中之前是很普遍的。注意,其构造器是私有的。因此,不可能创建出超出Orientation.HORIZONTAL 和 Orientation.VERTICAL 之外的对象。特别是,你可以使用 == 操作符来测试对象的等同性:
if(orientation == Orientation.HORIZONTAL)...

当类型安全的枚举实现 Serializable 接口时,默认的序列化机制是不适用的。假设我们写出一个Orientation 类型的值,并再次将其读回:

import java.io.*;
public class Orientation implements Serializable{
    public static final Orientation HORIZONTAL = new Orientation(1);
    public static final Orientation VERTICAL = new Orientation(2);
    private int value;
    private Orientation(int v){ value = v;}

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

        Orientation original = Orientation.HORIZONTAL;
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("orientation.dat"));
        out.writeObject(original);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("orientation.dat"));
        Orientation saved = (Orientation)in.readObject();

        System.out.println( Orientation.HORIZONTAL instanceof Orientation);
        System.out.println(saved == Orientation.HORIZONTAL);
        System.out.println(saved.HORIZONTAL == Orientation.HORIZONTAL);
        System.out.println(saved.VERTICAL == Orientation.HORIZONTAL);
        System.out.println( saved instanceof Orientation);
    }
}
/*
运行结果:
true
false
true
false
true
分析:事实上,saved 的值是Orientation 类型的一个全新的对象,它与任何预定义的常量都不等同。
  即使构造器是私有的,序列化机制也可以创建新的对象。
*/

为了解决这个问题,需要定义一种称为readResolve 的特殊序列化方法。如果定义了readResolve 方法,在对象被序列化之后就会调用它。它必须返回一个对象,而该对象之后会成为 readObject 的返回值。在上面的情况中,readResolve 将检查value 域,并返回恰当的枚举常量:

//在上述类中添加如下方法:
protected Object readResolve() throws ObjectStreamException{
        if(value == 1)
            return Orientation.HORIZONTAL;
        if(value == 2)
            return Orientation.VERTICAL;
        return null;
    }
/*
运行结果:
true
true
true
false
true
*/

请记住,向遗留代码中所有类型安全的枚举以及向所有支持单例设计模式的类中添加 readResolve 方法。

4.5 版本管理

   仍然使用旧1.0版本的用户可以读入新版本产生的文件吗?咋一看,好像不可能。无论类的定义产生什么样的变化,它的SHA指纹也会跟着变化,而我们知道对象输入流拒绝读入不同指纹的对象。

   解决方法:获得这个类的早期版本的指纹。类的所有较新的版本都必须和早期版本的序列号相同

   旧文件对象可能比程序中的对象具有更多或更少的数据域,尽力转换:

  •   名字匹配类型不匹配,不兼容不会转换
  •  程序中的对象多了数据域,设为默认值
  •  程序中的对象少了数据域,丢弃多出来的数据域。

4.6 为克隆使用序列化

   序列化有一个很有趣的用法:提供了一种克隆对象的简便途径。只要将对象序列化到输入流中,然后将其读回,这样的新对象是对现有对象的一个深拷贝。不必将对象吸入到文件中,因为可以使用ByteArrayOutPutStream将数据保存到字节数组中。

  虽然这个方法很灵巧,但是通常会比显式构建新对象并复制数据域的克隆方法慢的多。

//程序清单2-4
package serialClone;

/**
 * @version 1.21 13 Jul 2016
 * @author Cay Horstmann
 */

import java.io.*;
import java.util.*;
import java.time.*;

public class SerialCloneTest
{  
   public static void main(String[] args) throws CloneNotSupportedException
   {  
      Employee harry = new Employee("Harry Hacker", 35000, 1989, 10, 1);
      // clone harry
      Employee harry2 = (Employee) harry.clone();

      // mutate harry
      harry.raiseSalary(10);

      // now harry and the clone are different
      System.out.println(harry);
      System.out.println(harry2);
   }
}

/**
 * A class whose clone method uses serialization.
 */
class SerialCloneable implements Cloneable, Serializable
{  
   public Object clone() throws CloneNotSupportedException
   {
      try {
         // save the object to a byte array
         ByteArrayOutputStream bout = new ByteArrayOutputStream();
         try (ObjectOutputStream out = new ObjectOutputStream(bout))
         {
            out.writeObject(this);
         }

         // read a clone of the object from the byte array
         try (InputStream bin = new ByteArrayInputStream(bout.toByteArray()))
         {
            ObjectInputStream in = new ObjectInputStream(bin);
            return in.readObject();
         }
      }
      catch (IOException | ClassNotFoundException e)
      {  
         CloneNotSupportedException e2 = new CloneNotSupportedException();
         e2.initCause(e);
         throw e2;
      }
   }
}

/**
 * The familiar Employee class, redefined to extend the
 * SerialCloneable class. 
 */
class Employee extends SerialCloneable
{  
   private String name;
   private double salary;
   private LocalDate hireDay;

   public Employee(String n, double s, int year, int month, int day)
   {  
      name = n;
      salary = s;
      hireDay = LocalDate.of(year, month, day);
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public LocalDate getHireDay()
   {
      return hireDay;
   }

   /**
      Raises the salary of this employee.
      @byPercent the percentage of the raise
   */
   public void raiseSalary(double byPercent)
   {  
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   public String toString()
   {  
      return getClass().getName()
         + "[name=" + name
         + ",salary=" + salary
         + ",hireDay=" + hireDay
         + "]";
   }
}

5 操作文件

    Path和Files类封装了在用户机器上处理文件系统所需的所有功能。换句话说,输入输出流关心的是文件的内容,而我们在此处要讨论的类关心的是在磁盘上如何存储文件。这两个类实在JAVA SE 7中新添加进来的。

5.1 Path

 Path表示的是一个目录名序列,路径中的第一个部件可以是根部件,例如/或C:\,而允许访问的根部件取决于文件系统。一根部件开始的路径是绝对路径;否则,就是相对路径。

Path absolute=Paths.get("/home","harry")
Path relative=Paths.get("myprog","conf","user.properties");

   静态的Paths.get()方法接收一个或多个字符串,并用默认文件系统的分隔符连接起来。然后解析连接起来的结果,如果不是合法路径,抛出InvalidPathException异常。这个连接起来的结果就是一个Path对象。

  get方法可以获取包含多个部件构成的单个字符串:

Properties props=new Properties();
String basedir=props.getProperty("base.dir");
Path basePath=Paths.get(basedir);

组合或解析域名是司空见惯的操作,p.resolve(q)将按照下列规则返回一个路径:

  • 如果q是绝对路径,返回q
  • 否则,按照文件系统规则,返回“p后面跟着q”作为结果

例:

               String basedir=System.getProperty("user.dir");
		//相对
		Path basePath=Paths.get(basedir);
		Path workRelation=Paths.get("word");
		Path workPath=basePath.resolve(workRelation);
		System.out.println(basePath+"  "+workRelation+"  "+workPath);
		
		//绝对
		Path workAbsolute=Paths.get("C:", "d");
		Path workPath1=basePath.resolve(workAbsolute);
		System.out.println(basePath+"  "+workAbsolute+"  "+workPath1);

运行结果:

D:\workspace\Leecode  word  D:\workspace\Leecode\word
D:\workspace\Leecode  C:\d  C:\d

resolve有一种快捷方式,接收一个字符串而不是路径(就是把路径改成字符串)

resolveSibling 解析指定路径的父路径产生其兄弟路径。例:

	public static void main(String[] args) {
		String basedir=System.getProperty("user.dir");
		//相对
		Path basePath=Paths.get(basedir);
		Path workRelation=Paths.get("word");
		Path workPath=basePath.resolveSibling(workRelation);
		System.out.println(basePath+"  "+workRelation+"  "+workPath);
    }

运行结果:

D:\workspace\Leecode  word  D:\workspace\word

 resolve的对立面是relativize

import java.nio.file.Path;
import java.nio.file.Paths;
 
public class Rename {
	public static void main(String[] args) {
		Path p1 = Paths.get("C:\\Users\\Administrator");
		Path p2 = Paths.get("C:\\False");
		System.out.println(p2.relativize(p1));
		System.out.println(p1.relativize(p2));
		
		Path p3 = Paths.get("D:\\Yes\\Users\\Administrator");
		Path p4 = Paths.get("D:\\Yes\\False");
		
		System.out.println("***************************************");
		System.out.println(p4.relativize(p3));
		System.out.println(p3.relativize(p4));
		
		System.out.println("***************************************");
		System.out.println(p2.relativize(p1).equals(p4.relativize(p3)));
		
		System.out.println("***************************************");
		System.out.println(p2.relativize(p1).toAbsolutePath().normalize());
		System.out.println(p1.relativize(p2).toAbsolutePath().normalize());
	}
}

运行结果:

..\Users\Administrator
..\..\False
***************************************
..\Users\Administrator
..\..\False
***************************************
true
***************************************
D:\workspace\Users\Administrator
D:\False

可以从Path对象中构建出Scanner对象:

Scanner in =new Scanner(Paths.get("/home/fred/input.txt"));

注意:偶尔与遗留系统的API交互,使用的是File而不是Path接口。File接口以一个toPath方法,Path接口有一个toFile方法。

java.nio.file.Paths API

    • Modifier and TypeMethod and Description
      static Pathget(String first, String... more)

      将路径字符串或连接到路径字符串的字符串序列转换为 Path

      static Pathget(URI uri)

      将给定的URI转换为Path对象。

java.nio.file.Path API

java.io.File API:

  •  

    • Constructor and Description
      File(File parent, String child)

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

      File(String pathname)

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

      File(String parent, String child)

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

      File(URI uri)

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

       

      • Modifier and TypeMethod and Description
        booleancanExecute()

        测试应用程序是否可以执行此抽象路径名表示的文件。

        booleancanRead()

        测试应用程序是否可以读取由此抽象路径名表示的文件。

        booleancanWrite()

        测试应用程序是否可以修改由此抽象路径名表示的文件。

        intcompareTo(File pathname)

        比较两个抽象的路径名字典。

        booleancreateNewFile()

        当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。

        static FilecreateTempFile(String prefix, String suffix)

        在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称。

        static FilecreateTempFile(String prefix, String suffix, File directory)

        在指定的目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。

        booleandelete()

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

        voiddeleteOnExit()

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

        booleanequals(Object obj)

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

        booleanexists()

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

        FilegetAbsoluteFile()

        返回此抽象路径名的绝对形式。

        StringgetAbsolutePath()

        返回此抽象路径名的绝对路径名字符串。

        FilegetCanonicalFile()

        返回此抽象路径名的规范形式。

        StringgetCanonicalPath()

        返回此抽象路径名的规范路径名字符串。

        longgetFreeSpace()

        返回分区未分配的字节数 named此抽象路径名。

        StringgetName()

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

        StringgetParent()

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

        FilegetParentFile()

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

        StringgetPath()

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

        longgetTotalSpace()

        通过此抽象路径名返回分区 named的大小。

        longgetUsableSpace()

        返回上的分区提供给该虚拟机的字节数 named此抽象路径名。

        inthashCode()

        计算此抽象路径名的哈希码。

        booleanisAbsolute()

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

        booleanisDirectory()

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

        booleanisFile()

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

        booleanisHidden()

        测试此抽象路径名命名的文件是否为隐藏文件。

        longlastModified()

        返回此抽象路径名表示的文件上次修改的时间。

        longlength()

        返回由此抽象路径名表示的文件的长度。

        String[]list()

        返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。

        String[]list(FilenameFilter filter)

        返回一个字符串数组,命名由此抽象路径名表示的目录中满足指定过滤器的文件和目录。

        File[]listFiles()

        返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。

        File[]listFiles(FileFilter filter)

        返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。

        File[]listFiles(FilenameFilter filter)

        返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。

        static File[]listRoots()

        列出可用的文件系统根。

        booleanmkdir()

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

        booleanmkdirs()

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

        booleanrenameTo(File dest)

        重命名由此抽象路径名表示的文件。

        booleansetExecutable(boolean executable)

        为此抽象路径名设置所有者的执行权限的便利方法。

        booleansetExecutable(boolean executable, boolean ownerOnly)

        设置该抽象路径名的所有者或每个人的执行权限。

        booleansetLastModified(long time)

        设置由此抽象路径名命名的文件或目录的最后修改时间。

        booleansetReadable(boolean readable)

        一种方便的方法来设置所有者对此抽象路径名的读取权限。

        booleansetReadable(boolean readable, boolean ownerOnly)

        设置此抽象路径名的所有者或每个人的读取权限。

        booleansetReadOnly()

        标记由此抽象路径名命名的文件或目录,以便只允许读取操作。

        booleansetWritable(boolean writable)

        一种方便的方法来设置所有者对此抽象路径名的写入权限。

        booleansetWritable(boolean writable, boolean ownerOnly)

        设置此抽象路径名的所有者或每个人的写入权限。

        PathtoPath()

        返回从此抽象路径构造的java.nio.file.Path对象。

        StringtoString()

        返回此抽象路径名的路径名字符串。

        URItoURI()

        构造一个表示此抽象路径名的 file: URI

java.nio.file.Files API:

5.2 读写文件

Files类可以使得普通文件操作更加快捷

	public static void main(String[] args) throws IOException   {
		Path p=Paths.get(System.getProperty("user.dir"),"t");
		String want="Hello World";
		String want1="I love you";
		Files.write(p,want.getBytes(),StandardOpenOption.APPEND);
		Files.write(p,want1.getBytes(),StandardOpenOption.APPEND);  //追加内容

		byte[] result=Files.readAllBytes(p);
		System.out.println(new String(result));
		
		List<String> lines=Files.readAllLines(p);
		for(String s:lines)
			System.out.println(s);
		
    }

这些简便方法适用于处理中等长度的文本文件。如果处理的文件长度较大,或者是二进制文件,还是应该使用熟知的输入/输出流或读入/写出器。

		InputStream in=Files.newInputStream(p);
		OutputStream out=Files.newOutputStream(p);
		Reader rin=Files.newBufferedReader(p);
		Writer wout=Files.newBufferedWriter(p);

5.3 创建文件和目录

	public static void main(String[] args) t	public static void main(String[] args) throws IOException   {
		//Path p=Paths.get(System.getProperty("user.dir"),"g"); //  D:\workspace\Leecode\g
		//Files.createDirectory(p);  //在D:\workspace\Leecode(这个必须是存在的,否则抛异常)下创建了文件夹g
		Path p1=Paths.get(System.getProperty("user.dir"),"j","g");
		Files.createDirectories(p1);//创建一系列的文件夹     在D:\workspace\Leecode下创建了文件夹j,j下创建了g
		//创建文件x.txt,如果已经存在,抛异常。检查文件是否存在和创建文件是原子性的。
		Files.createFile(Paths.get(System.getProperty("user.dir"),"x.txt"));
		
    }

创建临时文件见API,在上面

5.4 复制、移动和删除文件

将文件从一个位置复制到另一个位置:

	public static void main(String[] args) throws IOException   {
		Path fromPath=Paths.get("D:\\workspace\\Leecode\\1.txt");
		Path toPath=Paths.get("D:\\workspace\\Leecode\\2.txt");
		//生成一个新的文件 2.txt
		Files.copy(fromPath,toPath);
    }

移动文件:

	public static void main(String[] args) throws IOException   {
		Path fromPath=Paths.get("D:\\workspace\\Leecode\\1.txt");
		Path toPath=Paths.get("D:\\workspace\\Leecode\\2.txt");
		//生成一个新的文件 2.txt,原来的文件消失了
		Files.move(fromPath,toPath);
    }

如果目标路径已存在,复制或移动将失败。如果想要覆盖目标路径,使用StantardCopyOption的一些值可以设置拷贝时的属性。

可以将一个输入流复制到Path中,表示想要将输入流存储到硬盘上。类似,可以将一个Path复制到输入流:

	public static void main(String[] args) throws IOException   {
		Path toPath=Paths.get("D:\\workspace\\Leecode\\1.txt");
		InputStream inputStream=new FileInputStream("D:\\workspace\\Leecode\\2.txt");
		//新建了一个 D:\\workspace\\Leecode\\1.txt 文件
		Files.copy(inputStream,toPath);
		
		Path fromPath=Paths.get("D:\\workspace\\Leecode\\1.txt");
		OutputStream outputStream=new FileOutputStream("D:\\workspace\\Leecode\\3.txt");
		//新建了一个 D:\\workspace\\Leecode\\3.txt 文件
		Files.copy(fromPath,outputStream);
    }

删除文件:

	public static void main(String[] args) throws IOException   {
		
		Path fromPath=Paths.get("D:\\workspace\\Leecode\\1.txt");
		Files.delete(fromPath);
    }

如果要删除的文件不存在,抛出异常。因此,可以用如下方法

	public static void main(String[] args) throws IOException   {
		
		Path fromPath=Paths.get("D:\\workspace\\Leecode\\1.txt");
		boolean b=Files.deleteIfExists(fromPath);
    }

用于文件操作的标准选项:

java.nio.file    Enum StandardOpenOption   :与newBufferedWriter,newInputStream,new OutputStream,write一起使用

    • Enum Constant and Description
      APPEND

      如果文件打开 WRITE访问,则字节将被写入文件的末尾而不是开头。

      CREATE

      创建一个新文件(如果不存在)。

      CREATE_NEW

      创建一个新的文件,如果该文件已经存在失败。

      DELETE_ON_CLOSE

      关闭时删除。

      DSYNC

      要求将文件内容的每次更新都与底层存储设备同步写入。

      READ

      打开阅读权限。

      SPARSE

      稀疏文件

      SYNC

      要求将文件内容或元数据的每次更新都同步写入底层存储设备。

      TRUNCATE_EXISTING

      如果文件已经存在,并且打开 WRITE访问,则其长度将截断为0。

      WRITE

      打开以进行写入。

java.nio.file    Enum StandardCopyOption   :与copy、move一起使用

java.nio.file  Enum LinkOption :与上面所有方法以及exists,isDirectory,isRegularFile一起使用

    • Enum Constant and Description
      NOFOLLOW_LINKS

      不要跟随符号链接。

java.nio.file  Enum FileVisitOption :与find,walk,walkFileTree一起使用

    • Enum Constant and Description
      FOLLOW_LINKS

      遵循符号链接。

符号链接:

符号链接软链接)是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用。[1] 符号链接最早在4.2BSD版本中出现(1983年)。今天POSIX操作系统标准、大多数类Unix系统Windows VistaWindows 7都支持符号链接。Windows 2000Windows XP在某种程度上也支持符号链接。

符号链接的操作是透明的:对符号链接文件进行读写的程序会表现得直接对目标文件进行操作。某些需要特别处理符号链接的程序(如备份程序)可能会识别并直接对其进行操作。

一个符号链接文件仅包含有一个文本字符串,其被操作系统解释为一条指向另一个文件或者目录的路径。它是一个独立文件,其存在并不依赖于目标文件。如果删除一个符号链接,它指向的目标文件不受影响。如果目标文件被移动、重命名或者删除,任何指向它的符号链接仍然存在,但是它们将会指向一个不复存在的文件。这种情况被有时被称为被遗弃

5.5 获取文件信息

部分见前面Files API:

 所有的文件系统有一个基本属性集,被封装在BasicFileAttribute接口。要获取这些属性,调用:

BasicFileAttributes attributes=Files.readAttributes(fromPath,BasicFileAttributes.class);

如果了解用户的文件系统兼容POSIX,可以获取一个PosixFileAttributes实例:

PosixFileAttributes attributes=Files.readAttributes(fromPath,PosixFileAttributes.class);

java.nio.file.attribute  Interface BasicFileAttributes API:

    • Modifier and TypeMethod and Description
      FileTimecreationTime()

      返回创建时间。

      ObjectfileKey()

      返回唯一地标识给定的文件或对象 null如果文件密钥不可用。

      booleanisDirectory()

      告诉文件是否是目录。

      booleanisOther()

      告诉该文件是否是常规文件,目录或符号链接以外的其他内容。

      booleanisRegularFile()

      告知文件是否是具有不透明内容的常规文件。

      booleanisSymbolicLink()

      告诉文件是否是符号链接。

      FileTimelastAccessTime()

      返回上一次访问的时间。

      FileTimelastModifiedTime()

      返回上次修改的时间。

      longsize()

      返回文件的大小(以字节为单位)。

5.6 访问目录中的项(涉及流的内容,later)

  Files.list方法(不会进入子目录,为了处理子目录,需要walk方法)会返回一个可以读取目录中各个项的Stream<Path>对象。目录是惰性读取的,这使得处理具有大量项的目录可以更高效。

  有一个带参的walk方法可以限制想要访问的树深度

  因为读取目录设计需要关闭的资源,应该使用try块:

	public static void main(String[] args) throws IOException   {
		
		Path fromPath=Paths.get("D:\\workspace");
		try(Stream<Path> entries=Files.list(fromPath)){
			
		}
    }

如果要过来walk返回的路径,而且过滤的内容设计与目录存储相关的属性,如尺寸、创建时间和类型,那么应该使用find方法替代walk方法。

无法容易的使用walk方法来删除树。

5.7 使用目录流(涉及流的内容,later)

5.8 ZIP文件系统

java.nio.file.FileSystems:

java.nio.file.FileSystem:

6 内存映射文件

 大多数操作系统都可以利用虚拟内存实现来将一个文件或者文件的一部分“映射”到内存中。然后,这个文件就可以当作是内存数组一样的访问,这比传统的文件操作要快得多。

  内存映射文件,是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

6.1 内存映射文件的性能

  对于中等尺寸文件的顺序读入则没有必要使用内存映射。

  java.nio包使内存映射变得非常简单,下面就是我们需要做的:

首先,从文件中获得一个通道,通道是用于磁盘文件的一种抽象,它使我们可以访问诸如内存映射,文件加锁机制及文件间快速传递数据等操作系统特性。

FileChannel channel=new FileChannel.open(path,options);

然后,通过调用FileChannel类的map方法从这个通道中获得一个ByteBuffer.你可以指定想要映射的文件区域和映射模式,支持的模式有3种:

一旦有了缓冲区,就可以使用ByteBuffer和Buffer超类的方法读写数据了。

缓冲区支持顺序和随机数据访问,有一个可以通过get和put方法来移动的位置。例如,可以这样顺序遍历缓冲区中的所有字节:

	public static void main(String[] args) throws InterruptedException,ExecutionException,IOException,ClassNotFoundException{
		FileChannel channel=FileChannel.open(Paths.get("D:\\workspace\\Leecode\\2.txt"));
		BasicFileAttributes attributes=Files.readAttributes(Paths.get("D:\\workspace\\Leecode\\2.txt"),BasicFileAttributes.class);
		MappedByteBuffer buffer=channel.map(FileChannel.MapMode.READ_ONLY,0,attributes.size());
		byte[] resource=new byte[(int)attributes.size()];
		int i=0;
		//顺序访问所有字节
		while(buffer.hasRemaining()){
			resource[i++]=buffer.get();
		}
		//随机访问
		byte[] resources=new byte[(int)attributes.size()*5];
		int k=0;
		for(int j=0;j<buffer.limit();j++) {
			resources[k++]=buffer.get(j);
		}
		
		//可以用下面的方法来读写数组
		byte[] resource1=new byte[(int)attributes.size()];
		buffer.get(resource1);
		System.out.println(new String(resource1));
		//最后,还有下面的方法
		//getInt  getLong getShort getChar getFloat getDouble用来读入文件中存储为二进制的基本类型值。
		
	}

正如我们提到的,Java对二进制数据使用高位在前的排序机制,但是,如果需要以低位在前的排序方式处理包含二进制数字的文件,那么,只需要调用:

buffer.order(ByteOrder.LITTLE_ENDIAN);

要查询缓冲区内当前的字节顺序,可以调用:

ByteOrder b=buffer.order();

要向缓冲区写数字,可以使用下列的方法:

putInt
putLong
putShort
putChar
putFloat
putDouble

可以使用下列的循环来计算一个字节序列的检验和:

CRC32 crc=new CRC32();
while(more bytes)
  crc.update(next byte);
long checkSum=crc.getValue();

 

package memoryMap;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.zip.*;

/**
 * This program computes the CRC checksum of a file in four ways. <br>
 * Usage: java memoryMap.MemoryMapTest filename
 * @version 1.01 2012-05-30
 * @author Cay Horstmann
 */
public class MemoryMapTest
{
   public static long checksumInputStream(Path filename) throws IOException
   {
      try (InputStream in = Files.newInputStream(filename))
      {
         CRC32 crc = new CRC32();
   
         int c;
         while ((c = in.read()) != -1)
            crc.update(c);
         return crc.getValue();
      }
   }

   public static long checksumBufferedInputStream(Path filename) throws IOException
   {
      try (InputStream in = new BufferedInputStream(Files.newInputStream(filename)))
      {
         CRC32 crc = new CRC32();
   
         int c;
         while ((c = in.read()) != -1)
            crc.update(c);
         return crc.getValue();
      }
   }

   public static long checksumRandomAccessFile(Path filename) throws IOException
   {
      try (RandomAccessFile file = new RandomAccessFile(filename.toFile(), "r"))
      {
         long length = file.length();
         CRC32 crc = new CRC32();
   
         for (long p = 0; p < length; p++)
         {
            file.seek(p);
            int c = file.readByte();
            crc.update(c);
         }
         return crc.getValue();
      }
   }

   public static long checksumMappedFile(Path filename) throws IOException
   {
      try (FileChannel channel = FileChannel.open(filename))
      {
         CRC32 crc = new CRC32();
         int length = (int) channel.size();
         MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, length);
   
         for (int p = 0; p < length; p++)
         {
            int c = buffer.get(p);
            crc.update(c);
         }
         return crc.getValue();
      }
   }

   public static void main(String[] args) throws IOException
   {
      System.out.println("Input Stream:");
      long start = System.currentTimeMillis();
      Path filename = Paths.get(args[0]);
      long crcValue = checksumInputStream(filename);
      long end = System.currentTimeMillis();
      System.out.println(Long.toHexString(crcValue));
      System.out.println((end - start) + " milliseconds");

      System.out.println("Buffered Input Stream:");
      start = System.currentTimeMillis();
      crcValue = checksumBufferedInputStream(filename);
      end = System.currentTimeMillis();
      System.out.println(Long.toHexString(crcValue));
      System.out.println((end - start) + " milliseconds");

      System.out.println("Random Access File:");
      start = System.currentTimeMillis();
      crcValue = checksumRandomAccessFile(filename);
      end = System.currentTimeMillis();
      System.out.println(Long.toHexString(crcValue));
      System.out.println((end - start) + " milliseconds");

      System.out.println("Mapped File:");
      start = System.currentTimeMillis();
      crcValue = checksumMappedFile(filename);
      end = System.currentTimeMillis();
      System.out.println(Long.toHexString(crcValue));
      System.out.println((end - start) + " milliseconds");
   }
}

java.nio.channels.FileChannel:

    • Modifier and TypeMethod and Description
      abstract voidforce(boolean metaData)

      强制将此通道文件的任何更新写入包含该通道的存储设备。

      FileLocklock()

      获取此通道文件的排他锁。

      abstract FileLocklock(long position, long size, boolean shared)

      获取此通道文件的给定区域的锁定。

      abstract MappedByteBuffermap(FileChannel.MapMode mode, long position, long size)

      将此频道文件的区域直接映射到内存中。

      static FileChannelopen(Path path, OpenOption... options)

      打开或创建文件,返回文件通道以访问该文件。

      static FileChannelopen(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)

      打开或创建文件,返回文件通道以访问该文件。

      abstract longposition()

      返回此频道的文件位置。

      abstract FileChannelposition(long newPosition)

      设置此通道的文件位置。

      abstract intread(ByteBuffer dst)

      从该通道读取到给定缓冲区的字节序列。

      longread(ByteBuffer[] dsts)

      从该通道读取到给定缓冲区的字节序列。

      abstract longread(ByteBuffer[] dsts, int offset, int length)

      从该通道读取字节序列到给定缓冲区的子序列中。

      abstract intread(ByteBuffer dst, long position)

      从给定的文件位置开始,从该通道读取一个字节序列到给定的缓冲区。

      abstract longsize()

      返回此通道文件的当前大小。

      abstract longtransferFrom(ReadableByteChannel src, long position, long count)

      从给定的可读字节通道将字节传输到该通道的文件中。

      abstract longtransferTo(long position, long count, WritableByteChannel target)

      将该通道文件的字节传输到给定的可写字节通道。

      abstract FileChanneltruncate(long size)

      将此频道的文件截断为给定大小。

      FileLocktryLock()

      尝试获取此频道文件的排他锁。

      abstract FileLocktryLock(long position, long size, boolean shared)

      尝试获取此通道文件的给定区域的锁定。

      abstract intwrite(ByteBuffer src)

      从给定的缓冲区向该通道写入一个字节序列。

      longwrite(ByteBuffer[] srcs)

      从给定的缓冲区向该通道写入一系列字节。

      abstract longwrite(ByteBuffer[] srcs, int offset, int length)

      从给定缓冲区的子序列将一个字节序列写入该通道。

      abstract intwrite(ByteBuffer src, long position)

      从给定的缓冲区向给定的文件位置开始,向该通道写入一个字节序列。

java.nio.Buffer:

    • Modifier and TypeMethod and Description
      abstract Objectarray()

      返回支持此缓冲区的数组 (可选操作)

      abstract intarrayOffset()

      返回该缓冲区的缓冲区的第一个元素的背衬数组中的偏移量 (可选操作)

      intcapacity()

      返回此缓冲区的容量。

      Bufferclear()

      清除此缓冲区。

      Bufferflip()

      翻转这个缓冲区。

      abstract booleanhasArray()

      告诉这个缓冲区是否由可访问的数组支持。

      booleanhasRemaining()

      告诉当前位置和极限之间是否存在任何元素。

      abstract booleanisDirect()

      告诉这个缓冲区是否为 direct

      abstract booleanisReadOnly()

      告知这个缓冲区是否是只读的。

      intlimit()

      返回此缓冲区的限制。

      Bufferlimit(int newLimit)

      设置此缓冲区的限制。

      Buffermark()

      将此缓冲区的标记设置在其位置。

      intposition()

      返回此缓冲区的位置。

      Bufferposition(int newPosition)

      设置这个缓冲区的位置。

      intremaining()

      返回当前位置和限制之间的元素数。

      Bufferreset()

      将此缓冲区的位置重置为先前标记的位置。

      Bufferrewind()

      倒带这个缓冲区。

java.nio.ByteBuffer:

    • Modifier and TypeMethod and Description
      static ByteBufferallocate(int capacity)

      分配一个新的字节缓冲区。

      static ByteBufferallocateDirect(int capacity)

      分配一个新的直接字节缓冲区。

      byte[]array()

      返回支持此缓冲区的字节数组 (可选操作)

      intarrayOffset()

      返回该缓冲区的缓冲区的第一个元素的背衬数组中的偏移量 (可选操作)

      abstract CharBufferasCharBuffer()

      创建一个字节缓冲区作为char缓冲区的视图。

      abstract DoubleBufferasDoubleBuffer()

      将此字节缓冲区的视图创建为双缓冲区。

      abstract FloatBufferasFloatBuffer()

      将此字节缓冲区的视图创建为浮动缓冲区。

      abstract IntBufferasIntBuffer()

      将此字节缓冲区的视图创建为int缓冲区。

      abstract LongBufferasLongBuffer()

      将此字节缓冲区的视图创建为长缓冲区。

      abstract ByteBufferasReadOnlyBuffer()

      创建一个新的只读字节缓冲区,共享此缓冲区的内容。

      abstract ShortBufferasShortBuffer()

      将此字节缓冲区的视图创建为短缓冲区。

      abstract ByteBuffercompact()

      压缩此缓冲区 (可选操作)

      intcompareTo(ByteBuffer that)

      将此缓冲区与另一个缓冲区进行比较。

      abstract ByteBufferduplicate()

      创建一个新的字节缓冲区,共享此缓冲区的内容。

      booleanequals(Object ob)

      告诉这个缓冲区是否等于另一个对象。

      abstract byteget()

      相对 获取方法。

      ByteBufferget(byte[] dst)

      相对批量 获取方法。

      ByteBufferget(byte[] dst, int offset, int length)

      相对批量 获取方法。

      abstract byteget(int index)

      绝对 获取方法。

      abstract chargetChar()

      读取char值的相对 get方法。

      abstract chargetChar(int index)

      绝对 获取方法来读取一个char值。

      abstract doublegetDouble()

      读取双重值的相对 get方法。

      abstract doublegetDouble(int index)

      绝对 获取读取双重值的方法。

      abstract floatgetFloat()

      读取浮点值的相对 get方法。

      abstract floatgetFloat(int index)

      用于读取浮点值的绝对 get方法。

      abstract intgetInt()

      用于读取int值的相对 get方法。

      abstract intgetInt(int index)

      用于读取int值的绝对 get方法。

      abstract longgetLong()

      读取长值的相对 get方法。

      abstract longgetLong(int index)

      绝对 获取读取长值的方法。

      abstract shortgetShort()

      相对 获取方法读取一个简短的值。

      abstract shortgetShort(int index)

      绝对 获取读取一个简短值的方法。

      booleanhasArray()

      告诉这个缓冲区是否由可访问的字节数组支持。

      inthashCode()

      返回此缓冲区的当前哈希码。

      abstract booleanisDirect()

      告诉这个字节缓冲区是否是直接的。

      ByteOrderorder()

      检索此缓冲区的字节顺序。

      ByteBufferorder(ByteOrder bo)

      修改缓冲区的字节顺序。

      abstract ByteBufferput(byte b)

      相对 放置(可选操作)

      ByteBufferput(byte[] src)

      相对大容量 put方法 (可选操作)

      ByteBufferput(byte[] src, int offset, int length)

      相对大容量 put方法 (可选操作)

      ByteBufferput(ByteBuffer src)

      相对大容量 put方法 (可选操作)

      abstract ByteBufferput(int index, byte b)

      绝对 put方法 (可选操作)

      abstract ByteBufferputChar(char value)

      写入char值的相对 put方法 (可选操作)

      abstract ByteBufferputChar(int index, char value)

      用于写入char值的绝对 put方法 (可选操作)

      abstract ByteBufferputDouble(double value)

      写入double值的相对 put方法 (可选操作)

      abstract ByteBufferputDouble(int index, double value)

      用于写入双精度值的绝对 put方法 (可选操作)

      abstract ByteBufferputFloat(float value)

      编写浮点值的相对 put方法 (可选操作)

      abstract ByteBufferputFloat(int index, float value)

      用于写入浮点值的绝对 put方法 (可选操作)

      abstract ByteBufferputInt(int value)

      编写int值的相对 put方法 (可选操作)

      abstract ByteBufferputInt(int index, int value)

      用于写入int值的绝对 put方法 (可选操作)

      abstract ByteBufferputLong(int index, long value)

      绝对 put方法写入一个长的值 (可选操作)

      abstract ByteBufferputLong(long value)

      写入长值的相对 put方法 (可选操作)

      abstract ByteBufferputShort(int index, short value)

      绝对 put方法写入一个简短的值 (可选操作)

      abstract ByteBufferputShort(short value)

      写入一个短值的相对 放置方法 (可选操作)

      abstract ByteBufferslice()

      创建一个新的字节缓冲区,其内容是此缓冲区内容的共享子序列。

      StringtoString()

      返回一个汇总此缓冲区状态的字符串。

      static ByteBufferwrap(byte[] array)

      将一个字节数组包装到缓冲区中。

      static ByteBufferwrap(byte[] array, int offset, int length)

      将一个字节数组包装到缓冲区中。

java.nio.CharBuffer:

    • Modifier and TypeMethod and Description
      static CharBufferallocate(int capacity)

      分配一个新的char缓冲区。

      CharBufferappend(char c)

      将指定的字符追加到此缓冲区 (可选操作)

      CharBufferappend(CharSequence csq)

      将指定的字符序列追加到此缓冲区 (可选操作)

      CharBufferappend(CharSequence csq, int start, int end)

      将指定字符序列的子序列附加到此缓冲区 (可选操作)

      char[]array()

      返回支持此缓冲区的char数组 (可选操作)

      intarrayOffset()

      返回该缓冲区的缓冲区的第一个元素的背衬数组中的偏移量 (可选操作)

      abstract CharBufferasReadOnlyBuffer()

      创建一个新的只读char缓冲区,共享此缓冲区的内容。

      charcharAt(int index)

      以相对于当前位置的给定索引读取字符。

      IntStreamchars()

      返回 int的流,从该序列零扩展 char值。

      abstract CharBuffercompact()

      压缩此缓冲区 (可选操作)

      intcompareTo(CharBuffer that)

      将此缓冲区与另一个缓冲区进行比较。

      abstract CharBufferduplicate()

      创建一个新的char缓冲区,共享此缓冲区的内容。

      booleanequals(Object ob)

      告诉这个缓冲区是否等于另一个对象。

      abstract charget()

      相对 获取方法。

      CharBufferget(char[] dst)

      相对批量 获取方法。

      CharBufferget(char[] dst, int offset, int length)

      相对批量 获取方法。

      abstract charget(int index)

      绝对 获取方法。

      booleanhasArray()

      告诉这个缓冲区是否由可访问的字符数组支持。

      inthashCode()

      返回此缓冲区的当前哈希码。

      abstract booleanisDirect()

      告诉这个char缓冲区是否直接。

      intlength()

      返回此字符缓冲区的长度。

      abstract ByteOrderorder()

      检索此缓冲区的字节顺序。

      abstract CharBufferput(char c)

      相对 放置(可选操作)

      CharBufferput(char[] src)

      相对大容量 put方法 (可选操作)

      CharBufferput(char[] src, int offset, int length)

      相对大容量 put方法 (可选操作)

      CharBufferput(CharBuffer src)

      相对大容量 put方法 (可选操作)

      abstract CharBufferput(int index, char c)

      绝对 put方法 (可选操作)

      CharBufferput(String src)

      相对大容量 put方法 (可选操作)

      CharBufferput(String src, int start, int end)

      相对大容量 put方法 (可选操作)

      intread(CharBuffer target)

      尝试将字符读入指定的字符缓冲区。

      abstract CharBufferslice()

      创建一个新的char缓冲区,其内容是此缓冲区内容的共享子序列。

      abstract CharBuffersubSequence(int start, int end)

      创建一个新的字符缓冲区,代表该缓冲区相对于当前位置的指定子序列。

      StringtoString()

      返回一个包含此缓冲区中字符的字符串。

      static CharBufferwrap(char[] array)

      将一个char数组包装到缓冲区中。

      static CharBufferwrap(char[] array, int offset, int length)

      将一个char数组包装到缓冲区中。

      static CharBufferwrap(CharSequence csq)

      将字符序列包装到缓冲区中。

      static CharBufferwrap(CharSequence csq, int start, int end)

      将字符序列包装到缓冲区中。

6.2 缓冲区数据结构

  缓冲区是具有相同类型的数值组成的数组,Buffer类是一个抽象类,它有众多的具体子类,包括ByteBuffer、CharBuffer、DoubleBuffer、IntBuffer, LongBuffer和ShortBuffer.

 StringBuffer和这些缓冲区没有关系

在实践中,最常用的是ByteBuffer、CharBuffer.如图所示:

6.3 文件加锁机制

  考虑一下多个同时执行的程序需要修改同一个文件的情形,很明显,这些程序需要以某种方式进行通信,不然这个文件很容易被损坏。文件锁可以解决这个问题,他可以控制对文件或文件中某个范围的字节的访问。

java.nio.channels.FileLock:

    • ModifierConstructor and Description
      protected FileLock(AsynchronousFileChannel channel, long position, long size, boolean shared)

      初始化此类的新实例。

      protected FileLock(FileChannel channel, long position, long size, boolean shared)

      初始化此类的新实例。

    • Modifier and TypeMethod and Description
      ChannelacquiredBy()

      返回获取此锁的文件的通道。

      FileChannelchannel()

      返回获取此锁的文件通道。

      voidclose()

      此方法调用 release()方法。

      booleanisShared()

      告诉这个锁是否共享。

      abstract booleanisValid()

      告诉这个锁是否有效。

      booleanoverlaps(long position, long size)

      告诉这个锁是否与给定的锁定范围重叠。

      longposition()

      返回锁定区域的第一个字节的文件中的位置。

      abstract voidrelease()

      释放这个锁

      longsize()

      以字节为单位返回锁定区域的大小。

      StringtoString()

      返回描述此锁的范围,类型和有效性的字符串。

7 正则表达式

  正则表达式用于指定字符串的模式

  对于大多数情况,一部分很直观的语法结构就足够用了:

  • 字符类是一个括在括号中的可选择的字符集,例如,[Jj],[0-9],[a-zA-Z]或[^0-9].这里“-”表示的是一个范围(所有Unicode值落在两个边界范围之内的字符)。而^表示补集。
  • 如果字符类中包含“-”,那么他必须是第一项或者最后意向;如果要包含“【”,那么它必须是第一项;如果要包含"^",那么它可以是除开始位置之外的任何位置。其中,你只需要转义“["和”\".
  • 有很多预定义的字符类,查看下表:

 正则表达式的简单用法就是测试某个特定的字符串是否与它匹配。

首先用表示正则表达式的字符串构建一个Pattern对象。然后从这个模式中获得一个Matcher,并调用它的matches方法。

	public static void main(String[] args) throws IOException   {
		String petternString="[0-9a-z]*";
		Pattern pattern=Pattern.compile(petternString);
		String input="9a8c";
		//这个input可以是任何实现了CharSequance接口的对象,例如String
		//StringBuilder和CharBuffer
		Matcher matcher=pattern.matcher(input);
		if(matcher.matches()) {
			System.out.println("ok");
		}
    }

在编译这个模式时,可以设置一个或多个标志:

patternString="[0-9]";
Pattern pattern=Pattern.compile(patternString,Pattern.CASE_INSENSITIVE+Pattern.UNICODE_CASE);

或者可以在模式中指定它们:

String regex="?iU:expression";

 下面是各个标志:

如果正则表达式中包含群组,那么Matcher对象可以揭露群组的边界。

//产生指定的群组的开始索引和结束之后的索引
int start(int groupIndex);
int end(int groupIndex);

//可以调用一下方法抽取匹配的字符串
String group(int groupIndex);

//对于具名的群组,可以使用以下方法

int start(String groupName);
int end(String groupName);
String group(String groupName);

群组0是整个输入,而用于第一个实际群组的索引是1.嵌套群组是按照前括号排序的,例如:

程序清单2-6提示输入一个模式,然后提示输入用于匹配的字符串,然后打印输入是否与模式相匹配。如果输入匹配模式,并且模式包含群组,那么这个程序将用括号打印出系统边界,例如:((11):(59))am

ypackage regex;

import java.util.*;
import java.util.regex.*;

/**
 * This program tests regular expression matching. Enter a pattern and strings to match, 
 * or hit Cancel to exit. If the pattern contains groups, the group boundaries are displayed 
 * in the match.
 * @version 1.02 2012-06-02
 * @author Cay Horstmann
 */
public class RegexTest
{
   public static void main(String[] args) throws PatternSyntaxException
   {
      Scanner in = new Scanner(System.in);
      System.out.println("Enter pattern: ");
      String patternString = in.nextLine();

      Pattern pattern = Pattern.compile(patternString);
      
      while (true)
      {
         System.out.println("Enter string to match: ");
         String input = in.nextLine();        
         if (input == null || input.equals("")) return;
         Matcher matcher = pattern.matcher(input);
         if (matcher.matches())
         {
            System.out.println("Match");
            int g = matcher.groupCount();
            if (g > 0)
            {
               for (int i = 0; i < input.length(); i++)
               {
                  // Print any empty groups
                  for (int j = 1; j <= g; j++)
                     if (i == matcher.start(j) && i == matcher.end(j)) 
                        System.out.print("()");        
                  // Print ( for non-empty groups starting here
                  for (int j = 1; j <= g; j++)
                     if (i == matcher.start(j) && i != matcher.end(j)) 
                        System.out.print('(');
                  System.out.print(input.charAt(i));
                  // Print ) for non-empty groups ending here
                  for (int j = 1; j <= g; j++)
                     if (i + 1 != matcher.start(j) && i + 1 == matcher.end(j)) 
                        System.out.print(')');
               }
               System.out.println();
            }
         }
         else
            System.out.println("No match");
      }
   }
}

通常,你不希望用正则表达式来匹配全部输入,而只是想找出输入中一个或多个匹配的子字符串。这时可以使用Matcher类的find方法来查找匹配内容,如果返回true,再使用start和end方法来查找匹配的内容,或使用不带引元的group方法来获取匹配的字符串。

	public static void main(String[] args) throws IOException   {
		String petternString="[0-9a-z]*";
		Pattern pattern=Pattern.compile(petternString);
		String input="A9a8cA";
		//这个input可以是任何实现了CharSequance接口的对象,例如String
		//StringBuilder和CharBuffer
		Matcher matcher=pattern.matcher(input);
		while(matcher.find()) {
			int start=matcher.start();
			int end=matcher.end();
			String match=matcher.group();
			System.out.println(start+" "+end+" "+match);
		}
    }
//运行结果
0 0 
1 5 9a8c
5 5 
6 6 

程序清单2-7对这种机制进行了应用,它定位一个Web页面上的所有超文本引用,并打印他们



import java.io.*;
import java.net.*;
import java.nio.charset.*;
import java.util.regex.*;

/**
 * This program displays all URLs in a web page by matching a regular expression that describes the
 * <a href=...> HTML tag. Start the program as <br>
 * java match.HrefMatch URL
 * @version 1.02 2016-07-14
 * @author Cay Horstmann
 */
public class HrefMatch
{
   public static void main(String[] args)
   {
      try
      {
         // get URL string from command line or use default
         String urlString;
         if (args.length > 0) urlString = args[0];
         else urlString = "http://java.sun.com";

         // open reader for URL
         InputStreamReader in = new InputStreamReader(new URL(urlString).openStream(),
               StandardCharsets.UTF_8);

         // read contents into string builder
         StringBuilder input = new StringBuilder();
         int ch;
         while ((ch = in.read()) != -1)
            input.append((char) ch);

         // search for all occurrences of pattern
         String patternString = "<a\\s+href\\s*=\\s*(\"[^\"]*\"|[^\\s>]*)\\s*>";
         Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
         Matcher matcher = pattern.matcher(input);

         while (matcher.find())
         {
            String match = matcher.group();
            System.out.println(match);
         }
      }
      catch (IOException | PatternSyntaxException e)
      {
         e.printStackTrace();
      }
   }
}

java.util.regex.Pattern:

java.util.regex.Matcher:

    • Modifier and TypeMethod and Description
      MatcherappendReplacement(StringBuffer sb, String replacement)

      实施非终端附加和替换步骤。

      StringBufferappendTail(StringBuffer sb)

      实施终端附加和替换步骤。

      intend()

      返回最后一个字符匹配后的偏移量。

      intend(int group)

      返回在上次匹配操作期间由给定组捕获的子序列的最后一个字符之后的偏移量。

      intend(String name)

      返回给定捕获子序列的最后一个字符之后的偏移量 named-capturing group以前的匹配操作期间。

      booleanfind()

      尝试找到匹配模式的输入序列的下一个子序列。

      booleanfind(int start)

      重新设置该匹配器,然后尝试从指定的索引开始找到匹配模式的输入序列的下一个子序列。

      Stringgroup()

      返回与上一个匹配匹配的输入子序列。

      Stringgroup(int group)

      返回在上一次匹配操作期间由给定组捕获的输入子序列。

      Stringgroup(String name)

      返回给定捕获的输入子序列 named-capturing group以前的匹配操作期间。

      intgroupCount()

      返回此匹配器模式中捕获组的数量。

      booleanhasAnchoringBounds()

      查询该匹配器的区域边界的锚定。

      booleanhasTransparentBounds()

      查询此匹配器的区域边界的透明度。

      booleanhitEnd()

      如果在匹配器执行的最后一次匹配操作中输入的结尾被搜索引擎命中,则返回true。

      booleanlookingAt()

      尝试将输入序列从区域开头开始与模式相匹配。

      booleanmatches()

      尝试将整个区域与模式进行匹配。

      Patternpattern()

      返回该匹配器解释的模式。

      static StringquoteReplacement(String s)

      返回一个文字替换 String为指定的 String

      Matcherregion(int start, int end)

      设置该匹配器区域的限制。

      intregionEnd()

      报告该匹配器区域的最终索引(排他)。

      intregionStart()

      报告该匹配器区域的开始索引。

      StringreplaceAll(String replacement)

      将与模式匹配的输入序列的每个子序列替换为给定的替换字符串。

      StringreplaceFirst(String replacement)

      将与模式匹配的输入序列的第一个子序列替换为给定的替换字符串。

      booleanrequireEnd()

      如果更多输入可以将正匹配更改为否定,则返回true。

      Matcherreset()

      重设此匹配器。

      Matcherreset(CharSequence input)

      使用新的输入序列重置此匹配器。

      intstart()

      返回上一个匹配的起始索引。

      intstart(int group)

      返回给定组在上一个匹配操作期间捕获的子序列的开始索引。

      intstart(String name)

      返回给定捕获的子序列的初始索引 named-capturing group以前的匹配操作期间。

      MatchResulttoMatchResult()

      返回此匹配器的匹配状态为MatchResult

      StringtoString()

      返回此匹配器的字符串表示形式。

      MatcheruseAnchoringBounds(boolean b)

      设置该匹配器的区域边界的锚定。

      MatcherusePattern(Pattern newPattern)

      更改,这 Matcher用于查找与匹配的 Pattern。

      MatcheruseTransparentBounds(boolean b)

      设置此匹配器的区域边界的透明度。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值