近日公司有个项目要做一个视频库,故开始阶段性研究。首先先要研究断点续传。
第一步:打算提高上传视频的时间,上传的时间分成两部分,读流和写入GFS。想做一个实验来验证到底时间花费在了哪里,故在程序中加入了打印语句。如下:
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("1="+sdf.format(new Date())); try { ServletFileUpload upload = new ServletFileUpload(); upload.setFileSizeMax(fileSizeMax * 1024 * 1024); FileItemIterator iter = upload.getItemIterator(request); while(iter.hasNext()){ FileItemStream item = iter.next(); String fileName = item.getName(); InputStream stream = item.openStream(); String fn = item.getFieldName(); System.out.println("2="+sdf.format(new Date())); if (!item.isFormField() && !StringUtil.isEmpty(fn)) { System.out.println("3(1)="+sdf.format(new Date())); String cloudFileName = videoCloudStorage.upload(fileName,stream); System.out.println("3(2)="+sdf.format(new Date())); out.write(ReturnMessageUtil.createOKMsg(cloudFileName)); } } } catch (Exception e) { logger.error("上传文件失败!", e); // out.write(ReturnMessageUtil.createErrorMsg("upload failed!")); response.sendError(500, "上传文件失败!"); } System.out.println("4="+sdf.format(new Date())); }
打印结果为:
1=2015-07-29 17:50:13
2=2015-07-29 17:50:13
2=2015-07-29 17:50:13
33(1)=2015-07-29 17:50:13
33(2)=2015-07-29 17:50:19
2=2015-07-29 17:50:19
4=2015-07-29 17:50:19
可以看出时间都花在了往云存储上传输。当时觉得客户端往weblogic传流是秒传的,weblogic是在得到了全部流后才往云存储上传输。然后又做实验,这次是不传云存储了,而是直接把流写文件到weblogic上,代码入下:
File file = new File("//data//weblogic//1.mp4"); FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); BufferedInputStream bis = new BufferedInputStream(stream); try { byte[] buffer = new byte[2048]; int len = -1; while ((len = bis.read(buffer)) != -1) { // 输出读入的数据 bos.write(buffer, 0, len); } bos.close(); } catch (IOException e) { e.printStackTrace(); }
这次再打印语句,发现传云存储和直接传服务器上生成文件时间基本一样,但是这个还是无法判断出来到底时间是花在了读取流上还是花在了写文件上,然后又做了一个实验,就是把获取的流不写入文件了,而是读取后什么都不做,直接注掉上面的bos.write()方法,再跑一遍,发现结果还是一样,所以只能得出的结论是,流并不是一次性就传到了weblogic上,而是只是打开了一个通道,当
while ((len = bis.read(buffer)) != -1) {
的时候才是去真正读取流,所以真正费时间还是在读取流上而非写入文件。但是FLASH端说只能单线程传流,所以看来优化上传还是要想别的办法。
第二步:秒传
用户点击上传按钮,FLASH先发送请求给后端,后端从数据库中用MD5判断验证是否已经上传过(且
上传完整)该视频,如果上传过,则实现秒传(即为直接返回服务器存储地址,不用再上传),如果上传过,但是没有上传全部,则给FLASH返回一个
已上传文件大小,FLASH重新分份后继续发送剩下的文件流给服务端。
知识点:MD5生成散列码
java.security.MessageDigest类用于为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。简单点说就是用于生成散列码。信息摘要是安全的单向哈希函数,它接收任意大小的数据,输出固定长度的哈希值。
用法如下:
public static void main(String[] args){ try { String str = "abcd"; MessageDigest com=MessageDigest.getInstance("MD5"); //生成MessageDigest对象 com.update(str.getBytes("UTF-8")); //传入需要计算的字符串 byte[] b = com.digest(); //计算消息摘要 System.out.println(convertToHexString(b)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } static String convertToHexString(byte data[]) { StringBuffer strBuffer = new StringBuffer(); for (int i = 0; i < data.length; i++) { strBuffer.append(Integer.toHexString(0xff & data[i])); } return strBuffer.toString(); }
输出结果为:e2fc714c4727ee9395f324cd2e7f331f (固定长度的)
如果是想获得文件的散列码,则代码为:
public class Auth{ public static byte[] create(String filename) throws Exception{ InputStream fis = new FileInputStream(filename); byte[] buf= new byte[1024]; MessageDigest com=MessageDigest.getInstance("MD5"); int num; do{ num=fis.read(buf); if(num>0){ com.update(buf,0,num); } }while(num!=-1); fis.close(); return com.digest(); } public static void main(String[] args){ try { byte[] fileA =create("E:\\1.jpg"); System.out.println(convertToHexString(fileA)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } static String convertToHexString(byte data[]) { StringBuffer strBuffer = new StringBuffer(); for (int i = 0; i < data.length; i++) { strBuffer.append(Integer.toHexString(0xff & data[i])); } return strBuffer.toString(); } }
输出结果为:4bca3e9cec71f8ad3601ddd31d7ceb
第三步:合并文件
断点续传的核心就是分块上传,然后再进行合并,我们先来了解一下合并所需的类RandomAccessFile。
它适用于由大小已知的记录组成的文件,所以我们可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录。
文件中记录的大小不一定都相同,只要能够确定哪些记录有多大以及它们在文件中的位置即可。
import java.io.RandomAccessFile;
public class RandomAccessFileDemo01 {
// 所有的异常直接抛出,程序中不再进行处理
public static void main(String args[]) throws Exception {
File f = new File("d:" + File.separator + "test.txt"); // 指定要操作的文件
RandomAccessFile rdf = null; // 声明RandomAccessFile类的对象
rdf = new RandomAccessFile(f, "rw"); // 读写模式,如果文件不存在,会自动创建
String name = null;
int age = 0;
name = "zhangsan"; // 字符串长度为8
age = 30; // 数字的长度为4
rdf.writeBytes(name); // 将姓名写入文件之中
rdf.writeInt(age); // 将年龄写入文件之中
name = "lisi "; // 字符串长度为8
age = 31; // 数字的长度为4
rdf.writeBytes(name); // 将姓名写入文件之中
rdf.writeInt(age); // 将年龄写入文件之中
name = "wangwu "; // 字符串长度为8
age = 32; // 数字的长度为4
rdf.writeBytes(name); // 将姓名写入文件之中
rdf.writeInt(age); // 将年龄写入文件之中
rdf.close(); // 关闭
}
};
(2)读取数据
import java.io.RandomAccessFile;
public class RandomAccessFileDemo02{
// 所有的异常直接抛出,程序中不再进行处理
public static void main(String args[]) throws Exception{
File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
RandomAccessFile rdf = null ; // 声明RandomAccessFile类的对象
rdf = new RandomAccessFile(f,"r") ; // 以只读的方式打开文件
String name = null ;
int age = 0 ;
byte b[] = new byte[8] ; // 开辟byte数组
// 读取第二个人的信息,意味着要空出第一个人的信息
rdf.skipBytes(12) ; // 跳过第一个人的信息
for( int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 读取一个字节
}
name = new String(b) ; // 将读取出来的byte数组变为字符串
age = rdf.readInt() ; // 读取数字
System.out.println("第二个人的信息 --> 姓名:" + name + ";年龄:" + age) ;
// 读取第一个人的信息
rdf.seek(0) ; // 指针回到文件的开头
for( int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 读取一个字节
}
name = new String(b) ; // 将读取出来的byte数组变为字符串
age = rdf.readInt() ; // 读取数字
System.out.println("第一个人的信息 --> 姓名:" + name + ";年龄:" + age) ;
rdf.skipBytes(12) ; // 空出第二个人的信息
for( int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 读取一个字节
}
name = new String(b) ; // 将读取出来的byte数组变为字符串
age = rdf.readInt() ; // 读取数字
System.out.println("第三个人的信息 --> 姓名:" + name + ";年龄:" + age) ;
rdf.close() ; // 关闭
}
};
运行结果
第一个人的信息 --> 姓名:zhangsan;年龄:30
第三个人的信息 --> 姓名:wangwu ;年龄:32
File file = new File("e:\\file.txt"); //文件内容为:今天挣了 File file1 = new File("e:\\file1.txt"); //文件内容为:1000元 RandomAccessFile file2 = new RandomAccessFile("e:\\file2.txt","rw"); BufferedReader is = new BufferedReader(new FileReader(file)); BufferedReader is1 = new BufferedReader(new FileReader(file1)); String line = ""; while((line = is.readLine())!=null){ file2.write(line.getBytes()); } file2.seek(file2.getFilePointer()); while((line = is1.readLine())!=null){ file2.write(line.getBytes()); } /** * 输出file2的内容 */ RandomAccessFile file3 = new RandomAccessFile("e:\\file2.txt","r"); byte[] b = new byte[(int)file3.length()]; byte[] c = new byte[(int)file3.length()]; for(int i=0;i<b.length;i++){ c[i] = file3.readByte(); } String aa = new String(c); System.out.println(aa); file2.close();输出结果为:今天挣了1000元。
String clientSignMd5 = request.getParameter("sign"); MD5 md5 = new MD5(); Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); String serversign = AppType.SPACE + "-xxxxxxxxx-" + sdf.format(date); String serverSignMd5 = md5.encodeByMD5(serversign); if(!clientSignMd5.equals(serverSignMd5)){ return ReturnMessageUtil.createErrorMsg("没有上传权限"); }