1.随机文件访问接口
对MP3解码时需要随机读取MP3文件,读取的文件既包括本地磁盘文件,也包括来自于网络的远程文件,两类文件按同一规范访问,为了实现这一目标,先定义一个随机文件访问接口,IRandomAccess.java,源码如下:
/*
* IrandomAccess.java -- 随机访问文件接口
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package instream;
public interface IRandomAccess {
public int read() throws Exception;
public int read(byte b[]) throws Exception;
public int read(byte b[], int off, int len) throws Exception;
public int dump(int src_off, byte b[], int dst_off, int len) throws Exception;
public void seek(long pos) throws Exception;
public long length();
public long getFilePointer();
public void close();
}
2.读/写通信接口
从远程文件读取的数据写入缓冲区,由于读/写位于不同的线程,为了实现读/写同步,定义读/写通信接口IWriterCallBack.java,源码如下:
/*
* IWriterCallBack.java -- 读/写通信的接口
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package instream;
/*
* 读/写通信的接口.类似于C++的回调函数
*
* 例:
* class BuffRandAcceURL 内实现本接口的方法tryWriting()
* class BuffRandAcceURL 内new Writer(this, ...)传值到Writer.objIWCB
* class Writer 内调用objIWCB.tryWriting()
*/
public interface IWriterCallBack {
public int tryWriting() throws InterruptedException;
public void updateBuffer(int i, int len);
public void updateWriterCount();
public int getWriterCount();
public void terminateWriters();
}
这里的读/写通信的接口类似于C++的回调函数功能:
class BuffRandAcceURL内实现本接口的方法tryWriting()
class BuffRandAcceURL 内new Writer(this, ...)传值到Writer.objIWCB
class Writer 内调用objIWCB.tryWriting()
3.带缓冲区的本地文件随机读取
以缓冲方式读取文件的效率肯定比不带缓冲高,JAVA类不提供带缓冲区的文件读写,没有现成的可用,只有自己构造。缓冲的方法简单讲就是从文件读取一块数据扔进缓冲区byteInBuf[],如何对缓冲区进行简单而有效的管理,是一个值得深入探讨问题,我的方法只是问题的一种可行解,并不是最优的。BuffRandAcceFile.java源码:
/*
* BuffRandAcceFile.java -- 带缓冲区的本地文件随机读取
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package instream;
import java.io.RandomAccessFile;
import java.io.IOException;
import tag.ID3Tag;
public class BuffRandAcceFile implements IRandomAccess {
private static final int DEFAULT_BUFSIZE=2048;
private static RandomAccessFile rafCurFile;
private byte byteInBuf[];
private long longStartPos;
private long longEndPos = -1;
private int intBufSize;
private long longCurPos;
private boolean boolBufDirty;
private long longFileEndPos;
private int intBufUsedSize;
private long longBufSizeMask;
private long longFileSize;
public BuffRandAcceFile(String strName) throws Exception {
this(strName, DEFAULT_BUFSIZE);
}
public BuffRandAcceFile(String strName, int intBufSize) throws Exception {
rafCurFile = new RandomAccessFile(strName, "r");
this.intBufSize = intBufSize;
byteInBuf = new byte[intBufSize];
longFileEndPos = rafCurFile.length() - 1;
this.longBufSizeMask = ~((long) this.intBufSize - 1L);
this.boolBufDirty = false;
long lFrameOffset = 0; //第一帧的偏移量
long lFrameSize = longFileSize = rafCurFile.length();
ID3Tag objID3Tag = new ID3Tag();
byte[] byteTmpBuf = new byte[128];
//ID3 v2
this.seek(0);
this.read(byteTmpBuf, 0, 10);
int size_v2 = objID3Tag.checkID3V2(byteTmpBuf,0);
if (size_v2 > 0) {
lFrameOffset = size_v2;
lFrameSize -= size_v2;
size_v2 -= 10; // header: 10 bytes
byte[] b = new byte[size_v2];
this.read(b, 0, size_v2);
objID3Tag.parseID3V2(b,0);
b = null;
}
//ID3 v1
this.seek(longFileSize - 128);
this.read(byteTmpBuf);
if(objID3Tag.checkID3V1(byteTmpBuf)) {
lFrameSize -= 128;
objID3Tag.parseID3V1(byteTmpBuf);
}
this.seek(lFrameOffset);
objID3Tag.printTag();
objID3Tag.destroy();
objID3Tag = null;
byteTmpBuf = null;
}
public int read(long pos) throws IOException {
if (pos < this.longStartPos || pos > this.longEndPos) {
this.flushbuf();
this.seek(pos);
if ((pos < this.longStartPos) || (pos > this.longEndPos)) {
return -1;
}
}
this.longCurPos = pos;
return this.byteInBuf[(int) (pos - this.longStartPos)] & 0xff;
}
public int read() throws IOException {
int iret = byteInBuf[(int) (this.longCurPos - this.longStartPos)] & 0xff;
this.seek(this.longCurPos + 1);
return iret;
}
public int read(byte b[]) throws IOException {
return this.read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
long readendpos = this.longCurPos + len - 1;
if (readendpos <= this.longEndPos && readendpos <= this.longFileEndPos) {
System.arraycopy(this.byteInBuf, (int) (this.longCurPos - this.longStartPos),
b, off, len);
} else {
if (readendpos > this.longFileEndPos) {
len = (int) (rafCurFile.length() - this.longCurPos + 1);
if(len <= 0)
return -1;
}
// if(this.longCurPos != rafCurFile.getFilePointer()) { //增加一条判断
rafCurFile.seek(this.longCurPos);
// System.out.println("1. rafCurFile.seek("+longCurPos+")");
// }
len = rafCurFile.read(b, off, len);
readendpos = this.longCurPos + len - 1;
}
this.seek(readendpos + 1);
return len;
}
/*
* 从当前位置复制,不移动文件"指针"
*/
public int dump(int src_off, byte b[], int dst_off, int len) throws IOException {
long rpos = this.longCurPos + src_off;
long readendpos = rpos + len - 1;
if (readendpos <= this.longEndPos && readendpos <= this.longFileEndPos) {
System.arraycopy(this.byteInBuf, (int) (rpos - this.longStartPos), b, dst_off, len);
} else {
if (readendpos > this.longFileEndPos) {
len = (int) (rafCurFile.length() - rpos + 1);// ???????
if(len <= 0)
return -1;
}
rafCurFile.seek(rpos);
len = rafCurFile.read(b, dst_off, len);
rafCurFile.seek(this.longCurPos);
}
return len;
}
public long length() {
return longFileSize;
}
public void seek(long pos) throws IOException {
if ((pos < this.longStartPos) || (pos > this.longEndPos)) {
this.flushbuf();
if ((pos >= 0) && (pos <= this.longFileEndPos)
&& (this.longFileEndPos != 0)) {
this.longStartPos = pos & this.longBufSizeMask;
this.intBufUsedSize = this.fillbuf();
} else if (((pos == 0) && (this.longFileEndPos == 0))
|| (pos == this.longFileEndPos + 1)) {
this.longStartPos = pos;
this.intBufUsedSize = 0;
}
this.longEndPos = this.longStartPos + this.intBufSize - 1;
}
this.longCurPos = pos;
}
public long getFilePointer() {
return this.longCurPos;
}
public void close() {
try {
rafCurFile.close();
} catch (IOException e) {}
}
private int fillbuf() throws IOException {
rafCurFile.seek(this.longStartPos);
//System.out.println("2. rafCurFile.seek("+this.longStartPos+")");
this.boolBufDirty = false;
return rafCurFile.read(this.byteInBuf);
}
private void flushbuf() throws IOException {
if (this.boolBufDirty == true) {
if (rafCurFile.getFilePointer() != this.longStartPos) {
rafCurFile.seek(this.longStartPos);
//System.out.println("3. rafCurFile.seek(" + this.longStartPos + ")");
}
rafCurFile.write(this.byteInBuf, 0, this.intBufUsedSize);
this.boolBufDirty = false;
}
}
}
4. 用环形缓冲方式读取远程文件
如果用HttpURLConnection类的方法打开连接,然后用InputStream类获得输入流,再用BufferedInputStream构造出带缓冲区的输入流,如果网速太慢的话,无论缓冲区设置多大,听起来都是断断续续的,达不到真正缓冲的目的。于是尝试编写代码实现用缓冲方式读取远程文件。我是不怎么赞同使用多线程下载的,加之有的链接下载速度本身就比较快,所以在下载速度足够的情况下,就让下载线程退出,直到只剩下一个下载线程。当然,多线程中令人头痛的死锁问题、HttpURLConnection的超时阻塞问题都会使代码看起来异常复杂。
简要介绍一下实现多线程环形缓冲的方法。将缓冲区buf[]分为16块,每块32K,下载线程负责向缓冲区写数据,每次写一块;读线程(BuffRandAcceURL类)每次读小于32K的任意字节。同步描述:写/写互斥等待空闲块;写/写并发填写buf[];读/写并发使用buf[]。
经过我很长一段时间使用,我认为比较满意地实现了我的目标,同其它MP3播放器对比,我的这种方法能够比较流畅、稳定地下载并播放。读取远程文件的BuffRandAcceURL.java源码如下:
/*
* BuffRandAcceURL.java -- 用环形缓冲方式读取HTTP协议远程文件
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package instream;
import java.net.URL;
import java.net.URLDecoder;
import tag.ID3Tag;
/*
* FIFO方式共享环形缓冲区buf[]
* byteBuf[]逻辑上分成16块, 每一块的长度UNIT_LENGTH=32K不小于最大帧长1732
*
* 同步: 写/写 -- 互斥等待空闲块
* 读/写 -- 并发访问buf[]
*
* 简单有效解决死锁?
*/
public final class BuffRandAcceURL implements IRandomAccess, IWriterCallBack {
public static final int UNIT_LENGTH_BITS = 15;
public static final int UNIT_LENGTH = 1 << UNIT_LENGTH_BITS; //2^16=32K
public static final int BUF_LENGTH = UNIT_LENGTH << 4;
public static final int UNIT_COUNT = BUF_LENGTH >> UNIT_LENGTH_BITS; //16块
public static final int BUF_LENGTH_MASK = (BUF_LENGTH - 1);
private static final int MAX_WRITER = 5;
private static long longFilePointer;
private static int longReadCursor;
private static int longReaded;
private static byte[] byteBuf; //同时作写线程同步锁
private static int[] intUnitSize; //同时作读线程互斥锁
private static int intAllocPos;
private static URL objURL;
private static boolean boolAlive = true;
private static int intWriterCounter;
private static long longFileSize;
private static long longAllFrameSize;
private static int intFreeUnits = UNIT_COUNT; // "信号量"计数器
public BuffRandAcceURL(String sURL) throws Exception {
this(sURL,MAX_WRITER);
}
public BuffRandAcceURL(String sURL, int download_threads) throws Exception {
byteBuf = new byte[BUF_LENGTH];
intUnitSize = new int[UNIT_COUNT];
objURL = new URL(sURL);
// 打印文件名
try {
String s = URLDecoder.decode(sURL, "GBK");
System.out.println("播放>> " + s.substring(s.lastIndexOf("/") + 1));
} catch (Exception e) {
System.out.println(sURL);
}
// 获取文件长度
// 为何同一URL(如http://mat1.qq.com/news/wmv/tang/03.mp3)文件长度有时对有时不对?
longAllFrameSize = longFileSize = objURL.openConnection().getContentLength();
if (longFileSize == -1)
throw new Exception("ContentLength=-1");
// 以异步方式解析tag
new TagThread(objURL, longFileSize);
// 创建"写"线程
// 线程被创建后立即连接URL开始下载,由于服务器限制了同一IP每秒最大连接次数,频繁连接
// 会被服务器拒绝,因此延时.
intWriterCounter = download_threads;
for (int i = 0; i < download_threads; i++) {
new Writer(this, objURL, byteBuf, i + 1);
Thread.sleep(200);
}
// 缓冲
tryCaching();
// 跳过 ID3 v2
ID3Tag tag = new ID3Tag();
int v2_size = tag.checkID3V2(byteBuf, 0);
if (v2_size > 0) {
longAllFrameSize -= v2_size;
seek(v2_size);
}
tag = null;
}
private void tryCaching() throws InterruptedException {
int cache_size = BUF_LENGTH;
int bi = intUnitSize[longReadCursor >> UNIT_LENGTH_BITS];
if(bi != 0)
cache_size -= UNIT_LENGTH - bi;
while (longReaded < cache_size) {
if (intWriterCounter == 0 || boolAlive == false)
return;
System.out.printf("\r[缓冲%1$6.2f%%] ",(float)longReaded / cache_size * 100);
synchronized (intUnitSize) {
intUnitSize.wait(200); //wait(200)错过通知也可结束循环?
}
}
System.out.printf("\r");
}
private int tryReading(int i, int len) throws Exception {
int n = (i + 1) & (UNIT_COUNT - 1);
int r = (intUnitSize[i] > 0) ? (intUnitSize[i] + intUnitSize[n]) : intUnitSize[i];
if (r < len) {
if (intWriterCounter == 0 || boolAlive == false)
return r;
tryCaching();
}
return len;
}
/*
* 各个"写"线程互斥等待空闲块
* 空闲块按序号由小到大顺序分配;管理空闲块采用类似于C++的信号量机制.
*/
public int tryWriting() throws InterruptedException {
int ret = -1;
synchronized (byteBuf) {
while (intFreeUnits == 0 && boolAlive)
byteBuf.wait();
if(intAllocPos >= longFileSize || boolAlive == false)
return -1;
ret = intAllocPos;
intAllocPos += UNIT_LENGTH;
intFreeUnits--;
}
return ret;
}
/*
* "写"线程向buf[]写完数据后调用,通知"读"线程
*/
public void updateBuffer(int i, int len) {
synchronized (intUnitSize) {
intUnitSize[i] = len;
longReaded += len;
intUnitSize.notify();
}
}
/*
* "写"线程准备退出时调用
*/
public void updateWriterCount() {
synchronized (intUnitSize) {
intWriterCounter--;
intUnitSize.notify();
}
}
public int getWriterCount() {
return intWriterCounter;
}
/*
* read方法内调用
*/
public void notifyWriter() {
synchronized (byteBuf) {
byteBuf.notifyAll();
}
}
/*
* 被某个"写"线程调用,用于终止其它"写"线程;isalive也影响"读"线程流程
*/
public void terminateWriters() {
synchronized (intUnitSize) {
if (boolAlive) {
boolAlive = false;
System.out.println("\n读取文件超时。重试 " + HttpReader.MAX_RETRY
+ " 次后放弃,请您稍后再试。");
}
intUnitSize.notify();
}
notifyWriter();
}
public int read() throws Exception {
int iret = -1;
int i = longReadCursor >> UNIT_LENGTH_BITS;
if (intUnitSize[i] == 0) {
if(intWriterCounter == 0)
return -1;
tryCaching();
}
if(boolAlive == false)
return -1;
iret = byteBuf[longReadCursor] & 0xff;
longReaded--;
longFilePointer++;
longReadCursor++;
longReadCursor &= BUF_LENGTH_MASK;
if (--intUnitSize[i] == 0) {
intFreeUnits++;
notifyWriter();
}
return iret;
}
public int read(byte b[]) throws Exception {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws Exception {
int i = longReadCursor >> UNIT_LENGTH_BITS;
// 1.等待有足够内容可读
if(tryReading(i, len) < len || boolAlive == false)
return -1;
// 2.读取
int tail = BUF_LENGTH - longReadCursor; // write_pos != BUF_LENGTH
if (tail < len) {
System.arraycopy(byteBuf, longReadCursor, b, off, tail);
System.arraycopy(byteBuf, 0, b, off + tail, len - tail);
} else
System.arraycopy(byteBuf, longReadCursor, b, off, len);
longReaded -= len;
longFilePointer += len;
longReadCursor += len;
longReadCursor &= BUF_LENGTH_MASK;
intUnitSize[i] -= len;
if (intUnitSize[i] < 0) {
int ni = longReadCursor >> UNIT_LENGTH_BITS;
intUnitSize[ni] += intUnitSize[i];
intUnitSize[i] = 0;
intFreeUnits++;
notifyWriter();
} else if (intUnitSize[i] == 0) {
intFreeUnits++; // 空闲块信号量计数加1
notifyWriter(); // 3.通知
}
// 如果邻接的下一块未填满表示文件读完,第1步已处理一次读空两块的情况.
return len;
}
/*
* 从buf[]偏移src_off位置复制.不移动文件"指针",不发信号.
*/
public int dump(int src_off, byte b[], int dst_off, int len) throws Exception {
int rpos = longReadCursor + src_off;
if(tryReading(rpos >> UNIT_LENGTH_BITS, len) < len || boolAlive == false)
return -1;
int tail = BUF_LENGTH - rpos;
if (tail < len) {
System.arraycopy(byteBuf, rpos, b, dst_off, tail);
System.arraycopy(byteBuf, 0, b, dst_off + tail, len - tail);
} else
System.arraycopy(byteBuf, rpos, b, dst_off, len);
return len;
}
public long length() {
return longFileSize;
}
public long getFilePointer() {
return longFilePointer;
}
public void close() {
boolAlive = false;
notifyWriter();
}
/*
* 随机读取定位
*/
public void seek(long pos) throws Exception {
longReaded -= pos;
longFilePointer = pos;
longReadCursor = (int)pos;
longReadCursor &= BUF_LENGTH_MASK;
int units = longReadCursor >> UNIT_LENGTH_BITS;
for (int i = 0; i < units; i++) {
intUnitSize[i] = 0;
notifyWriter();
}
intUnitSize[units] -= pos;
}
}
5.HTTP协议读远程文件
BuffRandAcceURL.java内的class BuffRandAcceURL只是实现了读/写功能的封装,具体实现从缓冲区读取由HttpReader.java内的class HttpReader实现,把从远程文件读到的内容写入缓冲区由Writer.java内的class Writer实现。class Writer实现Runnable接口,实例化的对象一经创建立即开始下载工作,所以在BuffRandAcceURL.java的第68行采用延时语句,以防止同一IP频繁的连接遭到服务器拒绝而返回错误码。
HttpReader.java源码如下:
/*
* HttpReader.java -- HTTP协议读远程文件
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package instream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public final class HttpReader {
public static final int MAX_RETRY = 10;
private URL objURL;
private HttpURLConnection httpConn;
private InputStream objIStream;
private long longCurPos; //决定seek方法中是否执行文件读取定位
private int intConnectTimeout;
private int intReadTimeout;
public HttpReader(URL u) {
this(u, 5000, 5000);
}
public HttpReader(URL u, int intConnectTimeout, int intReadTimeout) {
this.intConnectTimeout = intConnectTimeout;
this.intReadTimeout = intReadTimeout;
objURL = u;
}
public int read(byte[] b, int off, int len) throws IOException {
int r = objIStream.read(b, off, len);
longCurPos += r;
return r;
}
public int getData(byte[] b, int off, int len) throws IOException {
int r, rema = len;
while (rema > 0) {
if ((r = objIStream.read(b, off, rema)) == -1)
return -1;
rema -= r;
off += r;
longCurPos += r;
}
return len;
}
public void close() {
if (httpConn != null) {
httpConn.disconnect();
httpConn = null;
}
if (objIStream != null) {
try {
objIStream.close();
} catch (IOException e) {}
objIStream = null;
}
objURL = null;
}
/*
* 抛出异常通知重试.
* 例如响应码503可能是由某种暂时的原因引起的,同一IP频繁的连接请求会遭服务器拒绝.
*/
public void seek(long pos) throws IOException {
if (pos == longCurPos && objIStream != null)
return;
if (httpConn != null) {
httpConn.disconnect();
httpConn = null;
}
if (objIStream != null) {
objIStream.close();
objIStream = null;
}
httpConn = (HttpURLConnection) objURL.openConnection();
httpConn.setConnectTimeout(intConnectTimeout);
httpConn.setReadTimeout(intReadTimeout);
String sProperty = "bytes=" + pos + "-";
httpConn.setRequestProperty("Range", sProperty);
//httpConn.setRequestProperty("Connection", "Keep-Alive");
int responseCode = httpConn.getResponseCode();
if (responseCode < 200 || responseCode >= 300) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new IOException("HTTP responseCode="+responseCode);
}
objIStream = httpConn.getInputStream();
longCurPos = pos;
//System.out.println(Thread.currentThread().getName()+ ", longCurPos="+longCurPos);
}
}
class Writer用于创建“下载”线程,需要考虑与“读”线程同步,下载完一块数据后扔进事先拿到的缓冲区然后发信号通知可“读”。Writer.java源程序如下:
/*
* Writer.java -- 用于创建“下载”线程
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package instream;
import java.net.URL;
public final class Writer implements Runnable {
private static boolean boolIsAlive = true; // 一个线程超时其它线程也能退出
private static byte[] byteBuf;
private static IWriterCallBack objIWCB;
private HttpReader objHR;
public Writer(IWriterCallBack cb, URL u, byte[] b, int i) {
objHR = new HttpReader(u);
objIWCB = cb;
byteBuf = b;
Thread t = new Thread(this,"dt_"+i);
t.setPriority(Thread.NORM_PRIORITY + 1);
t.start();
}
public void run() {
int wpos = 0, rema = 0, retry = 0;
int idxmask = BuffRandAcceURL.UNIT_COUNT - 1;
boolean cont = true;
int index = 0; // byteBuf[]内"块"索引号
int startpos = 0; // index对应的文件位置(相对于文件首的偏移量)
long time0 = 0;
while (cont) {
try {
// 1.等待空闲块
if (retry == 0) {
if ((startpos = objIWCB.tryWriting()) == -1)
break;
index = (startpos >> BuffRandAcceURL.UNIT_LENGTH_BITS) & idxmask;
wpos = startpos & BuffRandAcceURL.BUF_LENGTH_MASK;
rema = BuffRandAcceURL.UNIT_LENGTH;
time0 = System.currentTimeMillis();
}
// 2.定位
objHR.seek(startpos);
// 3.下载"一块"
int w;
while (rema > 0 && boolIsAlive) {
w = (rema < 2048) ? rema : 2048; // 每次读几K合适?
if ((w = objHR.read(byteBuf, wpos, w)) == -1) {
cont = false;
break;
}
rema -= w;
wpos += w;
startpos += w; // 能断点续传
}
// 下载速度足够快就结束本线程
long t = System.currentTimeMillis() - time0;
if (objIWCB.getWriterCount() > 1 && t < 500)
cont = false;
// 4.通知"读"线程
objIWCB.updateBuffer(index, BuffRandAcceURL.UNIT_LENGTH - rema);
retry = 0;
} catch (Exception e) {
if (++retry == HttpReader.MAX_RETRY) {
boolIsAlive = false;
objIWCB.terminateWriters();
break;
}
}
}
objIWCB.updateWriterCount();
objHR.close();
objHR = null;
}
}
6. 读取远程文件的同时以异步方式解析MP3的标签
读取远程文件需要缓冲,根据设置的缓冲区大小和网络连接情况不同。缓冲可能要经历一个比较长的时间,可在文件缓冲的这段时间内解析MP3文件的标签信息并打印出来,感觉上觉得等待时间不长。TagThread.java内的class TagThread实现这一功能。同样,class TagThread实现了Runnable接口,对象一经实例化后立即开始解析远程MP3文件的信息。负责解析MP3文件的标签信息的类 class ID3Tag 在后续的文章中给出。TagThread.java源码如下:
/*
* TagThread.java -- 解码远程MP3文件ID3 tag
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version..
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package instream;
import java.io.IOException;
import java.net.URL;
import tag.ID3Tag;
public class TagThread implements Runnable {
private HttpReader objHR;
private long longFileSize;
public TagThread(URL u,long flen) throws IOException {
objHR = new HttpReader(u);
longFileSize = flen;
new Thread(this).start();
}
public void run() {
byte[] tmp_buf, b;
int retry = 0;
ID3Tag objID3tag = new ID3Tag();
while (true) {
try {
tmp_buf = new byte[128];
// id3 v1
objHR.seek(longFileSize - 128);
if (objHR.getData(tmp_buf, 0, 128) == 128)
if (objID3tag.checkID3V1(tmp_buf))
objID3tag.parseID3V1(tmp_buf);
// ID3 v2
int v2_size = 0;
objHR.seek(0);
if (objHR.getData(tmp_buf, 0, 10) == 10) {
v2_size = objID3tag.checkID3V2(tmp_buf, 0);
// System.out.println("v2_size = " + v2_size);
if (v2_size > 0) {
v2_size -= 10; // id3 v2 header
b = new byte[v2_size];
if (objHR.getData(b, 0, v2_size) == v2_size)
objID3tag.parseID3V2(b, 0);
}
}
objID3tag.printTag();
break;
} catch (Exception e) {
if (++retry == HttpReader.MAX_RETRY)
break;
}
}
tmp_buf = null;
b = null;
objID3tag.destroy();
objID3tag = null;
}
}