如何应对Android面试官->文件IO,手写APK加固框架核心实现(上)

前言

本章知识点分为上下两节,上章主要介绍下字节流、字符流、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文件加密、手写加固框架核心实现;

相关推荐
Chen-Edward1 小时前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
陈小桔2 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!2 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
爱学习的大牛1232 小时前
MVVM 架构 android
android·mvvm
xiaogg36782 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July2 小时前
Hikari连接池
java
微风粼粼3 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad3 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6733 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术3 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap