营销型网站建设怎么做,英文网站建设,网络设计报告怎么写,WordPress和微信公众号其它IO合集 1. 缓冲流1.1 概述1.2 字节缓冲流构造方法效率测试 1.3 字符缓冲流构造方法特有方法 2. 转换流2.1 字符编码和字符集字符编码字符集 2.2 编码引出的问题2.3 InputStreamReader类构造方法指定编码读取 2.4 OutputStreamWriter类构造方法指定编码写出转换流理解图解 3… 其它IO合集 1. 缓冲流1.1 概述1.2 字节缓冲流构造方法效率测试 1.3 字符缓冲流构造方法特有方法 2. 转换流2.1 字符编码和字符集字符编码字符集 2.2 编码引出的问题2.3 InputStreamReader类构造方法指定编码读取 2.4 OutputStreamWriter类构造方法指定编码写出转换流理解图解 3. 序列化3.1 概述3.2 ObjectOutputStream类构造方法序列化操作 3.3 ObjectInputStream类构造方法反序列化操作1反序列化操作2 3.4 练习序列化集合案例分析案例实现 4. 打印流4.1 概述4.2 PrintStream类构造方法改变打印流向 5. 数据流5.1 DataInput接口5.2 DataOutput接口5.3 常见实现类的使用DataInputStreamDataOutputStream 6. 压缩流和解压缩流7. 随机访问文件API详情 1. 缓冲流
1.1 概述
缓冲流,也叫高效流是对4个基本的FileXxx 流的增强所以也是4个流按照数据类型分类
字节缓冲流BufferedInputStreamBufferedOutputStream字符缓冲流BufferedReaderBufferedWriter
缓冲流的基本原理是在创建流对象时会创建一个内置的默认大小的缓冲区数组通过缓冲区读写减少系统IO次数从而提高读写的效率。
1.2 字节缓冲流
构造方法
public BufferedInputStream(InputStream in) 创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流。
构造举例代码如下
// 创建字节缓冲输入流
BufferedInputStream bis new BufferedInputStream(new FileInputStream(bis.txt));
// 创建字节缓冲输出流
BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(bos.txt));效率测试
查询API缓冲流读写方法与基本的流是一致的我们通过复制大文件375MB测试它的效率。
基本流代码如下
public class BufferedDemo {public static void main(String[] args) throws FileNotFoundException {// 记录开始时间long start System.currentTimeMillis();// 创建流对象try (FileInputStream fis new FileInputStream(jdk9.exe);FileOutputStream fos new FileOutputStream(copy.exe)){// 读写数据int b;while ((b fis.read()) ! -1) {fos.write(b);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end System.currentTimeMillis();System.out.println(普通流复制时间:(end - start) 毫秒);}
}十几分钟过去了...缓冲流代码如下
public class BufferedDemo {public static void main(String[] args) throws FileNotFoundException {// 记录开始时间long start System.currentTimeMillis();// 创建流对象try (BufferedInputStream bis new BufferedInputStream(new FileInputStream(jdk9.exe));BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(copy.exe));){// 读写数据int b;while ((b bis.read()) ! -1) {bos.write(b);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end System.currentTimeMillis();System.out.println(缓冲流复制时间:(end - start) 毫秒);}
}缓冲流复制时间:8016 毫秒如何更快呢
使用数组的方式代码如下
public class BufferedDemo {public static void main(String[] args) throws FileNotFoundException {// 记录开始时间long start System.currentTimeMillis();// 创建流对象try (BufferedInputStream bis new BufferedInputStream(new FileInputStream(jdk9.exe));BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(copy.exe));){// 读写数据int len;byte[] bytes new byte[8*1024];while ((len bis.read(bytes)) ! -1) {bos.write(bytes, 0 , len);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end System.currentTimeMillis();System.out.println(缓冲流使用数组复制时间:(end - start) 毫秒);}
}
缓冲流使用数组复制时间:666 毫秒1.3 字符缓冲流
构造方法
public BufferedReader(Reader in) 创建一个 新的缓冲输入流。public BufferedWriter(Writer out) 创建一个新的缓冲输出流。
构造举例代码如下
// 创建字符缓冲输入流
BufferedReader br new BufferedReader(new FileReader(br.txt));
// 创建字符缓冲输出流
BufferedWriter bw new BufferedWriter(new FileWriter(bw.txt));特有方法
字符缓冲流的基本方法与普通字符流调用方式一致不再阐述我们来看它们具备的特有方法。
BufferedReaderpublic String readLine(): 读一行文字。BufferedWriterpublic void newLine(): 写一行行分隔符,由系统属性定义符号。
readLine方法演示代码如下
public class BufferedReaderDemo {public static void main(String[] args) throws IOException {// 创建流对象BufferedReader br new BufferedReader(new FileReader(in.txt));// 定义字符串,保存读取的一行文字String line null;// 循环读取,读取到最后返回nullwhile ((line br.readLine())!null) {System.out.print(line);System.out.println(------);}// 释放资源br.close();}
}newLine方法演示代码如下
public class BufferedWriterDemo throws IOException {public static void main(String[] args) throws IOException {// 创建流对象BufferedWriter bw new BufferedWriter(new FileWriter(out.txt));// 写出数据bw.write(狄仁杰);// 写出换行bw.newLine();bw.write(探案);bw.newLine();bw.write(传奇);bw.newLine();// 释放资源bw.close();}
}
输出效果:
狄仁杰
探案
传奇2. 转换流
2.1 字符编码和字符集
字符编码
计算机中储存的信息都是用二进制数表示的而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则将字符存储到计算机中称为编码 。反之将存储在计算机中的二进制数按照某种规则解析显示出来称为解码 。比如说按照A规则存储同样按照A规则解析那么就能显示正确的文本符号。反之按照A规则存储再按照B规则解析就会导致乱码现象。
编码:字符(能看懂的)–字节(看不懂的)
解码:字节(看不懂的)–字符(能看懂的) 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。 编码表:生活中文字和计算机中二进制的对应规则
字符集
字符集 Charset也叫编码表。是一个系统支持的所有字符的集合包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号需要进行字符编码一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。 可见当指定了编码它所对应的字符集自然就指定了所以编码才是我们最终要关心的。
ASCII字符集 ASCIIAmerican Standard Code for Information Interchange美国信息交换标准代码是基于拉丁字母的一套电脑编码系统用于显示现代英语主要包括控制字符回车键、退格、换行键等和可显示字符英文大小写字符、阿拉伯数字和西文符号。基本的ASCII字符集使用7位bits表示一个字符共128字符。ASCII的扩展字符集使用8位bits表示一个字符共256字符方便支持欧洲常用字符。 ISO-8859-1字符集 拉丁码表别名Latin-1用于显示欧洲使用的语言包括荷兰、丹麦、德语、意大利语、西班牙语等。ISO-8859-1使用单字节编码兼容ASCII编码。 GBxxx字符集 GB就是国标的意思是为了显示中文而设计的一套字符集。GB2312简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时就表示一个汉字这样大约可以组合了包含7000多个简体汉字此外数学符号、罗马希腊的字母、日文的假名们都编进去了连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码这就是常说的全角字符而原来在127号以下的那些就叫半角字符了。GBK最常用的中文码表。是在GB2312标准基础上的扩展规范使用了双字节编码方案共收录了21003个汉字完全兼容GB2312标准同时支持繁体汉字以及日韩汉字等。GB18030最新的中文码表。收录汉字70244个采用多字节编码每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字同时支持繁体汉字以及日韩汉字等。 Unicode字符集 Unicode编码系统为表达任意语言的任意字符而设计是业界的一种标准也称为统一码、标准万国码。它最多使用4个字节的数字来表达每个字母、符号或者文字。有三种编码方案UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。UTF-8编码可以用来表示Unicode标准中任何字符它是电子邮件、网页及其他存储或传送文字的应用中优先采用的编码。互联网工程工作小组IETF要求所有互联网协议都必须支持UTF-8编码。所以我们开发Web应用也要使用UTF-8编码。它使用一至四个字节为每个字符编码编码规则 128个US-ASCII字符只需一个字节编码。拉丁文等字符需要二个字节编码。大部分常用字含中文使用三个字节编码。其他极少使用的Unicode辅助字符使用四字节编码。
2.2 编码引出的问题
在IDEA中使用FileReader 读取项目中的文本文件。由于IDEA的设置都是默认的UTF-8编码所以没有任何问题。但是当读取Windows系统中创建的文本文件时由于Windows系统的默认是GBK编码就会出现乱码。
public class ReaderDemo {public static void main(String[] args) throws IOException {FileReader fileReader new FileReader(E:\\File_GBK.txt);int read;while ((read fileReader.read()) ! -1) {System.out.print((char)read);}fileReader.close();}
}
输出结果那么如何读取GBK编码的文件呢
2.3 InputStreamReader类
转换流java.io.InputStreamReader是Reader的子类是从字节流到字符流的桥梁。它读取字节并使用指定的字符集将其解码为字符。它的字符集可以由名称指定也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
构造举例代码如下
InputStreamReader isr new InputStreamReader(new FileInputStream(in.txt));
InputStreamReader isr2 new InputStreamReader(new FileInputStream(in.txt) , GBK);指定编码读取
public class ReaderDemo2 {public static void main(String[] args) throws IOException {// 定义文件路径,文件为gbk编码String FileName E:\\file_gbk.txt;// 创建流对象,默认UTF8编码InputStreamReader isr new InputStreamReader(new FileInputStream(FileName));// 创建流对象,指定GBK编码InputStreamReader isr2 new InputStreamReader(new FileInputStream(FileName) , GBK);// 定义变量,保存字符int read;// 使用默认编码字符流读取,乱码while ((read isr.read()) ! -1) {System.out.print((char)read); // Һ}isr.close();// 使用指定编码字符流读取,正常解析while ((read isr2.read()) ! -1) {System.out.print((char)read);// 大家好}isr2.close();}
}2.4 OutputStreamWriter类
转换流java.io.OutputStreamWriter 是Writer的子类是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定也可以接受平台的默认字符集。
构造方法
OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
构造举例代码如下
OutputStreamWriter isr new OutputStreamWriter(new FileOutputStream(out.txt));
OutputStreamWriter isr2 new OutputStreamWriter(new FileOutputStream(out.txt) , GBK);指定编码写出
public class OutputDemo {public static void main(String[] args) throws IOException {// 定义文件路径String FileName E:\\out.txt;// 创建流对象,默认UTF8编码OutputStreamWriter osw new OutputStreamWriter(new FileOutputStream(FileName));// 写出数据osw.write(你好); // 保存为6个字节osw.close();// 定义文件路径String FileName2 E:\\out2.txt;// 创建流对象,指定GBK编码OutputStreamWriter osw2 new OutputStreamWriter(new FileOutputStream(FileName2),GBK);// 写出数据osw2.write(你好);// 保存为4个字节osw2.close();}
}转换流理解图解
转换流是字节与字符间的桥梁 3. 序列化
3.1 概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后相当于文件中持久保存了一个对象的信息。
反之该字节序列还可以从文件中读取回来重构对象对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息都可以用来在内存中创建对象。看图理解序列化 3.2 ObjectOutputStream类
java.io.ObjectOutputStream 类将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out) 创建一个指定OutputStream的ObjectOutputStream。
构造举例代码如下
FileOutputStream fileOut new FileOutputStream(employee.txt);
ObjectOutputStream out new ObjectOutputStream(fileOut);序列化操作
一个对象要想序列化必须满足两个条件:
该类必须实现java.io.Serializable 接口Serializable 是一个标记接口不实现此接口的类将不会使任何状态序列化或反序列化会抛出NotSerializableException 。该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的则该属性必须注明是瞬态的使用transient 关键字修饰。
public class Employee implements java.io.Serializable {public String name;public String address;public transient int age; // transient瞬态修饰成员,不会被序列化public void addressCheck() {System.out.println(Address check : name -- address);}
}2.写出对象方法
public final void writeObject (Object obj) : 将指定的对象写出。
public class SerializeDemo{public static void main(String [] args) {Employee e new Employee();e.name zhangsan;e.address beiqinglu;e.age 20; try {// 创建序列化流对象ObjectOutputStream out new ObjectOutputStream(new FileOutputStream(employee.txt));// 写出对象out.writeObject(e);// 释放资源out.close();fileOut.close();System.out.println(Serialized data is saved); // 姓名地址被序列化年龄没有被序列化。} catch(IOException i) {i.printStackTrace();}}
}
输出结果
Serialized data is saved3.3 ObjectInputStream类
ObjectInputStream反序列化流将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in) 创建一个指定InputStream的ObjectInputStream。
反序列化操作1
如果能找到一个对象的class文件我们可以进行反序列化操作调用ObjectInputStream读取对象的方法
public final Object readObject () : 读取一个对象。
public class DeserializeDemo {public static void main(String [] args) {Employee e null;try { // 创建反序列化流FileInputStream fileIn new FileInputStream(employee.txt);ObjectInputStream in new ObjectInputStream(fileIn);// 读取一个对象e (Employee) in.readObject();// 释放资源in.close();fileIn.close();}catch(IOException i) {// 捕获其他异常i.printStackTrace();return;}catch(ClassNotFoundException c) {// 捕获类找不到异常System.out.println(Employee class not found);c.printStackTrace();return;}// 无异常,直接打印输出System.out.println(Name: e.name); // zhangsanSystem.out.println(Address: e.address); // beiqingluSystem.out.println(age: e.age); // 0}
}对于JVM可以反序列化对象它必须是能够找到class文件的类。如果找不到该类的class文件则抛出一个 ClassNotFoundException 异常。
反序列化操作2
**另外当JVM反序列化对象时能找到class文件但是class文件在序列化对象之后发生了修改那么反序列化操作也会失败抛出一个InvalidClassException异常。**发生这个异常的原因如下
该类的序列版本号与从流中读取的类描述符的版本号不匹配该类包含未知数据类型该类没有可访问的无参数构造方法
Serializable 接口给需要序列化的类提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Employee implements java.io.Serializable {// 加入序列版本号private static final long serialVersionUID 1L;public String name;public String address;// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.public int eid; public void addressCheck() {System.out.println(Address check : name -- address);}
}3.4 练习序列化集合
将存有多个自定义对象的集合序列化操作保存到list.txt文件中。反序列化list.txt 并遍历集合打印对象信息。
案例分析
把若干学生对象 保存到集合中。把集合序列化。反序列化读取时只需要读取一次转换为集合类型。遍历集合可以打印所有的学生信息
案例实现
public class SerTest {public static void main(String[] args) throws Exception {// 创建 学生对象Student student new Student(老王, laow);Student student2 new Student(老张, laoz);Student student3 new Student(老李, laol);ArrayListStudent arrayList new ArrayList();arrayList.add(student);arrayList.add(student2);arrayList.add(student3);// 序列化操作// serializ(arrayList);// 反序列化 ObjectInputStream ois new ObjectInputStream(new FileInputStream(list.txt));// 读取对象,强转为ArrayList类型ArrayListStudent list (ArrayListStudent)ois.readObject();for (int i 0; i list.size(); i ){Student s list.get(i);System.out.println(s.getName()-- s.getPwd());}}private static void serializ(ArrayListStudent arrayList) throws Exception {// 创建 序列化流 ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(list.txt));// 写出对象oos.writeObject(arrayList);// 释放资源oos.close();}
}4. 打印流
4.1 概述
平时我们在控制台打印输出是调用print方法和println方法完成的这两个方法都来自于java.io.PrintStream类该类能够方便地打印各种数据类型的值是一种便捷的输出方式。
4.2 PrintStream类
构造方法
public PrintStream(String fileName) 使用指定的文件名创建一个新的打印流。
构造举例代码如下
PrintStream ps new PrintStream(ps.txt)改变打印流向
System.out就是PrintStream类型的只不过它的流向是系统规定的打印在控制台上。不过既然是流对象我们就可以玩一个小把戏改变它的流向。
public class PrintDemo {public static void main(String[] args) throws IOException {// 调用系统的打印流,控制台直接输出97System.out.println(97);// 创建打印流,指定文件的名称PrintStream ps new PrintStream(ps.txt);// 设置系统的打印流流向,输出到ps.txtSystem.setOut(ps);// 调用系统的打印流,ps.txt中输出97System.out.println(97);}
}5. 数据流
DataInput 和 DataOutput 接口定义了以二进制格式写数字、字符、boolean 值和字符串的方法可以让我们更方便的操纵数据。
5.1 DataInput接口
DataInput接口提供了一组用于读取基本数据类型的方法如int、long、double等以及读取UTF字符串的方法。它是Java的一个接口通常由DataInputStream类实现。
常用方法包括
readBoolean()读取一个boolean值。readByte()读取一个byte值。readShort()读取一个short值。readInt()读取一个int值。readLong()读取一个long值。readFloat()读取一个float值。readDouble()读取一个double值。readUTF()读取一个UTF字符串。readFully(byte[] b)将字节读入到数组中期间阻塞直至所有字节都读入。skipBytes(int n)跳过 n 个字节期间阻塞直至所有字节都被跳过。
5.2 DataOutput接口
DataOutput接口提供了一组用于写入基本数据类型的方法如int、long、double等以及写入UTF字符串的方法。它是Java的一个接口通常由DataOutputStream类实现允许把数据及其类型一并写入。
常用方法包括
writeBoolean(boolean v)写入一个boolean值。writeByte(int v)写入一个byte值。writeShort(int v)写入一个short值。writeInt(int v)写入一个int值。writeLong(long v)写入一个long值。writeFloat(float v)写入一个float值。writeDouble(double v)写入一个double值。writeUTF(String str)写入一个UTF字符串。
5.3 常见实现类的使用
DataInputStream
DataInputStream实现了DataInput接口可以从输入流中读取基本数据类型和UTF字符串。
try (DataInputStream dis new DataInputStream(new FileInputStream(data.bin))) {int intValue dis.readInt();double doubleValue dis.readDouble();String strValue dis.readUTF();
} catch (IOException e) {e.printStackTrace();
}DataOutputStream
DataOutputStream实现了DataOutput接口可以向输出流中写入基本数据类型和UTF字符串。
try (DataOutputStream dos new DataOutputStream(new FileOutputStream(data.bin))) {dos.writeInt(42);dos.writeDouble(3.14);dos.writeUTF(Hello, world!);
} catch (IOException e) {e.printStackTrace();
}这样可以使用DataInput和DataOutput接口及其实现类来读写基本数据类型和UTF字符串了。
6. 压缩流和解压缩流
压缩流
负责压缩文件或者文件夹
解压缩流
负责把压缩包中的文件和文件夹解压出来
/*
* 解压缩流
*
* */
public class ZipStreamDemo1 {public static void main(String[] args) throws IOException {//1.创建一个File表示要解压的压缩包File src new File(D:\\aaa.zip);//2.创建一个File表示解压的目的地File dest new File(D:\\);//调用方法unzip(src,dest);}//定义一个方法用来解压public static void unzip(File src,File dest) throws IOException {//解压的本质把压缩包里面的每一个文件或者文件夹读取出来按照层级拷贝到目的地当中//创建一个解压缩流用来读取压缩包中的数据ZipInputStream zip new ZipInputStream(new FileInputStream(src));//要先获取到压缩包里面的每一个zipentry对象//表示当前在压缩包中获取到的文件或者文件夹ZipEntry entry;while((entry zip.getNextEntry()) ! null){System.out.println(entry);if(entry.isDirectory()){//文件夹需要在目的地dest处创建一个同样的文件夹File file new File(dest,entry.toString());file.mkdirs();}else{//文件需要读取到压缩包中的文件并把他存放到目的地dest文件夹中按照层级目录进行存放FileOutputStream fos new FileOutputStream(new File(dest,entry.toString()));int b;while((b zip.read()) ! -1){//写到目的地fos.write(b);}fos.close();//表示在压缩包中的一个文件处理完毕了。zip.closeEntry();}}zip.close();}
}public class ZipStreamDemo2 {public static void main(String[] args) throws IOException {/** 压缩流* 需求* 把D:\\a.txt打包成一个压缩包* *///1.创建File对象表示要压缩的文件File src new File(D:\\a.txt);//2.创建File对象表示压缩包的位置File dest new File(D:\\);//3.调用方法用来压缩toZip(src,dest);}/** 作用压缩* 参数一表示要压缩的文件* 参数二表示压缩包的位置* */public static void toZip(File src,File dest) throws IOException {//1.创建压缩流关联压缩包ZipOutputStream zos new ZipOutputStream(new FileOutputStream(new File(dest,a.zip)));//2.创建ZipEntry对象表示压缩包里面的每一个文件和文件夹//参数压缩包里面的路径ZipEntry entry new ZipEntry(aaa\\bbb\\a.txt);//3.把ZipEntry对象放到压缩包当中zos.putNextEntry(entry);//4.把src文件中的数据写到压缩包当中FileInputStream fis new FileInputStream(src);int b;while((b fis.read()) ! -1){zos.write(b);}zos.closeEntry();zos.close();}
}public class ZipStreamDemo3 {public static void main(String[] args) throws IOException {/** 压缩流* 需求* 把D:\\aaa文件夹压缩成一个压缩包* *///1.创建File对象表示要压缩的文件夹File src new File(D:\\aaa);//2.创建File对象表示压缩包放在哪里压缩包的父级路径File destParent src.getParentFile();//D:\\//3.创建File对象表示压缩包的路径File dest new File(destParent,src.getName() .zip);//4.创建压缩流关联压缩包ZipOutputStream zos new ZipOutputStream(new FileOutputStream(dest));//5.获取src里面的每一个文件变成ZipEntry对象放入到压缩包当中toZip(src,zos,src.getName());//aaa//6.释放资源zos.close();}/** 作用获取src里面的每一个文件变成ZipEntry对象放入到压缩包当中* 参数一数据源* 参数二压缩流* 参数三压缩包内部的路径* */public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {//1.进入src文件夹File[] files src.listFiles();//2.遍历数组for (File file : files) {if(file.isFile()){//3.判断-文件变成ZipEntry对象放入到压缩包当中ZipEntry entry new ZipEntry(name \\ file.getName());//aaa\\no1\\a.txtzos.putNextEntry(entry);//读取文件中的数据写到压缩包FileInputStream fis new FileInputStream(file);int b;while((b fis.read()) ! -1){zos.write(b);}fis.close();zos.closeEntry();}else{//4.判断-文件夹递归toZip(file,zos,name \\ file.getName());// no1 aaa \\ no1}}}
}7. 随机访问文件
RandomAccessfile 类可以在文件中的任何位置查找或写人数据。磁盘文件都是随机访问的但是与网络套接字通信的输入/输出流却不是。
你可以打开一个随机访问文件只用于读入或者同时用于读写你可以通过使用字符串r(用于读入访问)或rw(用于读入/写出访问)作为构造器的第二个参数来指定这个选项。
var in new RandomAccessFile(employee.dat, r);
var inOut new RandomAccessFile(employee.dat, rw);当你将已有文件作为 RandomAccessfile 打开时这个文件并不会被删除。
随机访问文件有一个表示下一个将被读人或写出的字节所处位置的文件指针seek方法可以用来将这个文件指针设置到文件中的任意字节位置seek的参数是一个long类型的整数它的值位于0到文件按照字节来度量的长度之间。
getfilePointer方法将返回文件指针的当前位置。
RandomAccessFile 类同时实现了DataInput和 DataOutput接口。为了读写随机访问文件可以使用在前面小节中讨论过的诸如 readInt/writeInt 和 readchar/wrltechar 之类的方法。
我们现在要剖析一个将雇员记录存储到随机访问文件中的示例程序其中每条记录都拥有相同的大小这样我们可以很容易地读人任何记录。假设你希望将文件指针置于第三条记录处那么你只需将文件指针置于恰当的字节位置然后就可以开始读人了。
long n 3;
in.seek((n-1)*RECORD_SIZE);
var enew Employee();
e.readData(in);如果你希望修改记录然后将其存回到相同的位置那么请切记要将文件指针置回到这条记录的开始处:
in.seek((n-1)*RECORD_SIZE);
e.writeData(out);要确定文件中的字节总数可以使用length方法而记录的总数则等于用字节总数除以每条记录的大小。
long nbytes in.length();// length in bytes
int nrecords (int) (nbytes / RECORD_SIZE);整数和浮点值在二进制格式中都具有固定的尺寸但是在处理字符串时就有些麻烦了因此我们提供了两个助手方法来读写具有固定尺寸的字符串。
writeFixedString写出从字符串开头开始的指定数量的码元(如果码元过少该方法将用0值来补齐字符串)。
public static void writeFixedString(String s, int size, DataOutput out)throws IOException {for(int i0;i size; i){char ch 0;if(is.length())chs.charAt(i);out.writeChar(ch);}
}readFixedString方法从输人流中读入字符直至读入size个码元或者直至遇到具有0值的字符值然后跳过输入字段中剩余的0值。为了提高效率这个方法使用了StringBuilder类来读入字符串。
public static String readFixedString(int size, DataInput in) throws IOException {var b new StringBuilder(size);int i 0;var done false;while (!done i size) {char ch in.readChar();i;if (ch 0) done true;else b.append(ch);}in.skipBytes(2 * (size - i));return b.toString();
}我们将 writeFixedString和readFixedString方法放到了 DataIO 助手类的内部为了写出一条固定尺寸的记录我们直接以二进制方式写出所有的字段:
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());读回数据也很简单:
String name DataIO.readFixedString(Employee.NAME_SIZE, in);
double salary in.readDouble();
int y in.readInt();
int m in.readInt();
int d in.readInt();让我们来计算每条记录的大小:我们将使用40个字符来表示姓名字符串因此每条记录包含 100个字节:
40字符80字节用于姓名。1 double8字节用于薪水。3 int12 字节用于日期。
API详情
RandomAccessFile(String file, String mode)RandomAccessFile(File file, String mode) 打开给定的用于随机访问的文件。mode字符串r表示只读模式; rw表示读/写模式;rws表示每次更新时都对数据和元数据的写磁盘操作进行同步的读/写模式;rwd表示每次更新时只对数据的写磁盘操作进行同步的读/写式。 long getFilePointer() 返回文件指针的当前位置。void seek(long pos) 将文件指针设置到距文件开头pos个字节处long length() 返回文件按照字节来度量的长度。