前言
本章知识点分为上下两节,上章主要介绍下字节流、字符流、NIO、下章介绍下 Dex文件加密、手写加固框架核心实现;
字节流
从内存角度来看
InputStream.read();将文件中的内容读入到内存中;
OutputStream.write();从内存中的内容写出去到一个文件中;
BufferedOutputStream
-
数据的缓存;
-
减少磁盘磁头操作;
-
例如 byte[] byte = new byte[1024],有缓存的情况是:当数据写满1024的时候,一次性的写入磁盘。如果没有缓存则是每次都是 1byte 的写入磁盘,每写 1byte 操作一次磁盘;
-
flush
-
如果写的只有 200 byte,没有达到 1024,就会自动写入磁盘,但是当调用了 flush 之后,就会强制调用磁盘 IO 写入;
-
close
-
close 之后会自动调用 flush;
PS:文件的读写本身就是堵塞的,所以需要放到异步线程中执行,不能阻塞主线程;
基础用法示例:
scss
public class DataOutputStreamTest {
public static void main(String[] args) {
testDataOutputStream();
testDataInputStream();
}
private static void testDataOutputStream() {
try {
DataOutputStream dataOutputStream = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(
new File("src/testtxt/dataStream.txt"))));
dataOutputStream.writeBoolean(true);
dataOutputStream.writeByte((byte) 0x41);
dataOutputStream.writeChar((char)0x4243);
dataOutputStream.writeShort((short) 0x4445);
dataOutputStream.writeInt(0x12345678);
dataOutputStream.writeLong(0x987654321L);
dataOutputStream.writeUTF("abcdefghijklmnopqrstuvwxyz");
dataOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void testDataInputStream() {
try {
DataInputStream dataInputStream = new DataInputStream(
new BufferedInputStream(
new FileInputStream(
new File("src/testtxt/dataStream.txt"))));
dataInputStream.readBoolean();
dataInputStream.readByte();
dataInputStream.readChar();
dataInputStream.readShort();
dataInputStream.readInt();
dataInputStream.readLong();
dataInputStream.readUTF();
dataInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
装饰模式
在阎宏博士的《JAVA与模式》一书中开头是这样描述装饰(Decorator)模式的:
装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
Android源码中很多地方其实都有用到装饰模式,最常见的就是 Context;
装饰模式的类图如下:
Component:抽象构建接口;
ConcreteComponent:具体的构建对象,实现组件对象接口,通常就是被装饰的原始对象。就对这个对象添加功能;
Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,内部持有一个 Component 对象,就是持有一个被装饰的对象;
ConcreteDecoratorA 和 ConcreteDecoratorB:实际的装饰对象,实现具体添加功能;
BufferedInputStream 是如何提升性能的?
BufferedInputStream 中提供了一个 buf 数组;
在执行 read 操作的时候,它并没有一次性全部读取,我们把文件内容读到内存中的时候,可以一个byte 一个byte的读取,也可以 byte[1024] 的一次读 1024 的这样读取,当然了一次读取1024的效率肯定是要比一个一个的读取效率高,所以 BufferedInputStream 是在每次读取的时候都读取一个完整的 buf 数组,将这个 buf 数组填满;
getInIfOpen().read(buffer, pos, buffer.lenght - pos);
如果不用 BufferedInputStream 可以使用 FileInputStream 来代替,但是需要使用 read(byte[] b) 这个接口;
每次读取这个数组的长度,不传递每次就是 byte[1] ;
字符流
字节和字符的区别?
通常 A B C D等这些都是一个字节,中文是两个字节,如果按照字节去读你文件中的中文,这样读取出来的就是没有意义的了;这个时候我们就需要用字符流去读取;
字节流与字符流最大的区别是:readLine(); 字符有行的概念的时候 字符流才有意义;
为什么字符流我们用的特别少,因为我们大部分的操作都是 json、xml、zip、apk、exe、png等数据,使用字符流没有意义;
中文状态下
一个字符 == 两个字节
英文状态下
一个字符 == 一个字节
所以这个时候就有了字符编码格式(GBK、GB2312、UTF-8等等)来兼容各种不同的文字,最终这些文字还是用字节来处理的;
基础用法示例:
csharp
public class InputStreamReaderTest {
public static void testInputStreamReader(InputStream is) {
try {
InputStreamReader inputStreamReader = new InputStreamReader(is);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str;
while ((str = bufferedReader.readLine()) != null) {
System.out.println("mars:" + inputStreamReader.getEncoding());
System.out.println(str);
}
bufferedReader.close();
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void testOutputStreamWriter() {
try {
BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(
new File("src/testtxt/writerAndStream.txt")), "UTF-8"));
bufferedWriter.write("欢迎来到NBA世界,奇迹在这里发生");
bufferedWriter.flush();
bufferedWriter.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
testOutputStreamWriter();
try {
testInputStreamReader(new FileInputStream(
new File("src/testtxt/writerAndStream.txt")));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
非流式概念
RandomAccessFile
RandomAccessFile 概括来说就是 指哪打哪
- 随机读取,可以指定读取的位置,通过 seek 方法;
- 应用网络数据的读写,断点续传
File 概括来说就是 打哪指哪
这里说的是 读写文件的方式,不是具体的磁盘文件;
RandomAccessFile 基础示例代码:
scss
public class RandomAccessFileTest {
public static void testRandomAccessFile() {
File file = new File("src/texttxt/raf.txt");
if (file.exists()) {
file.delete();
}
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(1000);
printFileLength(randomAccessFile);
randomAccessFile.setLength(10000);
System.out.println("oo");
printFileLength(randomAccessFile);
System.out.println("xx");
randomAccessFile.writeUTF("晚上跟我走");
printFileLength(randomAccessFile);
System.out.println("bb");
randomAccessFile.writeChar('a');
randomAccessFile.writeChars("abcde");
System.out.println("dd");
randomAccessFile.seek(5000);
char[] cbuf = new char[100];
for(int i=0; i<cbuf.length; i++){
cbuf[i] = 'a';
randomAccessFile.writeChar(cbuf[i]);
}
printFileLength(randomAccessFile);
byte[] buff = new byte[1000];
for(int i = 0; i < buff.length; i++) {
buff[i] = 1;
}
randomAccessFile.seek(1000); randomAccessFile.writeBytes(new String(buff));
printFileLength(randomAccessFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void printFileLength(RandomAccessFile rsfWriter) throws IOException {
System.out.println("file length: " + rsfWriter.length() + " file pointer: " + rsfWriter.getFilePointer());
}
}
为什么要有 RandomAccessFile ?可以用来做什么?
RandomAccessFile 通常用来做网络数据的断点续传;
其中最重要的方法有两个:
seek(int index); // 可以将指针移动到某个位置开始读写;
setLength(long len); // 给写入的文件预留空间;
一个文件,可以分成多份,每个线程从不同的节点开始读取;但是这里不是你切分的线程越多越好,因为网络是有带宽的~
NIO(java.nio)
FileChannel
FileChannel 是对 I/O 操作的封装,FileChannel 配合 ByteBuffer 将读写的数据缓存到内存中,然后以批量缓存的方式,省去了非批量操作时的中间重复操作,操纵大文件时可以显著提高效率;
我们使用的 FileInputStrem 中 其实已经加入了 FileChannel;
FileChannel 示例如下:
ini
private void fileChannel() {
File sourceFile = new File("src/texttxt/nio.mp4");
val randomAccessSourceFile = RandomAccessFile(sourceFile, "r");
File targetFile = new File("src/texttxt/targetnio.mp4"); File randomAccessTargetFile = new RandomAccessFile(targetFile, "rw");
FileChannel sourceFileChannel = randomAccessSourceFile.getChannel();
FileChannel targetFileChannel = randomAccessTargetFile.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);
while ((sourceFileChannel.read(byteBuffer)) != -1) {
byteBuffer.flip();
targetFileChannel.write(byteBuffer);
byteBuffer.clear();
}
}
下一章预告
dex文件加密、手写加固框架核心实现;