Android利用FileChannel高效写float数组(大量数据)到文件,读文件到float数组

        在项目中遇到解析模型数据出现一大堆float数据,处理float数组数据非常耗时间,为了更快显示模型,方案是快速读取已经解析好的数据文件,并转为float数组使用。难点即是:如何快速高效写float数组到文件,并且能够快速读取文件到数组。以下是本人尝试几种方案,作对比:

模型文件为:28-2.stl;大小:1722984B;

方法一:以二进制读写;

写float[]数组vArr到文件“/mnt/sdcard/binary.txt”

 private void saveFloatToFile(float[] vArr, String gcodeFile) {
        FileOutputStream fos = null;
        DataOutputStream dos = null;
        // create file output stream
        try {
            fos = new FileOutputStream(gcodeFile);
            dos = new DataOutputStream(fos);// create data output stream
            for (float f : vArr) {// for each byte in the buffer
                // write float to the data output stream
                dos.writeFloat(f);
            }
            dos.flush();// force bytes to the underlying stream
            dos.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

从文件中读取内容到float[]数组:

 private float[] getFloatFromFile(String gcodeFile, int Count) {
        float[] verts = new float[Count * 3 * 3];
        InputStream is = null;
        DataInputStream dis = null;
        try {
            is = new FileInputStream(gcodeFile);// create file input stream
            dis = new DataInputStream(is);// create new data input stream
            int i = 0;
            while (dis.available() > 0) {// read till end of the stream
                verts[i] = dis.readFloat();// read character
                i++;
            }
            is.close();
            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return verts;
    }

通过System.currentTimeMillis()来获取当前时间,来计算写数组和读数组耗时,并输出log,

写时间:

long time = System.currentTimeMillis();
saveFloatToFile(verts, "/mnt/sdcard/binary.txt");
Log.d("StlLoadListener", "write time: " + (System.currentTimeMillis() - time));

读时间:

long time = System.currentTimeMillis();
verts = getFloatFromFile("/mnt/sdcard/binary.txt", facetCount);//binary
Log.d("StlLoadListener", "read time : " + (System.currentTimeMillis() - time));

运行后,可以知道保存后文件大小:1240488B

写文件花费时间6815ms,读时间10237ms,可想而知,效率非常低,主要原因在于每次读取一个float数赋给数组,而有1Mfloat数据,需要读取次数就很庞大。

        这个时候我们会考虑是否可以将整个数组以一个对象写到文件,然后直接读出来就是该数组?

方法二:序列化;

        在Java应用程序当中对类进行序列化操作只需要实现Serializable接口就可以,由系统来完成序列化和反序列化操作。

接口:

class ModelArray implements Serializable {
    float[] array;
    public ModelArray(float[] array) {
        this.array = array;
    }
}

序列化:

private  void saveFloatToObject(float[] verts, String gcodeFile) {
        ModelArray stu1 = new ModelArray(verts);
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(new File(gcodeFile)));
            os.writeObject(stu1);
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

反序列化:

    private float[] getFloatFromObject(String gcodeFile, int Count) {
        float[] verts = new float[Count * 3 * 3];
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream(new File(gcodeFile)));
            verts = ((ModelArray) is.readObject()).array;
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return verts;
    }

运行后,可以知道保存后文件大小:1240592B

写文件花费时间6301ms,读时间5361ms,虽然较二进制读改观一半时间,但是显然不是我们所想要的,主要原因在于:在序列化操作的时候会产生大量的临时变量,(使用了反射机制)从而导致GC的频繁调用性能会降低。

        那么有什么办法可以直接进行类似内存复制一样,撇开是什么数据类型的,更高效的float数组与文件之间操作吗?

方法三:NIO中的FileChannel;

官方说明:一个读,写,映射,操作文件的通道。

        文件通道有可以被查询和修改的一个当前位置,文件本身包含了一个可悲读写的变长字节序列,并且它的当前的size会被查询。当被写入的字节超过当前文件的大小时,文件的大小会增加;当文件被截断的时候,文件的大小会减少。文件有一个写被关联的像权限,内容类型和最后修改时间的元数据;这个类没有定义访问元数据的方法。
     此外对于熟悉的字节通道读写关闭操作,这个类定义了以下的特定文件操作:
    1.在文件的绝对位置的字节读写操作在某种程度上不会影响通道的当前位置。
    2.文件的区域可能会被直接映射到内存中,对于大文件来说,这比通常的读写方法更有效。
    3.为了保证数据在系统崩溃之后不丢失数据,文件的修改模式会被强制到底层存储设备。
    4.字节能从一个文件被转换为一些其他的通道,反之亦然,这种操作在某种程度上会被许多操作系统或者文件系统优化成一个非常快速的直接传输。
    5.文件的区域也许会被锁住来防止其它程序的访问。
    文件通道在多线程下是安全的,close方法能在任何时候被调用,这个方法在接口Channel中被指定。在任何时间,只有涉及到通道的position和改变它的文件的size的过程中的操作,当第一个这样的方法在处理中时尝试初始化第二个这样的操作将被阻塞,知道第一个操作完成。其它的操作,特别是显式的获取position,会被同步处理;它们是否事实上做了同步,这样以来于底层操作系统的实现,因此是未特别指定的。
    文件的视图通过这个类的实例被提供,这个视图确保跟在相同程序中的被其它实例提供的相同文件视图的一致性。这个类的实例也许会也许不会提供视图,通过其他的同步程序保持视图的可见性,应归于被执行的底层操作系统和网络文件系统延迟的引导的缓存。不管语言在其它程序中写,这是事实,而且不管它们运行在相同的机器上还是不同的机器上,任何这样的不一致的确切性质是依赖于系统,因此,未特指。
    这个类没有定义打开文件或者创建一个新文件的方法;这些方法也许在未来的发行版中被添加。像这样的文件通道可以通过
FileInputStream,FileOutputStream,RandomAccessFile类中的getChannel方法获得,这个getChannel返回的文件通道是被连接到相同的底层文件的。
    文件通道的状态与通过getChannel方法返回的channel对象是紧密相连的,如果明确的改变文件通道的position或者读写字节,则会改变原始对象的文件position,反之亦然。通过文件通道改变文件通道的长度将改变原始对象的文件长度,反之亦然。改变文件通道的内容也会改变原始对象的文件内容,反之亦然。
    这个类指定了个别的指针,像“只读打开”,“只写打开”,“读写打开”。通过FileInputStream的getChannel方法获取的文件通道是只读的,当然通过FileOutputStream的getChannel的方法获取的文件通道是可写的,通过RandomAccessFile的
getChannel的方法获取的文件通道,在创建时如果传递的参数是“r",则为只读,否则为”读写“或者“写”。
    文件通道也许在追加模式下被打开,

例如它的获得是通过一个给FileOutputStream(boolean)或者FileOutputStream(FIle,boolean)传递参数true时,则这个通道是在追加模式下。在这种模式下,每个相对写方法的调用操作首先会把position增长到文件尾部,然后写入被请求的数据。是否文件position的增长和数据的写入在原子操作下被完成是依赖于操作系统的,并为特指。

直接看代码,写Data:

 private void writeFloatToData(float[] verts, String gcodeFile, int count) {
        try {
            RandomAccessFile aFile = new RandomAccessFile(gcodeFile, "rw");
            FileChannel outChannel = aFile.getChannel();
            //one float 4 bytes
            ByteBuffer buf = ByteBuffer.allocate(4 * count * 3 * 3);
            buf.clear();
            buf.asFloatBuffer().put(verts);
            //while(buf.hasRemaining())
            {
                outChannel.write(buf);
            }
            //outChannel.close();
            buf.rewind();
            outChannel.close();
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
        }
    }

读Data:

private float[] readFloatFromData(String gcodeFile, int Count) {
        float[] verts = new float[Count * 3 * 3];
        try {
            RandomAccessFile rFile = new RandomAccessFile(gcodeFile, "rw");
            FileChannel inChannel = rFile.getChannel();
            ByteBuffer buf_in = ByteBuffer.allocate(3 * 3 * Count * 4);
            buf_in.clear();
            inChannel.read(buf_in);
            buf_in.rewind();
            buf_in.asFloatBuffer().get(verts);
            inChannel.close();
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
        }
        return verts;
    }

运行后,可以知道保存后文件大小:1240488B(和二进制方法一样)

写文件花费时间8ms,读时间29ms,可想而知,这是神速,哈哈哈~

 

码字不易,转载请说明,谢谢~

 

 

 

 

 

 

 

你可能感兴趣的:(android)