likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 2

前言

本章知识点分为上下两节,上章主要介绍下字节流、字符流、NIO、下章介绍下 Dex文件加密、手写加固框架核心实现;

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

字节流

从内存角度来看

InputStream.read();将文件中的内容读入到内存中;

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

OutputStream.write();从内存中的内容写出去到一个文件中;

BufferedOutputStream 

  • 数据的缓存;

  • 减少磁盘磁头操作;

  • 例如 byte[] byte = new byte[1024],有缓存的情况是:当数据写满1024的时候,一次性的写入磁盘。如果没有缓存则是每次都是 1byte 的写入磁盘,每写 1byte 操作一次磁盘;

  • flush

  • 如果写的只有 200 byte,没有达到 1024,就会自动写入磁盘,但是当调用了 flush 之后,就会强制调用磁盘 IO 写入;

  • close

  • close 之后会自动调用 flush;

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

PS:文件的读写本身就是堵塞的,所以需要放到异步线程中执行,不能阻塞主线程;

基础用法示例:

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;

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

装饰模式的类图如下:

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

Component:抽象构建接口;

ConcreteComponent:具体的构建对象,实现组件对象接口,通常就是被装饰的原始对象。就对这个对象添加功能;

Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,内部持有一个 Component 对象,就是持有一个被装饰的对象;

ConcreteDecoratorA 和 ConcreteDecoratorB:实际的装饰对象,实现具体添加功能;

BufferedInputStream 是如何提升性能的?

BufferedInputStream 中提供了一个 buf 数组;

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

在执行 read 操作的时候,它并没有一次性全部读取,我们把文件内容读到内存中的时候,可以一个byte 一个byte的读取,也可以 byte[1024] 的一次读 1024 的这样读取,当然了一次读取1024的效率肯定是要比一个一个的读取效率高,所以 BufferedInputStream 是在每次读取的时候都读取一个完整的 buf 数组,将这个 buf 数组填满;

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

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

getInIfOpen().read(buffer, pos, buffer.lenght - pos);

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

如果不用 BufferedInputStream 可以使用 FileInputStream 来代替,但是需要使用 read(byte[] b) 这个接口;

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

每次读取这个数组的长度,不传递每次就是 byte[1] ;

字符流

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

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

字节和字符的区别?

通常 A B C D等这些都是一个字节,中文是两个字节,如果按照字节去读你文件中的中文,这样读取出来的就是没有意义的了;这个时候我们就需要用字符流去读取;

字节流与字符流最大的区别是:readLine();   字符有行的概念的时候 字符流才有意义;

为什么字符流我们用的特别少,因为我们大部分的操作都是 json、xml、zip、apk、exe、png等数据,使用字符流没有意义;

中文状态下

一个字符 == 两个字节

英文状态下

一个字符 == 一个字节

所以这个时候就有了字符编码格式(GBK、GB2312、UTF-8等等)来兼容各种不同的文字,最终这些文字还是用字节来处理的;

基础用法示例:

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 基础示例代码:

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); // 给写入的文件预留空间;

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

一个文件,可以分成多份,每个线程从不同的节点开始读取;但是这里不是你切分的线程越多越好,因为网络是有带宽的~

NIO(java.nio)

FileChannel

FileChannel 是对 I/O 操作的封装,FileChannel 配合 ByteBuffer 将读写的数据缓存到内存中,然后以批量缓存的方式,省去了非批量操作时的中间重复操作,操纵大文件时可以显著提高效率;

我们使用的 FileInputStrem 中 其实已经加入了 FileChannel;

FileChannel 示例如下:

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文件加密、手写加固框架核心实现;

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

欢迎三连

来都来了,点赞关注、点个赞吧~~