现在需要从一个文件中读取数据到一个数组中,数组的大小要到读完文件才能确定大小。最终结果可能数组会比较大也可能比较小,因此需要动态的分配一个数组。如果先分配100个长度的数组,如果读取时发现数组小了,就分配一个长度为200的数组,还小就分配个长度为300。将前一个数组拷贝过来,并丢弃前一个数组。结束时,再将得到的数组多余部分裁掉。这样的方法似乎比较低效,并且可能会比较消耗内存,因此想请问各位有没有比较高效的方法解决这个问题?

解决方案 »

  1.   

    参考一下ArrayList的实现吧,或者直接用。一般采用二倍的扩充方式
      

  2.   


    如果使用数据结构,由于java没有指针,要使用类实现,这样将更低效。
      

  3.   


    我的实现方法就是参照了ArrayList实现,ArrayList本来就比较低效。采用二倍扩容的话会比较耗内存,而且这个数值应该不用二倍怎么多,所以采用每次增加100。
      

  4.   

    元素填充完毕之后调trimToSize()去除多余内存。
      

  5.   

    如果你喜欢研究的话那就看看。
    System类的静态方法吧,
    arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 
      

  6.   


    最简单的办法,首先你一切一切问题的源头是你不知道该分配多大的byte数组.对吧.
    反过来说,如果你知道该分多大,一切一切的后面的考虑都不需要了. 所以那么就有
    public static byte[] readFully(File f) throws IOException {
    RandomAccessFile  raf = new RandomAccessFile(f, "r");

    // detertmine file length;  if the file is too long, only retreive first Integer.MAX_VALUE
    long llen;
    int len;
    llen = raf.length();
    if (llen > Integer.MAX_VALUE) {
    len = Integer.MAX_VALUE;
    }else {
    len = (int) llen;
    }

    byte[] bs = new byte[len];
    raf.readFully(bs);
    raf.close();
    return bs;
    }
    如果你真的不知道大小,那ArrayList *2的方案其实比你想象的有效。你可以先试一试
      

  7.   


    我没有说高效,只是每次加100可以更节省内存。实际情况是读取一个png图片,而其中的IDAT块可能会有多块,但IDAT的块应该不会太大。测试了一个1920 * 5384的图片,有104块。当然这可能是个特例,再大应该也不会大到哪里去。因此采用每次加100。有哪位知道png的IDAT块是否有最大限制?
      

  8.   


    其中每个IDAT的大小也可能不固定。(IDAT看上面的解释)
      

  9.   

    如果有人有需要了解png格式的话可以看
    http://book.51cto.com/art/200903/112741.htm
    这个相当详细。
      

  10.   

    File的话,可以直接通过 file.length()得到文件大小,然后一次性分配好内存空间,然后载入13楼已经说的很清楚了
      

  11.   


    就平均情况而言,ArrayList中的二倍扩充一定比你每次增加100效率要高的多。
    (1) 当数据数量比较少的情况下,每次扩充两倍也不会消耗多少内存的。逼近小数据量 乘以2 也不会大到哪去。
    (2) 如果数据量比较大的话,那么开辟新数组并转移老数组的数据这个过程严重制约算法的效率。而每次增加100一定会照成数据转移(System.arraycopy)的次数远远大于2倍增长。或许真假100在运行过程中节约内存浪费的概率比较大。但是程序运行完毕以后也不会有太多影响,我们可以使用trimToSize()方法。
      

  12.   

    难道你在实现链表时node不是类似
    public class IntNode{
      public int info;
      public IntNode next;
      public IntNode(int i){
        this(i);
      }
      public IntNode(int i, IntNode n){
         info = i;
         next = n;
      }}
    实现,如果不是倒要请教楼上使用的是什么方法.创建对象时都要new,这当然都需要时间的。
    数组是号称对象,但在底层实现的效率肯定要比上层实现的高。
      

  13.   

    “数组是号称对象,但在底层实现的效率肯定要比上层实现的高”
    没看Java源代码,这也是猜测。但对象里不含另一对象,肯定效率会更高。
      

  14.   

    你是希望每一块都分别存到一个byte[]里去么?
    按照PNG的格式,每一块的数据都是有规律的(除了PNG标记头的8字节外)
    对于每一块,先是4字节的长度,然后是4字节块标志(比如'IDAT','IHDR')
    然后是块内容,长度即为前面4字节标志的长度,然后是4字节的CRC32校验码因此就可以通过先读长度,然后分配好byte[]的长度,然后读取即可
      

  15.   

    对于多个 IDAT 块,用LinkedList来保存即可
      

  16.   

    你不是说ArrayList低效吗 每次加100可能节省了内存但是降低了效率,总资源消耗量可能更大
    18楼有说,另外就像他们说的,如果能获得所需资源大小就直接分配了
      

  17.   


    现在的问题就是事先不知道数组将是多大,否则也就没有这一问题了。因此png文件格式在这方面不太合理。像bmp就有标识数据大小的标识。Java手册里有:
    add 操作以分摊的固定时间 运行,也就是说,添加 n 个元素需要 O(n) 时间。
    O(n)的时间复杂度就是比较低效的。记得看过C++里面实现ArrayList就是以翻倍的形式扩展数组。
      

  18.   

    在java,集合类的很多实现类当中,到处可见数据接口扩容的代码,常用的都是*2。另外使用native方法copy数组内容,基本已经达到了最佳速度了。
    public static native void arraycopy();
    至于内存,java本身就是个耗内存的主,呵呵。
      

  19.   

    现在就把问题数据输入限定在未知前提吧有个疑问,如果O(n)算低效的话,那有没有一种数据结构添加n个元素时间复杂度是O(1)级别的呢?刚才看了ArrayList的实现,发现不是×2,如下(我认为虽然没办法绝对有效证明ArrayList是不是整体效率最高,但既然是jdk,应该还是权威的,或者举出反例,这个更有意义了,这个前面有人阐述过了)/**
         * Increases the capacity of this <tt>ArrayList</tt> instance, if
         * necessary, to ensure that it can hold at least the number of elements
         * specified by the minimum capacity argument.
         *
         * @param   minCapacity   the desired minimum capacity
         */
        public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
             if (newCapacity < minCapacity)
    newCapacity = minCapacity;
                // minCapacity is usually close to size, so this is a win:
                elementData = Arrays.copyOf(elementData, newCapacity);
    }
        }
      

  20.   


    算法时间复杂度是常数级O(1)或对数级O(log)比如对半查找。那是最好的。如果已知数组的长度分配内存就是常数级的。ArrayList的实现应该有多种方法,Java可能不是*2,这要看源代码。呵呵。
      

  21.   

    O(1) O(logn)是要比O(n)快 但动态分配这种时间复杂度有吗,考虑考虑这方面挺有好处的,即使最后“未果”但数组初始化分配的时候是常数级,但添加元素呢?不也是O(n)吗 况且不能增长
      

  22.   


    当然有。我前面说了。Java手册里ArrayList的add方法有说明:
    add 操作以分摊的固定时间运行,也就是说,添加 n 个元素需要 O(n) 时间。数组在已知长度分配空间是常量级,当然不包括动态添加。所以就是要问一下有没有更高效的。或者是我所说的实际情况下,读取png文件的IDTA情况下有没有比动态分配空间更高效
      

  23.   

    不确定,是不是每块都是长度标识,
    如果是的话,前面说的#23就好了,
    要不,你也可也考虑一个,每100个字节一个单位数组,不够的话创建另外一个 100 字段的数组,用另外一个 LinkedList 把这些数组装起来。这样就不需要在内存中复制数据。
      

  24.   

    Java 中有很多 I/O 类有 Filter Stream,可以考虑模仿它们,把几个物理上不连接但逻辑上能拼接在一起的数组用一个 Filter InputStream/OutputStream 来处理,让客户看起来它是在读写一个拼接后完整的数组。这样的话,上面每 100 个字节一个数组,所有数组用 LinkedList 串起来就有可行性。
      

  25.   


    如果我理解的没错,你现在的情况有两点.
    第一个是 一个png文件里一共有多少chunk,无法知道.
    第二个是每一个chunk的大小都不一样。这两个都是未知大小,所以你需要分别考虑这两种未知大小的分配。第一个没有很好的办法,因为png格式设计来的就是用链的形式的来存储块,png的结束是用一个IEND块来表式的。所以你没可能一上来就知道大小,而*只能*选取一个容器来跟踪。希望你同意这个观点.那么用容器跟踪的话,紧接着那么一般最关心的确就是容器的效率问题。首先有一点,对增,删,查,改,任何一个容器(这里是List)的实现这几个操作的效率都不会同时最高。
    要么增,删好,查改差, 要么查改好,增删差。我想个你也同意.这样的话,那么这就是一个权衡,你要看看你的case到底是想到什么,天下没有免费的午餐,你要这个就得牺牲那个。你有的只是选择权. 下面这个三个是,纯大多数的实用的选择范围
    java.util.ArrayList - 数组实现
    java.util.LinkedLIst - 链表实现
    org.apache.commons.collections.list.TreeList - 自平衡二叉树实现  O(log n) for add/remove它们的比较测试数据如下,
    [code]
                  get  add  insert  iterate  remove
     * TreeList       3    5       1       2       1
     * ArrayList      1    1      40       1      40
     * LinkedList  5800    1     350       2     325
    [/code]
    你看看你的case,选一个吧 :P
    第二个问题相对来说比较简单,一个块的大小不是固定的这个没有错。
    但是png块的设计里,一个块的长度可以很容易知道。你那个link.
    [code]
    块数据块定义 :  **块数据长度(4bytes)**,块标志(4bytes),块数据, 块CRC校验码(4bytes)
    [/code]那这样就是知道大小的情形, 就比较好办. 你定义一个Chunk类,直接读成byte[].
    直接用问题1里你选好的List<Chunk>来来跟踪读入就好示意代码:
    class Chunk {int size;
    int flag;
    int crc;
    byte[] data;    void read(LittleEndianObjectInputStream ois) {
            size = ois.readInt();
            flag = ois.readInt();
            data = new byte[size];
            ios.readFully(data);
            crc = ios.readInt();
        } }public static List<Chunk> readPngFile(LittleEndianObjectInputStream is) {
            readPngFileHeader(is);
            Chunk c;
            List<Chunk> chunks = new TreeList<Chunk>(); // 我这里用TreeSet
            while(true) {
                c = new Chunk();
                c.read(is);
                chunks.add(c);
                if (c.size==0) // we met the IEND 
                     break;
           }
           return chunks
     }
    我想你应该是真正来寻找你的问题答案来的,希望我的回复对你有用.
      

  26.   

    csdn的code标签还不太会用.补上测试数据。你参考下它们的比较测试数据如下,
     *              get  add  insert  iterate  remove
     * TreeList       3    5       1       2       1
     * ArrayList      1    1      40       1      40
     * LinkedList  5800    1     350       2     325
      

  27.   


    感谢热心回复。你的理解没错。我是想知道是否有比ArrayList方式更高效的方法。我感觉没必要使用ArrayList一样会很简单。判断一下数据是否超出,超出利用一个数组和System.arraycopy调整一下。最后再利用System.arraycopy裁剪一下就可以了。效率应该与ArrayList差不多。*            get      add     insert    iterate     remove
    * TreeList    3         5        1         2          1
    * ArrayList   1         1        40        1          40
    * LinkedList 5800       1        350       2          325这个数据里我觉得TreeList里的insert、remove操作(单纯操作不包括查找节点)应该和LinkedList里的一样。差异应该在查找节点上。
      

  28.   


    这个不就是 ArrayList的实现方面吗:) ,你愿意自己写一个也行啊
    应该是的。 LinkedList的查找很费时,而且add虽然很好,但insert的时候也要先查找再add.不知道你是否会有 insert/remove操作 , 如果只是add和 iterate操作的话, LinkedList无疑是最好的选择.另外大部分情况下ArrayList都是很好 的选择。除了一个情况:
    就是你有大量的中间元素的insert和remove,这个时候TreeList优于ArrayList.看TreeList里的关于 三个list的说明。
     * <code>ArrayList</code> is a good general purpose list implementation.
     * It is faster than <code>TreeList</code> for most operations except inserting
     * and removing in the middle of the list. <code>ArrayList</code> also uses less
     * memory as <code>TreeList</code> uses one object per entry.
     * <p>
     * <code>LinkedList</code> is rarely a good choice of implementation.
     * <code>TreeList</code> is almost always a good replacement for it, although it
     * does use sligtly more memory.
      

  29.   


    不涉及add、remove这么多操作,只是读取数据,所以就没用ArrayList。
      

  30.   


    确定了数组长度以后不变了的,分配就是O(1)。ArrayList里add是每次操作是O(1),而n次add操作就是O(n)。
      

  31.   

    是不是O(1)和长度变不变有关系吗?
    数组除非是引用赋引用这样算O(1),如
    int[] a = {...};
    int[] b = {...};
    a = b;
    其它情况如果单个元素逐个赋值,n个元素不也是O(n)吗?
      

  32.   


    不要盯住一个n一个数组确定长度比如100,那O(100)其实就是O(1)在算法复杂度里没有O(100)这样的说法,那是便于理解写的O(100),所以就算O(1000)也是O(1),只要O(n)中的n是确定的,那O(n)就变为常量级的。常量级的时间复杂度就是算法每次花费的时间是固定的。
      

  33.   

    回到这个问题,一旦确定选用了动态增长的数据结构的话,其添加元素的时间复杂度肯定不会是常数级了,那剩下就有对数级比O(n)快了,想想ArrayList为什么是O(n),是慢在什么地方呢,ArrayList为什么不能是O(logn)呢,这其中的tradeoff是不是简单的时间换空间,以按倍数的增长和按常数相加增长方式的区别,按常数相加增长方式有可能还导致更高的时间复杂度~ArrayList是不是已经到达或接近那个平衡的点了呢
      

  34.   


    唉,不看你们形而上学了……TreeList 就是对数级的,  二叉平衡查找是对数级的
    LinkedList  是对直接append和iterate是O(1) ,  定点insert是O(n)  
    ArrayList 根据你的增长算法求均算算看吧。言止于此
      

  35.   

    java 反射类应该提供了动态创建数组的功能:java.lang.reflect.Array;