编程语言您现在的位置是:首页 > 博客日志 > 编程语言

java 如何以最快的速度读写文件?如何提高读写速度?

<a href='mailto:'>微wx笑</a>的头像微wx笑 2021-07-31编程语言172 3 0关键字: java  文件  nio  FileChannel  

最近在研究 解码解密微信电脑版image文件夹下缓存的用户图片 dat文件解码解密查看方法 时,开始读取文件是一个字节一个字节的读取写入,速度非常之慢,让人无法忍受,于是开始研究尝试如何以最快的速度读写文件?

最近在研究 解码解密微信电脑版image文件夹下缓存的用户图片 dat文件解码解密查看方法 时,开始读取文件是一个字节一个字节的读取写入,速度非常之慢,让人无法忍受,于是开始研究尝试如何以最快的速度读写文件?4Tn无知


4Tn无知

版本1不使用缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.util.Date;
 
class decode
{
    public static void main(String[] args) {
        System.out.println("hello");
 
        File f = new File("D:/Documents/WeChat Files/wxid_/Image/2021-07");
        if (!f.exists() || !f.isDirectory()){
            System.out.println("目录不存在");
        }
        File[] fl = f.listFiles(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(".dat")){
                    return true;
                }
                return false;
            }
        });
        System.out.println("共找到 " + fl.length + " 个 dat 文件");
 
        if (fl.length == 0){
            System.out.println("没有需要解码的文件");
        }
        long begin = new Date().getTime();
        System.out.println("====开始解码====time:" + begin);
        for (File file : fl){
            System.out.println(file.getAbsolutePath());
            try {
                FileInputStream fis = new FileInputStream(file);
                File of = new File("D:/Documents/WeChat Files/wxid_/FileStorage/Image/decodeimg/" + file.getName() + ".jpeg");
                FileOutputStream fos = new FileOutputStream(of);
                int b;
                while((b = fis.read()) != -1){
                    fos.write(b ^ 0x73);
                }
 
                fis.close();
                fos.close();
            catch (Exception e) {
                 
            }
        }
         
        long end = new Date().getTime();
        System.out.println("decode====" + fl.length + " 个dat文件解码完成====用时:" + (end - begin));
    }
 
 
}

一个字节一个字节的读取写入,真是让人崩溃!4Tn无知

为什么会这样呢?

调用I\O操作的时候,实际上还是一个一个的读或者写,关键就在,CPU只有一个,不论是几个核心。CPU在系统调用时,会不会还要参与主要操作?参与多次就会花更多的时间。4Tn无知

系统调用时,若不用缓冲,CPU会酌情考虑使用 中断。此时CPU是主动地,每个周期中都要花去一部分去询问I\O设备是否读完数据,这段时间CPU不能做任何其他的事情(至少负责执行这段模块的核不能)。所以,调用一次读了一个字,通报一次,CPU腾出时间处理一次。4Tn无知

而设置缓冲,CPU通常会使用 DMA 方式去执行 I\O 操作。CPU 将这个工作交给DMA控制器来做,自己腾出时间做其他的事,当DMA完成工作时,DMA会主动告诉CPU“操作完成”。这时,CPU接管后续工作。在此,CPU 是被动的。DMA是专门 做 I\O 与 内存 数据交换的,不仅自身效率高,也节约了CPU时间,CPU在DMA开始和结束时做了一些设置罢了。4Tn无知

所以,调用一次,不必通报CPU,等缓冲区满了,DMA 会对C PU 说 “嘿,伙计!快过来看看,把他们都搬走吧”。4Tn无知

综上,设置缓冲,就建立了数据块,使得DMA执行更方便,CPU也有空闲,而不是呆呆地候着I\O数据读来。从微观角度来说,设置缓冲效率要高很多。尽管,不能从这个程序上看出来。 几万字的读写\就能看到差距4Tn无知

版本2按块读取

参考  解码解密微信电脑版image文件夹下缓存的用户图片 dat文件解码解密查看方法  文章中的代码4Tn无知

设置了一个1M大小的Buffer,byte[] bs = new byte[1024 * 1024];4Tn无知

速度一下子就提上来了,如果不添加获取解密字节码及文件扩展名的代码,对1300左右个文件进行解码,两三秒就完成了,版本1可是需要十几分钟的。
4Tn无知

版本3使用BufferedStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.util.Date;
 
class decode3
{
    public static void main(String[] args) {
        System.out.println("hello");
 
        File f = new File("D:/Documents/WeChat Files/wxid_/FileStorage/Image/2021-07");
        if (!f.exists() || !f.isDirectory()){
            System.out.println("目录不存在");
        }
        File[] fl = f.listFiles(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(".dat")){
                    return true;
                }
                return false;
            }
        });
        System.out.println("共找到 " + fl.length + " 个 dat 文件");
 
        if (fl.length == 0){
            System.out.println("没有需要解码的文件");
        }
        long begin = new Date().getTime();
        System.out.println("====开始解码====time:" + begin);
        for (File file : fl){
            System.out.println(file.getAbsolutePath());
            try {
                FileInputStream fis = new FileInputStream(file);
                File of = new File("D:/Documents/WeChat Files/wxid_/FileStorage/Image/decodeimg3/" + file.getName() + ".jpeg");
                FileOutputStream fos = new FileOutputStream(of);
                BufferedInputStream bis = new BufferedInputStream(fis);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
 
                byte[] buff = new byte[1024 1024];
                int rl = 0;
                while((rl = bis.read(buff)) > 0){
                    for (int i = 0; i < rl; i++){
                        buff[i] = (byte)(buff[i] ^ 0x73);
                    }
                    bos.write(buff, 0, rl);
                }
 
                bis.close();
                bos.close();
                fis.close();
                fos.close();
                 
            catch (Exception e) {
                 
            }
        }
         
        long end = new Date().getTime();
        System.out.println("decode3====" + fl.length + " 个dat文件解码完成====用时:" + (end - begin));
    }
}

这个版本中间又加了一层4Tn无知

1
2
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);

然而在性能速度上没有明显的提升,经过多次的测试,貌似性能更稳定一点点。4Tn无知

部分测试结果

decode1====解码完成====用时:13882254Tn无知

decode1====解码完成====用时:13677314Tn无知

decode1====解码完成====用时:13621414Tn无知

decode2====解码完成====用时:32904Tn无知

decode2====解码完成====用时:44744Tn无知

decode2====1263 个dat文件解码完成====用时:32014Tn无知

decode2====1263 个dat文件解码完成====用时:26224Tn无知

decode2====1263 个dat文件解码完成====用时:25534Tn无知

decode2====1263 个dat文件解码完成====用时:26074Tn无知

decode2====1263 个dat文件解码完成====用时:31384Tn无知

decode3====1263 个dat文件解码完成====用时:32424Tn无知

decode3====1263 个dat文件解码完成====用时:28644Tn无知

decode3====1263 个dat文件解码完成====用时:24494Tn无知

decode3====1263 个dat文件解码完成====用时:24254Tn无知

decode3====1263 个dat文件解码完成====用时:25284Tn无知

版本4Nio.FileChannel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.util.Date;
import java.nio.channels;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
/**
 * 微信电脑版image文件夹下缓存的用户图片dat文件解码解密类
 * @author lipw
 * @email admin@ivu4e.com
 * @site https://ivu4e.com/
 */
class decode4
{
    public static void main(String[] args) {
        System.out.println("hello");
        // 使用时修改这里的路径就可以了
        BatchDecodeFileContent("D:/Documents/WeChat Files/wxid_/FileStorage/Image/2021-07""D:/Documents/WeChat Files/wxid_/FileStorage/Image/decodeimg5");
    }
 
    /**
    * 批量对文件进行解密处理
    * @param    inputFileDir 要解密的文件夹
    * @param    outputFileDir 解密后保存在哪个文件夹
    */
    public static void BatchDecodeFileContent(String inputFileDir, String outputFileDir){
        File f = new File(inputFileDir);
        if (!f.exists() || !f.isDirectory()){
            System.out.println("要解密的文件夹不存在!");
        }
         
        if (!outputFileDir.endsWith("/")){
            outputFileDir = outputFileDir.concat("/");
        }
        File o = new File(outputFileDir);
        if (!o.exists()){
            try {
                System.out.println("解密后保存的文件夹不存在,尝试创建...");
                o.mkdirs();
            catch (Exception e) {
                System.out.println("创建解密后保存的文件夹失败,程序终止执行");
                return;
            }
        }
         
        File[] fl = f.listFiles(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(".dat")){
                    return true;
                }
                return false;
            }
        });
        System.out.println("共找到 " + fl.length + " 个 dat 文件");
 
        if (fl.length == 0){
            System.out.println("没有需要解码的文件");
        }
        long begin = new Date().getTime();
        System.out.println("====开始解码====time:" + begin);
 
        Bom bom;
        FileInputStream fis;
        File of;
        FileOutputStream fos;
        FileChannel channelIn;
        FileChannel channelOut;
        ByteBuffer bs = ByteBuffer.allocate(1024 10);
        int rl;
        for (File file : fl){
            System.out.println(file.getAbsolutePath());
            try {
                fis = new FileInputStream(file);
                channelIn = fis.getChannel();
                bs.clear();
                rl = channelIn.read(bs);
                bs.position(0);
                bom = getFileBom(bs.array());
                if (bom.getXorVal() == 0x00 || bom.getExtn() == null){
                    System.out.println("获取加密的字节码失败");
                    continue;
                }
                of = new File(outputFileDir + file.getName() + bom.getExtn());
                fos = new FileOutputStream(of);
                channelOut = fos.getChannel();
                while(rl > 0){
                    for (int i = 0; i < rl; i++){
                        bs.put(i, (byte)(bs.get(i) ^ bom.getXorVal()));
                    }
                    channelOut.write(bs);
                    bs.clear();
                    rl = channelIn.read(bs);
                    bs.position(0);
                }
 
                channelIn.close();
                channelOut.close();
                fis.close();
                fos.close();
                 
            catch (Exception e) {
                 
            }
        }
         
        long end = new Date().getTime();
        System.out.println("decode4====" + fl.length + " 个dat文件解码完成====用时:" + (end - begin));
    }
 
    /**
     * 获取加密的字节码
     * @param buff 读取的文件的第一块,包含文件头的部分
     * @return
     */
    public static Bom getFileBom(byte[] buff){
        Bom bom = new Bom();
        if (buff.length < 2){
            return bom;
        }
        // jpeg
        if ((byte)(buff[0] ^ 0xFF) == (byte)(buff[1] ^ 0xD8)){
            bom.setXorVal((byte)(buff[0] ^ 0xFF));
            bom.setExtn(".jpeg");
             return bom;
        }
        // png
        if ((byte)(buff[0] ^ 0x89) == (byte)(buff[1] ^ 0x50)){ // Xor计算之后需要加上强制类型转换(byte),否则比较的时候会出现不相等的情况
             bom.setXorVal((byte)(buff[0] ^ 0x89));
             bom.setExtn(".png");
             return bom;
        }
        // bmp
        if ((byte)(buff[0] ^ 0x42) == (byte)(buff[1] ^ 0x4D)){
            bom.setXorVal((byte)(buff[0] ^ 0x42));
            bom.setExtn(".bmp");
             return bom;
        }
        // gif
        if ((byte)(buff[0] ^ 0x47) == (byte)(buff[1] ^ 0x49)){
            bom.setXorVal((byte)(buff[0] ^ 0x47));
            bom.setExtn(".gif");
             return bom;
        }
        // tif
        if ((byte)(buff[0] ^ 0x49) == (byte)(buff[1] ^ 0x49)){
            bom.setXorVal((byte)(buff[0] ^ 0x49));
            bom.setExtn(".tif");
             return bom;
        }
        System.out.printf("%x%x==%x%x", buff[0], buff[1],(buff[0] ^ 0x89),(buff[1] ^ 0x50));
        return bom;
    }
 
    // 文件头
    static class Bom {
        // 对文件加密解密使用的字节码
        byte xorVal = 0x00;
        // 文件扩展名
        String extn = null;
         
        public Bom() {
 
        }
        public byte getXorVal() {
            return xorVal;
        }
        public void setXorVal(byte xorVal) {
            this.xorVal = xorVal;
        }
        public String getExtn() {
            return extn;
        }
        public void setExtn(String extn) {
            this.extn = extn;
        }
         
    }
}

这个版本使用了 java.nio.channels.FileChannel,开始的时候设置Buffer大小为1M(1024*1024),但是性能不理想,经常尝试,1024 * 10 的性能比较好,应该是跟文件的大小有关系,大多数文件都在1M以内。4Tn无知

测试结果

decode2====1490 个dat文件解码完成====用时:28944Tn无知

decode2====1490 个dat文件解码完成====用时:27114Tn无知


4Tn无知

decode4====1492 个dat文件解码完成====用时:37704Tn无知

decode4====1492 个dat文件解码完成====用时:42824Tn无知

可以看出,使用 java.nio 的性能也没有显著的提高,反而变慢了,或者还是测试文件太少了;也可能与文件大小有关系,文件较大的话,估计差别会更明显一些。4Tn无知

版本5内存映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   /**
    * 使用直接缓冲区完成文件的复制(内存映射文件)
    * 耗费时间:142
    */
   private static void  nioCopyTest2() throws IOException {
       long startTime = System.currentTimeMillis();
 
       FileChannel inChannel = FileChannel.open(Paths.get("E:\\ 1.avi"), StandardOpenOption.READ);
 
       FileChannel outChennel = FileChannel.open(Paths.get("E:\\ 12.avi"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
 
       //内存映射文件(什么模式 从哪开始 到哪结束)
       MappedByteBuffer inMappeBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
       MappedByteBuffer outMappeBuf =  outChennel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
 
       //直接都缓冲区进行数据的读写操作
       byte[] dst = new byte[inMappeBuf.limit()];
       inMappeBuf.get(dst);
       outMappeBuf.put(dst);
 
       inChannel.close();
       outChennel.close();
       long end = System.currentTimeMillis();
       System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
   }

总结

经过测试发现,昨天的测试结果是缓冲区开到1M的时候性能比较稳定,4K、8K、100K、512K时性能都不稳定,时快时慢,超过1M的话就越大越慢。今天得到的结果是1024 * 10 的性能比较好,应该是跟文件的大小有关系,大多数文件都在1M以内。另外 BufferedInputStream, BufferedOutputStream,java.nio.channels.FileChannel,这些高大上的类并没有性能上的明显提升。4Tn无知

另外参考:java四种文件读写方式及性能比较 文章中的测试结果:4Tn无知

文件大小读写方式耗时
30M普通文件流50-60 ms
缓存流32-35 ms
随机文件方式40-50 ms
内存映射文件50-60 ms
461M普通文件流1300-2300 ms
缓存流1700-2000 ms
随机文件方式1300-3000 ms
内存映射文件890-1000 ms
1.47G普通文件流11s
缓存流9s
随机文件方式10s
内存映射文件3s(首次较慢)


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知


4Tn无知

从他的结果来看,也是当文件较大的时候,才有性能上的明显提升。所以性能的提升需要根据文件的大小来确认具体使用哪一种方案。4Tn无知

本文由 微wx笑 创作,采用 署名-非商业性使用-相同方式共享 4.0 许可协议,转载请附上原文出处链接及本声明。
原文链接:https://www.ivu4e.cn/blog/lang/2021-07-31/710.html

很赞哦! (22) 有话说 (0)

文章评论