接上篇【多文件自平衡云传输 五】可视化界面---显示进度条在做好了界面之后,再实现一个可以监控传输速率的功能。能够将速率动态地显示在界面上。
需要显示什么格式?
介于网络传输都是以字节(B)为单位的。但是如果此刻传输的数据很大时这个值为很大,效果就不好。所以第一步需要一个能将字节(B)转换为KB,MB,GB,而且可以自己指定中/英文格式的工具。这个工具只做一件事,就是转换单位获取带单位的字符串。
比如:678---->678B, 1024--->1.00KB。具体代码如下:
public class TimeDate {
public static final int CHAR_CN = 1; //标记:表示中文格式
public static final int CHAR_EN = 0; //标记:表示英文格式
//两种格式对应的显示 英文:B 中文:字节
private static final String[] charTypes = { "B", "字节" };
/**
* 一个int型4字节(32位)java中写一个整数,默认是int型:比如1,就是默认为int型。
* 一个long型8字节(64位)Java中整数后边显式地带上L 就表示long型
* 所以此处一定要显式的带上L
*/
private static final long[] level = {
1L << 10, //1KB
1L << 20, //1MB
1L << 30, //1GB
1L << 40, //1TB
};
private static final char[] un = {
'K',
'M',
'G',
'T',
};
public static String sizeToByte(long size) {
return sizeToByte(size, CHAR_EN);
}
//我的目的是传过来一个文件片段大小,我可以将其转换单位。而且可以指定显示格式(中/英)
public static String sizeToByte(long size, int charType) {
String type = charTypes[charType];
if (size < 0L) {
return "非法长度值!";
}
//0 - 999B的情况
if (size < level[0]) {
return size + type;
}
StringBuffer res = new StringBuffer();
//1KB - 999KB的情况
if (size < level[1]) {
return toBytes(size, 0, res, type);
}
//1MB - 999MB的情况
if (size < level[2]) {
return toBytes(size, 1, res, type);
}
//1GB - 999GB的情况
if (size < level[3]) {
return toBytes(size, 2, res, type);
}
//1TB以上的情况---这种情况基本上现在还不存在
return toBytes(size, 3, res, type);
}
//单位换算核心方法---传递过来的size的单位永远是B
private static String toBytes(long size, int index, StringBuffer res, String type) {
double tmp = (double) size / (double) level[index];
/**
* 通过上述计算得出的tmp是double类型的,也就是说永远是包含小数点的,再换句话说就是永远可以保证含最低一位小数
* 只不过分两种情况:
* 1. 小数位数大于两位
* 2. 小数位数小于等于两位。比如:999.0
* 所以我要截取小数点后两位就需要补个0
*/
String s = String.valueOf(tmp) + "0";
int dotIndex = s.indexOf('.');
//截取小数点后两位。保证精度
res.append(s.substring(0, dotIndex + 3));
//添加单位
res.append(un[index]).append(type);
return res.toString();
}
}
传输速率 = 传输长度 / 时间
传输长度在最最最之前就封装好了。无论是一个文件片段的长度还是一个文件的长度都可以获取到,现在只要求出时间差就行了。打算给两个显示:平均速率 和 当前速率。
首先速率的计算要在每次接收完一个片段之后就计算一次。
当前速率:传输完一个文件片段的速率。获取上下两个片段传输的时间差除以当前接收片段。
平均速率:每次传输完之后的时刻 - 最开始传输的时刻。就可以表示从开始到接收到现在用了多少时间,当然需要记录这个段内的传输总长度。
基于上述分析,具体实现如下:
定义接口,两个方法不仅可以测试接收的速率还可以测试发送的速率。
public interface IFileSectionSendReceiveSpeed {
void afterSend(int len);
void afterReceive(int len);
}
public class FileSectionSendReceiveSpeedAdapter implements IFileSectionSendReceiveSpeed {
public FileSectionSendReceiveSpeedAdapter() {}
@Override
public void afterSend(int len) {}
@Override
public void afterReceive(int len) {}
}
public class NetSpeed extends FileSectionSendReceiveSpeedAdapter {
private volatile static NetSpeed me;
private volatile static long startReceiveTime; //资源接收前计时
private volatile static long lastReceiveTime; //接收完一个片段计时
private volatile static long receiveSize; //当前接收总长度
private volatile static long curSpeed; //当前速度
private volatile static long averSpeed; //平均速度
private NetSpeed() {}
public static NetSpeed getInstance() {
if(me == null) {
synchronized (NetSpeed.class) {
if(me == null) {
long time = System.currentTimeMillis();
startReceiveTime = time;
lastReceiveTime = time ;
me = new NetSpeed();
}
}
}
return me;
}
@Override
public void afterReceive(int len) {
long curTime = System.currentTimeMillis();
/**
* 求出与上次时间差。注意一定要是双精度的,要不然如果是整数,在时间差很短的情况下deltaTime值为0
* 会抛出除0错异常
*/
double deltaTime = curTime - lastReceiveTime;
//总计耗时---用于计算总速度
long allTime = curTime - startReceiveTime;
//系统时间单位为毫秒--将其转换为秒。求出单位时间内接收的片段长度
curSpeed = (long) (len*1000.0 / deltaTime);
//更新当前接收长度
receiveSize = receiveSize += len;
//计算平均速率
averSpeed = (long) (receiveSize *1000.0 / allTime);
//更新上次接收片段时间
lastReceiveTime = curTime;
}
public static long getAverSpeed() {
return averSpeed;
}
public static long getCurSpeed() {
return curSpeed;
}
public static void clear() {
me = null;
receiveSize = 0;
curSpeed = averSpeed = 0;
startReceiveTime = lastReceiveTime = 0;
}
}
然后在接收完一个片段之后计算速度
public class FileSectionSendRecevice {
private ISendReceive sendReceive;
private IFileSectionSendReceiveSpeed speed;
public FileSectionSendRecevice() {
this.sendReceive = new NetSendReceive();
speed = new FileSectionSendReceiveSpeedAdapter();
}
public void setSpeed(IFileSectionSendReceiveSpeed speed) {
this.speed = speed;
}
//....
public FileSectionInfo receiveSection(DataInputStream dis) throws IOException {
byte[] bytes = this.sendReceive.receive(dis, 16);
FileSectionInfo sectionInfo = new FileSectionInfo(bytes);
int len;
if((len = sectionInfo.getLen()) > 0) {
byte[] content = this.sendReceive.receive(dis, len);
sectionInfo.setContent(content);
//此处进行测速
this.speed.afterReceive(len);
}
return sectionInfo;
}
//...
}
为了能让其显示在界面上,还需要对界面内容做一些调整:
public abstract class ReceiveProgressDialog extends JDialog implements IMecView {
//...
private JLabel jlblCurrentSpeed;
private JLabel jlblAverageSpeed;
@Override
public void init() {
//...
JPanel jpnlFooter = new JPanel(new GridLayout(1, 2));
this.add(jpnlFooter, BorderLayout.SOUTH);
JPanel jpnlCurrentSpeed = new JPanel(new FlowLayout(FlowLayout.LEFT));
jpnlFooter.add(jpnlCurrentSpeed);
JLabel jlblCurrentSpeedTopic = new JLabel("当前速率 : ");
jlblCurrentSpeedTopic.setFont(smallFont);
jpnlCurrentSpeed.add(jlblCurrentSpeedTopic);
this.jlblCurrentSpeed = new JLabel(TimeDate.sizeToByte(0L, TimeDate.CHAR_CN));
this.jlblCurrentSpeed.setFont(smallFont);
jpnlCurrentSpeed.add(this.jlblCurrentSpeed);
JPanel jpnlAverageSpeed = new JPanel(new FlowLayout(FlowLayout.LEFT));
jpnlFooter.add(jpnlAverageSpeed);
JLabel jlblAverageSpeedTopic = new JLabel("平均速率 : ");
jlblAverageSpeedTopic.setFont(smallFont);
jpnlAverageSpeed.add(jlblAverageSpeedTopic);
this.jlblAverageSpeed = new JLabel(TimeDate.sizeToByte(0L, TimeDate.CHAR_CN));
this.jlblAverageSpeed.setFont(smallFont);
jpnlAverageSpeed.add(this.jlblAverageSpeed);
}
public void updataSpeed(long allSize, long curSize) {
jlblAverageSpeed.setText(TimeDate.sizeToByte(allSize, TimeDate.CHAR_CN));
jlblCurrentSpeed.setText(TimeDate.sizeToByte(curSize, TimeDate.CHAR_CN));
}
//....
}
接收时启动计时,线程安全的单例模式下可保证只初始化计时一次。然后再更新一下界面的速率显示即可。
测试结果: