【问题标题】:Android: how to have random access from an InputStream?Android:如何从 InputStream 进行随机访问?
【发布时间】:2013-10-17 02:16:15
【问题描述】:

我有一个 InputStream,以及相关的文件名和大小。

我需要访问/读取 InputStream 中的一些随机(增加)位置。这个位置存储在一个整数数组中(命名为偏移量)。

InputStream inputStream = ...

String fileName = ...
int fileSize = (int) ...

int[] offsets = new int[]{...};  // the random (increasing) offsets array

现在,给定一个 InputStream,我发现只有两种可能的解决方案可以跳转到文件的随机(增加)位置。

第一个是使用InputStreamskip()方法(注意我实际使用BufferedInputStream,因为我需要mark()reset()文件指针)。

//Open a BufferInputStream:
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

byte[] bytes = new byte[1];

int curFilePointer = 0;
long numBytesSkipped = 0;
long numBytesToSkip = 0;
int numBytesRead = 0;

//Check the file size:
if ( fileSize < offsets[offsets.length-1] ) {  // the last (bigger) offset is bigger then the file size... 
    //Debug:
    Log.d(TAG, "The file is too small!\n");

    return;
}

for (int i=0, k=0; i < offsets.length; i++, k=0) {  // for each offset I have to jump...
    try {
        //Jump to the offset [i]:
        while( (curFilePointer < offsets[i]) && (k < 10) ) {  // until the correct offset is reached (at most 10 tries)
            numBytesToSkip = offsets[i] - curFilePointer;
            numBytesSkipped = bufferedInputStream.skip(numBytesToSkip);

            curFilePointer += numBytesSkipped;  // move the file pointer forward

            //Debug:
            Log.d(TAG, "FP: " + curFilePointer + "\n");

            k++;
        }

        if ( curFilePointer != offsets[i] ) {  // it did NOT jump properly... (what's going on?!)
            //Debug:
            Log.d(TAG, "InputStream.skip() DID NOT JUMP PROPERLY!!!\n");

            break;
        }

        //Read the content of the file at the offset [i]:
        numBytesRead = bufferedInputStream.read(bytes, 0, bytes.length);
        curFilePointer += numBytesRead;  // move the file pointer forward

        //Debug:
        Log.d(TAG, "READ [" + curFilePointer + "]: " + bytes[0] + "\n");
    }
    catch ( IOException e ) {
        e.printStackTrace();

        break;
    }
    catch ( IndexOutOfBoundsException e ) {
        e.printStackTrace();

        break;
    }
}

//Close the BufferInputStream:
bufferedInputStream.close() 

问题是,在我的测试中,对于一些(通常是大的)偏移,它在跳过正确的字节数之前已经循环了 5 次或更多次。正常吗?而且,最重要的是,我可以/应该推进 skip() 吗? (即:10 个周期是否足以确定它总是会到达正确的偏移量?)

我发现的唯一替代方法是通过File.createTempFile(prefix, suffix, directory) 和以下函数从InputStream 创建RandomAccessFile

public static RandomAccessFile toRandomAccessFile(InputStream inputStream, File tempFile, int fileSize) throws IOException {
    RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, "rw");

    byte[] buffer = new byte[fileSize];
    int numBytesRead = 0;

    while ( (numBytesRead = inputStream.read(buffer)) != -1 ) {
        randomAccessFile.write(buffer, 0, numBytesRead);
    }

    randomAccessFile.seek(0);

    return randomAccessFile;
}

拥有一个 RandomAccessFile 实际上是一个更好的解决方案,但性能会呈指数级下降(首先是因为我将拥有多个文件)。


编辑: 使用 byte[] buffer = new byte[fileSize] 加速(并且很多)RandomAccessFile 创建!


//Create a temporary RandomAccessFile:
File tempFile = File.createTempFile(fileName, null, context.getCacheDir());
RandomAccessFile randomAccessFile = toRandomAccessFile(inputStream, tempFile, fileSize);

byte[] bytes = new byte[1];
int numBytesRead = 0;

//Check the file size:
if ( fileSize < offsets[offsets.length-1] ) {  // the last (bigger) offset is bigger then the file size...
    //Debug:
    Log.d(TAG, "The file is too small!\n");

    return;
}

for (int i=0, k=0; i < offsets.length; i++, k=0) {  // for each offset I have to jump...
    try {
        //Jump to the offset [i]:
        randomAccessFile.seek(offsets[i]);

        //Read the content of the file at the offset [i]:
        numBytesRead = randomAccessFile.read(bytes, 0, bytes.length);

        //Debug:
        Log.d(TAG, "READ [" + (randomAccessFile.getFilePointer()-4) + "]: " + bytes[0] + "\n");
    }
    catch ( IOException e ) {
        e.printStackTrace();

        break;
    }
    catch ( IndexOutOfBoundsException e ) {
        e.printStackTrace();

        break;
    }
}

//Delete the temporary RandomAccessFile:
randomAccessFile.close();
tempFile.delete();

现在,是否有更好(或更优雅)的解决方案来从 InputStream 进行“随机”访问?

【问题讨论】:

  • 好的,关于我是否应该信任 skip() 方法的第一个问题,根据 Oracle 文档:“由于各种原因,skip 方法最终可能会跳过一些较小的字节数,可能为 0。这可能是由多种条件中的任何一种造成的;在跳过 n 个字节之前到达文件末尾只是一种可能性。链接:docs.oracle.com/javase/7/docs/api/java/io/…
  • 你从哪里得到 InputStream?
  • 来自 ZIP 文件:ZipFile.getInputStream()
  • 实际上,我刚刚意识到我可以使用与文件大小相同的缓冲区来加速(并且很多)RandomAccessFile 的创建(这将减少 I/O 访问...... )!

标签: java android random-access bufferedinputstream randomaccessfile


【解决方案1】:

不幸的是,您以InputStream 开头,但在这种情况下,如果您总是向前跳过,则在文件中缓冲流是没有用的。但是你不必计算你给skip打了多少次电话,这不是很感兴趣。

必须检查流是否已经结束,以防止无限循环。检查默认skip 实现的source,我想说你必须继续调用skip,直到它返回0。这将表明已经到达流的结尾。根据我的口味,JavaDoc 对此有点不清楚。

【讨论】:

  • 正如我所说,在某一时刻我需要标记()和重置()位置,即我需要前后移动。这就是我使用 BufferedInputStream 的原因(标准 InputStream 不提供此功能)。关于计算 skip() 的数量,我这样做是为了解决性能问题。在开始 for(对于每个偏移量)之前,我检查最大偏移量是否在文件大小之内(小于)。因此,理论上,我可以随意跳过,而不必担心 EoF。但是,老实说,防止它执行过多的 skip() 是很好的......
【解决方案2】:

你不能。 InputStream 是一个流,也就是说一个顺序构造。你的问题体现了一个矛盾的术语。

【讨论】:

  • 它实际上只有在你阅读最后一个问题(我在其中使用了“随机访问”这个词)而不阅读整个问题的情况下才会这样做......事实上,在我的问题中你可以已经找到一种从 InputStream 创建 RandomAccessFile 的方法(我的问题更多是关于性能问题)。
猜你喜欢
  • 2014-07-07
  • 1970-01-01
  • 2014-04-11
  • 1970-01-01
  • 1970-01-01
  • 2016-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多