0811(029天 输入输出流05 收尾)
每日一狗(田园犬西瓜瓜)
输入输出流05 收尾
比较接口补充(不想自己写排序规则,想用别人的东西,就要遵守别人的规则)
Arrays.sort(arr);
要求arr必须可以强转成可比较类型(Comparable),必须重写compareTo方法- 要求类必须可扩展
Arrays.sort(arr, com);
中的com是一个比较器(Comparator)的局部内部类- 临时修改比较规则
- 临时创建比较规则
- 比较类无法扩展
文章目录
1. 啥也不是
每日圣经
面向对象的学习方法
分散关注,每个人只关注自己所负责的一小部分
- 先学抽象中的东西
- 在学习特定实现中有什么功能
- 过滤
压缩文件写入
package com.yan2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class Test1 {
public static void main(String[] args) throws Exception {
// PrintWriter pw = new PrintWriter(new BufferedWriter(
// new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream("data/abc.data")))));
// pw.println("1,zhangsan,23.45");
// pw.println("2,lisi,55.55");
// pw.close();
BufferedReader br = new BufferedReader(
new InputStreamReader(new GZIPInputStream(new FileInputStream("data/abc.data"))));
String ss=null;
while((ss=br.readLine())!=null)
System.out.println(ss);
br.close();
}
}
2. 特殊 RandomAccessFile
注释:本篇中所提到的指针并非真正的指针,只是相对于本文件开头的偏移字节数量,java中并没有指针和地址的概念。
RandomAccessFile不属于IO流,支持对文件的读取和写入随机访问
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,既可以读取文件内容,也 可以向文件输出数据。
- 可以读写文件已经操作过的部分
- 支持随机读写
- 覆盖写(字符级别的写一个覆盖一个字符,不会清空文件内容后再写)
构造器
RandomAccessFile类在创建对象时,除了指定文件本身,还需要指定一个mode参数指定 RandomAccessFile的访问模式,该参数有如下四个值:
- r 只读
- rw 可读可写,文件不存在时尝试创建文件
- rws 可读可写,区别与上边rw的是本模式会同步数据到物理存储设备中
- rwd与rws类似,只是仅对文件的内容同步更新到磁盘,而不修改文件的元数据
2.1 读写
-
支持八种简单类型及其包装类和字符串类型的读写操作
-
当然还有那三个write和read
-
文件结尾会抛出EOFException文件结尾异常
注意:读写方法要一致,跟队列很像,先进后出
覆盖部分数据进行写入时,必须与原始数据的占用空间一致才行,(之前写入的是一个20汉字的字符,然后转到这个地址,只覆盖写入了10汉字的字符串,这时候文件内容就完了,后边的数据全错了)
考点:文件第一个写的是一个当前文件内有效的地址,要获取这个地址指向的数据是多少
package com.yang2;
import java.io.RandomAccessFile;
import java.util.Random;
public class Test01 {
public static void main(String[] args) throws Exception {
// 文件写入
RandomAccessFile ref = new RandomAccessFile("data/3.data", "rw");
Random r = new Random();
ref.writeLong(104L);
for (int i = 0; i < 100; i++) {
int kk = r.nextInt();
double dd = r.nextDouble();
if (104 == ref.getFilePointer()) {
System.out.println(kk + "--" + dd); // 1611856893--0.3018222595729477
}
ref.writeInt(kk);
ref.writeDouble(dd);
}
ref.close();
}
}
package com.yang2;
import java.io.RandomAccessFile;
public class Test02 {
public static void main(String[] args) throws Exception {
// 读出文件
RandomAccessFile raf = new RandomAccessFile("data/3.data", "rw");
long index = raf.readLong();
raf.seek(index);
System.out.println(raf.readInt());//1611856893
System.out.println(raf.readDouble());//0.3018222595729477
raf.close();
}
}
2.2 记录指针(相对位置)的特殊方法
支持跳到文件任意位置读写数据,
- 将指针定位到对应的地址:raf.seek(index);
- 跳过多少字节(无法回退):raf.seek(byteLength);
- 获取当前地址:ref.getFilePointer();
2.3 典型案例:
- 断点续传
- 多线程下载单个文件
2.4 常见方法
- void close() 关闭操作
- int read(byte[] b)将内容读取到一个byte数组之中
- byte readByte()读取一个字节
- int readInt()从文件中读取整型数据… readDouble()等8种简单类型
- String readLine()读取一行数据
- void writeBytes(String s)将一个字符串写入到文件之中,按字节的方式处理。
- writeChars void writeInt(int v)将一个int型数据写入文件,长度为4位。
3. NIO开篇
从JDK1.4开始Java引入了一系列改进的输入/输出处理的新功能,统称为NIO,即新IO,新增了许多用于 处理输入输出的类,新IO采用内存映射文件的方式处理输入输出,新IO将文件或文件的一段区域映射到 内存中,这样就可以像访问内存一样来访问文件,这种方式进行输入输出比传统的输入输出快的多
先将文件映射到内存中,然后将在操作的时候是操作内存中的数据。
见见世面就行了🤣
3.1 NIO基础
Channel通道和Buffer缓冲是NIO中的两个核心对象
- Chanel是对传统输入输出系统的模拟,通过map方法可以将一块数据映射到内存中
- Buffer本质是一个数组,发送到Channel中的所有对象都必须先放到Buffer中,而从Channel中读 取的数据必须先放到Buffer中
3.2 Buffer的使用
Buffer是一个抽象类,主要作用是用于装入数据,然后输出数据
- 最常见的子类ByteBuffer可以在底层字节数组上进行get/set操作
- 其它基本数据类型都有对应的Buffer类:CharBuffer、 ShortBuffer、 IntBuffer、LongBuffer、 FloatBuffer、 DoubleBuffer
- 静态方法static XxxBuffer allocate(int capacity)创建一个容量为capacity的XxxBuffer对象
Buffer中有3个重要概念:容量capacity、界限limit和位置position
- 容量capacity表示该Buffer的最大数据容量,创建后则不能改变
- 界限limit,位于limit后的数据既不可被读,也不可被写(默认和容量相等)
- 位置position用于指明下一个可以被读出的或者写入缓冲区的位置索引(默认为0,使用put每放进去一个数据就会向后移动一个位置)
- 标记mark位置:咱们自己打的标记,可以随时返回这个标记在干点啥
常用方法
- capacity():int返回Buffer的容量大小
- hasRemaining():boolean判断是否还有元素可以进行处理
- remaining():int返回当前位置和界限之间的元素个数
- position():int返回当前操作的位置
- mark():Buffer设置Buffer的标记位置,只能在0和position之间做标记
- reset():Buffer将位置position转到mark所在的位置
- rewind():Buffer将位置position设置到0,取消设置的mark
- put(obj)用于向Buffer中放入数据
- get()用于从Buffer中取出数据
- buffer.flip();void 将没有新数据的区域封印起来,把那个limit移动到指定位置
// 创建一个容积为10的buffer对象
CharBuffer bf = CharBuffer.allocate(10);
// capacity() 获取对象容积
System.out.println(bf.capacity()); // 10
// position() 获取当前操作位置
System.out.println(bf.position()); // 0
// limit() 获取当前封锁区开始位置
System.out.println(bf.limit()); // 10
// hasRemaining() //return position < limit; 看一下还有空间吗
System.out.println(bf.hasRemaining()); // true
bf.put((char) 250); // 为当前位置放上存储的数据
System.out.println(bf.get(0)); // ú 获取指定索引上的数据
// remaining(); // return limit - position; 看一下还有几个空间
System.out.println(bf.remaining()); // 9
bf.mark(); // 在当前位置上打个标记
for (int i = 0; i < 5; i++) {
bf.put(i + "");
}
bf.reset();
for (int i = 0; i < 5; i++) {
System.out.print(bf.get() + " "); // 0 1 2 3 4
}
bf.rewind(); // 将位置拉到0,同时取消mark标记
3.3 Channel
Channel可以直接将文件的部分或者全部直接映射成Buffer
注意:不能直接访问Channel中的数据,包括读取、写入都不行。Channel只能与Buffer进行交互.
-
所有Channel不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel方法来返回对应的Channel
-
常用的是FileInputStream、FileOutputStream的getChannel()返回的FileChannel
常用的方法
Channel中最常用的三个方法是map()、read()和write()
read():将数据读出到buffer中
File file = new File("data/Test1.java");
FileChannel inChannel = new FileInputStream(file).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(256);
// 多次读取数据的方式从文件中获取内容
while (inChannel.read(buffer) != -1) {
buffer.flip(); // 将没有数据的区域封印起来
// 获取一个UTF-8的字符集对象
Charset charset = Charset.forName("UTF-8");
// 为这个字符集对象创建一个解码器
CharsetDecoder decoder = charset.newDecoder();
// 使用解码器将ByteBuffer转换为CharBuffer
CharBuffer cb = decoder.decode(buffer);
System.out.println(cb);
buffer.clear();// 将position设置为0,为下一次读取数据做准备
}
map():方法将Channel对应的部分或全部数据映射成ByteBuffer
File f = new File("data/Test1.java");
FileChannel in = new FileInputStream(f).getChannel();
FileChannel out = new FileOutputStream("data/a.txt").getChannel();
MappedByteBuffer buffer = in.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
// 参数1为执行映射时的模式,有只读、读写模式;参数2和3用于设置哪些数据执行映射。可以将FileChannel中全部数据映射为ByteBuffer
out.write(buffer);
buffer.clear();
Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer cb = decoder.decode(buffer);
System.out.println(cb);
write():写入数据
File file = new File("data/a.txt");
int len = (int) file.length();
RandomAccessFile raf = new RandomAccessFile(file, "rw");
// 返回的channel是只读还是读写,取决于RandomAccessFile文件对象的打开模式
FileChannel channel = raf.getChannel();
// buffer支持可读,将文件中的所有内容映射到buffer中
ByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, len);
channel.position(len); // 移动指针到内容末尾
channel.write(buffer); // 重新写出buffer中的内容,实际上就是将文件内容拷贝
3.4 selector
是NIO的核心组件之一,用于检查数个NIO Channel通道的状态是否处于可读、可写。
好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上 下文切换带来的开销。
4. Chatset字符集
计算机中将二进制文件转化内字符的一张映射表,
Charset类的常用方法
- availableCharsets():SortedMap 获取当前JDK所支持的所有字符集
- newDecoder():CharsetDecoder获取该编码字符集对应的解码器
- decode(ByteBuffer):CharBuffer方法可以将字节序列ByteBuffer转换为CharBuffer字符序列
- newEncoder():CharsetEncoder获取该编码字符集对应的编码器
- encode(CharBuffer):ByteBuffer方法可以将字符序列CharBuffer转换为ByteBuffer字节序列
Charset c1 = Charset.forName("GBK");
CharsetEncoder encoder = c1.newEncoder();
CharsetDecoder decoder = c1.newDecoder();
CharBuffer cb = CharBuffer.allocate(8);
cb.put('孙');
cb.put('误');
cb.put('空');
cb.flip();
//将CharBuffer转换为ByteBuffer
ByteBuffer bb=encoder.encode(cb);
for(int i=0;i<6;i++) {
System.out.println(bb.get(i)+" ");
}
System.out.println("====================");
//将byteBuffer转换为charBuffer
System.out.println(decoder.decode(bb));
扩展小芝士
- 字符串写出后在结尾部分会存在一个结尾标志字符