java多线程实现下载图片并压缩

作者:Terisadeng 时间:2023-01-17 22:28:34 

最近在做一个需求:从其他系统的ftp目录下载存储图片url的文件,然后读取文件中的url地址,根据地址下载图片后按天压缩成一个包,平均一个地址文件中包含4000个地址左右,也就是说一个文件扫描后需要下载4000个左右的图片,然后压缩,下面把我的实现方式和优化过程记录下来,如果大家有什么更好的方式可以分享。

使用框架:SpringMVC

定时任务实现:继承org.springframework.scheduling.quartz.QuartzJobBean;

ftp环境搭建就不说了,在其他博客记录过,使用虚拟机中的CentOS搭建的FTP服务,创建FTP账号及对应目录,事先上传需要下载的图片地址文件。文件内容格式“图片ID||图片地址”。

方法一、最简单的实现方法就是先下载存储图片url地址的文件,然后读取文件遍历图片地址,调下载图片的方法将图片存储到本地,最后压缩下载的图片,完成后删除下载的图片,只保留压缩包。


public class PictureTransferJob extends QuartzJobBean
{
protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException
{
//实际的FTP配置是读取配置文件获取的
//FTP地址
String hostName ="192.168.1.112";
//FTP端口
int port = 2001;
/FTP账号
String userName = "test1";
//ftp密码
String password = "test1";
//ftp文件存储目录
String ftpDowload = "/";
//文件本地存储路径
String path = this.getClass().getResource("/").getPath();
//图片地址文件存储目录
String addrPath=path.substring(1, path.indexOf("WEB-INF/classes"))+"picAddr";
//实际下载的图片存储目录
String picPath=path.substring(1, path.indexOf("WEB-INF/classes"))+"pic";
addrPath = addrPath.replace("%20"," ");
picPath = picPath.replace("%20"," ");
try
{
//创建存储图片地址的文件
creatFile(addrPath);
//创建存储实际图片的文件
creatFile(picPath);
String oldAddrPath = addrPath;
String oldPicPath = picPath;
//创建FTP连接
FtpUtil2 ftpUtil2 = new FtpUtil2(hostName, port,userName, password, ftpDowload, true);
//遍历FTP目录下的文件
String[] files = ftpUtil2.ListAllFiles();
//本地数据库会有一个表记录下载过的文件,这里会查询数据库和ftp列出的文件名比较,如果已经下载过的文件就不会下载,避免重复下载。
//下面省略比较的过程,循环files数组,在本地创建文件
for(int i=0;i<files.length;i++){
creatFile(addrPath+File.separator+fileName);
//ftpDowload是ftp服务器存储文件的地址,addrPath是本地存储文件的地址
//这里一个返回状态判断文件是否下载成功
boolean downloadInvestorFlag = ftpUtil2.downloadFile(ftpDowload, addrPath);
//文件下载成功后调读取文件的方法,将需要下载的图片地址存入容器
boolean entityState = setPictureDetail(addrPath,picPath,fileNameDate);
}
}  
catch (Exception e)
{
 e.printStackTrace();
 //调记录错误日志的业务类用于发送下载文件出错的短信
}
}

//这里开始读图片地址
private boolean setPictureDetail(String addrPath,String picPath,String synDate)
{
System.out.println("----------进入setPictureDetail方法-----------");
BufferedReader br = null;
try
{
 br=new BufferedReader(new InputStreamReader(new FileInputStream(addrPath),"UTF-8"));
 String row;
 int count=0;
//map中存储每行读取到的图片名称和URL地址
 Map<String, String> addrMap=new HashMap<String, String>();
 while ((row=br.readLine())!=null)
 {
 try
 {
  count++;
  if (count==1)
  {
  continue;
  }
  String[] column = row.split("\\|\\|", -1);
  addrMap.put(column[0].trim(), column[1].trim());
 }
 catch (Exception e)
 {
  e.printStackTrace();
 }
 }
 System.out.println(new Date());
 //这里调用压缩方法,压缩方法中会调用执行下载图片的方法
 zipPic(picPath,synDate,addrMap);
 System.out.println(new Date());
 System.out.println("----------完成--------------");
 return true;
}
catch (Exception e)
{
 e.printStackTrace();
 //调用记录错误日志的业务类
 return false;
}finally {
 try {
 if (null != br)
  br.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
}
}
/**
* 根据url地址下载图片
* @throws IOException
*/
private boolean downPic(String picPath,List<Entry<String, String>> addrList,List<File> picList)throws IOException{
InputStream is=null;
FileOutputStream fos=null;
URL url=null;
String fileName=null;
String picAddr=null;
File pic=null;
try
{
 for(Map.Entry<String, String> addrEntry:addrList)
 {
 fileName=addrEntry.getKey();
 picAddr=addrEntry.getValue();
 //创建Url对象
 url=new URL(picAddr);
 is=url.openStream();
 //URLConnection获取到的流通过InputStream直接写入字节数组会缺失数据,导致下载的图片不完整,使用org.apache.commons.io.IOUtils.toByteArray(urlconnection.openstream())可以解决
 byte[] bytes=IOUtils.toByteArray(is);//new byte[is.available()];获取的字节
 //流中数据读入字节数组,读入后,流中数据清空
 pic=new File(picPath+fileName+".jpg");
 fos=new FileOutputStream(pic);
 fos.write(bytes);
 //将下载的图片存入List,待图片全部下载完成后传入zip方法进行压缩
 picList.add(pic);
 fos.flush();
 fos.close();
 is.close();
 }
 return true;
}
catch (Exception e)
{
 e.printStackTrace();
 return false;
}
finally{
 if (null!=fos)
 {
 fos.close();
 }
 if (null!=is)
 {
 is.close();
 }
}
}
//这里是压缩文件的伪代码
private void zipPic(picPath,synDate,addrMap);{
//传入需要压缩的文件列表和压缩文件名
ZipUtil.zipByStream(picList,new File(picPath+synDate+".zip"));
}
/**
* 创建文件
* @param path
*/
private void creatFile(String path)
{
File file = new File(path);
if(!file.exists())
{
 file.mkdirs();
}
}
}

方法二、多线程下载、直接压缩流

方法一虽然实现了基本功能,但是由于需要下载的图片太多,以及压缩本地图片文件和删除图片也比较耗时,所以可以优化速度的地方有两个。一个就是提高下载图片的效率,一个就是提高压缩的效率。

提高下载效率的方法可以使用多线程下载,提高压缩效率的方法是可以不将图片保存到本地而直接压缩文件流。

多线程实现方式:首先我们保存了需要下载的文件的地址列表,我们要使用多线程下载就要保证不同线程下载的图片不会重复,因此需要一个标志来区分,这时就可以使用一个索引计数器,按每个线程下载一定量图片分割,从0开始,每隔比如400个图片就用一个线程下载,这样就可以确定需要的线程个数,并且每个线程下载的图片不会重复。

压缩文件实现方式:因为生成压缩文件的本质也是读取需要压缩的文件流,然后生成压缩包,因此我们可以不创建下载的图片文件,而直接使用容器存储所有线程下载的图片流数据,然后将流数据传给压缩工具类直接压缩,这样就省略了读取图片文件创建流,然后生成压缩包,再删除本地图片文件的繁琐过程。

下面列出改造的主要实现:


/**
* 将下载的图片按天压缩
* @throws IOException
*/
private boolean zipPic(String picPath,String synDate,Map<String, String> addrMap) throws IOException{
//这里由于是多线程存储图片流,所以需要使用线程安全的map,因此使用ConcurrentHashMap
Map<String,InputStream> pictureList=new ConcurrentHashMap<String,InputStream>();
//这里定义每个线程下载的图片个数
int count=400;
//存储需要下载的图片地址
List<Entry<String, String>> addrList=new ArrayList<Entry<String, String>>(addrMap.entrySet());
//线程数,加一是因为要创建一个线程下载最后不足400个的图片
int nThreads=(addrList.size()/count)+1;
//CountDownLatch countDownLatch = new CountDownLatch(nThreads);
try
{
boolean downPic=false;
//执行多线程下载图片
downPic=downPic(picPath,addrList,picList,pictureList,nThreads,count);
if (downPic)
{
 ZipUtil.zipByArray(picList,new File(picPath+synDate+".zip"));
}
return true;
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
}

下面是创建线程池


/**
* 根据url地址下载图片
* @throws InterruptedException
*/
private boolean downPic(String picPath,List<Entry<String, String>> addrList,Map<String, byte[]> picList,Map<String, InputStream> pictureList,int nThreads,int count)throws IOException, InterruptedException{
ExecutorService threadPool=Executors.newFixedThreadPool(nThreads);
// 创建两个个计数器
CountDownLatch begin=new CountDownLatch(0);
CountDownLatch end=new CountDownLatch(nThreads);
// 循环创建线程
for (int i = 0; i < nThreads; i++) {
List<Entry<String, String>>subAddrList=null;
// 计算每个线程执行的数据
if ((i + 1) == nThreads) {
 int startIndex = (i * count);
 int endIndex = addrList.size();
 subAddrList = addrList.subList(startIndex, endIndex);
} else {
 int startIndex = (i * count);
 int endIndex = (i + 1) * count;
 subAddrList = addrList.subList(startIndex, endIndex);
}
// 线程类
PicDownload mythead = new PicDownload(picPath,subAddrList,picList,pictureList);
// 这里执行线程的方式是调用线程池里的threadPool.execute(mythead)方法。
try
{
 threadPool.execute(mythead);
}
catch (Exception e)
{
 //记录错误日志
 return false;
}
}
begin.countDown();
end.await();
// 执行完关闭线程池
threadPool.shutdown();
//这里一定要循环直到线程池中所有线程都结束才能往下走,测试时由于没有这一步导致子线程下载图片还没完成,而主线程已经往下走了,导致压缩包内没有图片
//也可以使用CountDownLatch实现
/*while (true)
{
if (threadPool.isTerminated())
{
 System.out.println("所有子线程已结束!");
 break;
}
}*/
return true;
}

下面是线程实现


class PicDownload implements Runnable{
//下载图片的地址列表
List<Entry<String, String>> addrList;
//装载下载成功的图片列表
Map<String, byte[]> picList;
Map<String, InputStream> pictureList;
//图片本地存储路径
String picPath;

CountDownLatch begin,end;
public PicDownload(String picPath,List<Entry<String, String>> addrList,Map<String, InputStream> picList,CountDownLatch begin,CountDownLatch end){
this.addrList=addrList;
this.picList=picList;
this.picPath=picPath;
this.begin=begin;
this.end=end;
}
@Override
public void run()
{
try
{
 System.out.println(Thread.currentThread().getName()+"------"+Thread.currentThread().getId());
 downPicture(addrList);
 //System.out.println(countDownLatch.getCount());
 begin.await();
}
catch (Exception e)
{
 e.printStackTrace();
}finally{
 end.countDown();
 //countDownLatch.countDown();
}
}
public boolean downPicture(List<Entry<String, String>> addrList) throws Exception{
InputStream is=null;
FileOutputStream fos=null;
URL url=null;
String fileName=null;
String picAddr=null;
File pic=null;
try
{
 for(Map.Entry<String, String> addrEntry:addrList)
 {
 fileName=addrEntry.getKey();
 picAddr=addrEntry.getValue();
 //创建Url对象
 url=new URL(picAddr);
 is=url.openStream();
 //URLConnection获取到的流通过InputStream直接写入字节数组会缺失数据,导致下载的图片不完整,使用org.apache.commons.io.IOUtils.toByteArray(urlconnection.openstream())可以解决
 //byte[] bytes=IOUtils.toByteArray(is);//new byte[is.available()];获取的字节
 //流中数据读入字节数组,读入后,流中数据清空
 picList.put(fileName+".jpg", is);
 //这时候由于没有把流写入文件,一定不能关闭流,否则流中的数据就会丢失
 //is.close();
 }
 return true;
}
catch (Exception e)
{
 e.printStackTrace();
 return false;
}
finally{
 //不能关闭流
 /*if (null!=is)
 {
 is.close();
 }*/
}
}
}

上面使用流来压缩遇到了另一个问题,在压缩文件时会出现java.net.SocketException:Connection reset
分析了一下原因,应该是由于流InputStream和UrlConnection是连接状态的,UrlConnection超时重置导致了获取输入流失败。

尝试设置URLConnection的超时时间,但是测试时发现图片下载收到网速影响较大,这种方式很不稳定,不可取,最后只有放弃使用流,而改用字节数组传给压缩工具类,然后将字节数组转为流压缩。


/**
*使用容器存储下载的图片字节数组
*/
public boolean downPicture(List<Entry<String, String>> addrList) throws Exception{
InputStream is=null;
FileOutputStream fos=null;
URL url=null;
String fileName=null;
String picAddr=null;
File pic=null;
try
{
for(Map.Entry<String, String> addrEntry:addrList)
{
 fileName=addrEntry.getKey();
 picAddr=addrEntry.getValue();
 //创建Url对象
 url=new URL(picAddr);
 //打开连接,创建java.net.URLConnection对象,该对象没有关闭连接的方法,可以转为它的子类HttpURLConnection调用disconnect方法关闭连接。
 //java.net.URLConnection和java.net.HttpURLConnection都有设置超时时间的方法关闭连接
 //HttpURLConnection uc=(HttpURLConnection)url.openConnection();
 is=uc.getInputStream();
 //URLConnection获取到的流通过InputStream直接写入字节数组会缺失数据,导致下载的图片不完整,使用org.apache.commons.io.IOUtils.toByteArray(urlconnection.openstream())可以解决
 byte[] bytes=IOUtils.toByteArray(is);//new byte[is.available()];获取的字节
 //流中数据读入字节数组,读入后,流中数据清空
 //is.read(bytes);
 picList.put(fileName+".jpg",bytes);
 is.close();
}
return true;
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
finally{
if (null!=is)
{
 is.close();
}
}
}

总结:

实现过程中遇到的问题:

1、使用线程池时对于共享状态,比如这里的存储下载的图片字节数据容器是所有线程共享的,因此需要使用同步的容器,否则会导致存储的数据出问题,因此使用了ConcurrentHashMap<String,byte[]>
2、这里存在一个主线程和子线程的执行顺序问题,因为主线程需要等待线程池中所有线程下载图片结束后才能往下走去压缩图片,如果主线程不等待子线程结束就向下执行压缩方法就会导致压缩图片缺少或者没有压缩图片。因此可以使用CountDownLatch实现,或者在关闭线程池语句下面使用死循环检查threadPool.isTerminated()才能继续执行主线程去压缩图片。
3、由于直接将UrlConnection获取到的输入流直接传给压缩类进行压缩,存在连接超时重置的情况,因此改用将下载的流存入字节数组,再传给压缩类压缩,避免使用流出现意外情况。
4、在使用urlconnection.openStream()获取输入流后,转换为字节数组下载的图片是不完整的。。使用org.apache.commons.io.IOUtils.toByteArray(urlconnection.openstream())可以解决,具体可以阅读其源码看实现。

下面是FTP工具类的实现:


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPReply;

public class FtpUtil2 {
private FTPClient ftpClient = null;

// ftp服务器地址
private String hostName;

// ftp服务器默认端口
public static int defaultport = 21;

// 登录名
private String userName;

// 登录密码
private String password;

// 需要访问的远程目录
private String remoteDir;

/**
* @param hostName
*  主机地址
* @param port
*  端口号
* @param userName
*  用户名
* @param password
*  密码
* @param remoteDir
*  默认工作目录
* @param is_zhTimeZone
*  是否是中文FTP Server端
* @return
* @return
*/

/**
* 新增方法
*/
public FtpUtil2()
{
PropConfig config = PropConfig.loadConfig("system.properties");
String hostName = config.getConfig("ftpAddress");
String port = config.getConfig("ftpPort");
String userName = config.getConfig("ftpUserName");
String password = config.getConfig("ftpPassword");
String remoteDir = config.getConfig("remoteFilePath");
boolean is_zhTimeZone= true;
this.hostName = hostName;
this.userName = userName;
this.password = password;
this.remoteDir = remoteDir == null ? "" : remoteDir;
this.ftpClient = new FTPClient();
if (is_zhTimeZone) {
 this.ftpClient.configure(FtpUtil2.Config());
 this.ftpClient.setControlEncoding("GBK");
}
// 登录
this.login();
// 切换目录
this.changeDir(this.remoteDir);
this.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.setDefaultPort(Integer.parseInt(port));  
}
public FtpUtil2(String hostName, int port, String userName,
 String password, String remoteDir, boolean is_zhTimeZone) {
this.hostName = hostName;
this.userName = userName;
this.password = password;
defaultport=port;
this.remoteDir = remoteDir == null ? "" : remoteDir;
this.ftpClient = new FTPClient();
if (is_zhTimeZone) {
 this.ftpClient.configure(FtpUtil2.Config());
 this.ftpClient.setControlEncoding("GBK");

}
// 登录
this.login();
// 切换目录
this.changeDir(this.remoteDir);
this.setFileType(FTPClient.ASCII_FILE_TYPE);
ftpClient.setDefaultPort(port);

}

/**
* 登录FTP服务器
*/
public boolean login() {
boolean success = false;
try {
 ftpClient.connect(this.hostName,defaultport);
 ftpClient.login(this.userName, this.password);
 int reply;
 reply = ftpClient.getReplyCode();
 if (!FTPReply.isPositiveCompletion(reply)) {
 ftpClient.disconnect();
 return success;
 }
} catch (FTPConnectionClosedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
} catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
success = true;
System.out.println("连接到ftp服务器:" + this.hostName + " 成功..开始登录");
return success;
}

private static FTPClientConfig Config() {
FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
conf.setRecentDateFormatStr("MM月dd日 HH:mm");
// conf.setRecentDateFormatStr("(YYYY年)?MM月dd日( HH:mm)?");
return conf;
}

/**
* 变更工作目录
*
* @param remoteDir
*
*/
public void changeDir(String remoteDir) {
try {
 this.remoteDir = remoteDir;
 ftpClient.changeWorkingDirectory(remoteDir);
} catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
System.out.println("变更工作目录为:" + remoteDir);
}

/**
* 返回上一级目录(父目录)
*/
public void toParentDir() {
try {
 ftpClient.changeToParentDirectory();
} catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
}

/**
* 列出当前工作目录下所有文件
*/
public String[] ListAllFiles() {
String[] names = this.ListFiles("*");
return this.sort(names);
}

/**
* 列出指定工作目录下的匹配文件
*
* @param dir
*  exp: /cim/
* @param file_regEx
*  通配符为*
*/
public String[] ListAllFiles(String dir, String file_regEx) {
String[] names = this.ListFiles(dir + file_regEx);
return this.sort(names);
}

/**
* 列出匹配文件
*
* @param file_regEx
*  匹配字符,通配符为*
*/
public String[] ListFiles(String file_regEx) {
try {
 /**
  * FTPFile[] remoteFiles = ftpClient.listFiles(file_regEx);
  * //System.out.println(remoteFiles.length); String[] name = new
  * String[remoteFiles.length]; if(remoteFiles != null) { for(int
  * i=0;i<remoteFiles.length;i++) { if(remoteFiles[i] == null)
  * name[i] = ""; else
  * if(remoteFiles[i].getName()==null||remoteFiles
  * [i].getName().equals
  * (".")||remoteFiles[i].getName().equals("..")) { name[i] = "";
  * } else name[i] = remoteFiles[i].getName();
  * System.out.println(name[i]); } }
  */
 ftpClient.enterLocalPassiveMode();
 String[] name = ftpClient.listNames(file_regEx);;
 if (name == null)
 return new String[0];

return this.sort(name);

} catch (Exception e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
return new String[0];
}

public void Lists(String reg) {
try {
 String[] a = ftpClient.listNames(reg);
 if (a != null) {
 for (String b : a) {
  System.out.println(b);
 }
 }
} catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
}

/**
* 设置传输文件的类型[文本文件或者二进制文件]
*
* @param fileType
*  --BINARY_FILE_TYPE,ASCII_FILE_TYPE
*/
public void setFileType(int fileType) {
try {
 ftpClient.setFileType(fileType);
} catch (IOException e) {
 e.printStackTrace();
}
}

/**
* 上传文件
*
* @param localFilePath
*  --本地文件路径+文件名
* @param newFileName
*  --新的文件名
*/
public void uploadFile(String localFilePath, String newFileName) {
// 上传文件
this.ftpClient.enterLocalPassiveMode();// 被动模式连接
BufferedInputStream buffIn = null;
try {
 buffIn = new BufferedInputStream(new FileInputStream(localFilePath));
 boolean ifUpload = ftpClient.storeFile(newFileName, buffIn);
 if (!ifUpload) {
 System.out.println("上传文件失败。。。");
 } else {
 System.out.println("上传文件成功。。。");
 }
} catch (Exception e) {
 e.printStackTrace();
} finally {
 try {
 if (buffIn != null)
  buffIn.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
}
}

/**
* 上传文件2
*
* @param file
*  --FileInputStream的文件
* @param newFileName
*  --新的文件名
*/
public void newUploadFile(FileInputStream file, String newFileName) {
// 上传文件
this.ftpClient.enterLocalPassiveMode();// 被动模式连接
BufferedInputStream buffIn = null;
try {
 buffIn = new BufferedInputStream(file);
 boolean ifUpload = ftpClient.storeFile(newFileName, buffIn);
 if (!ifUpload) {
 System.out.println("上传文件失败。。。");
 } else {
 System.out.println("上传文件成功。。。");
 }
} catch (Exception e) {
 e.printStackTrace();
} finally {
 try {
 if (buffIn != null)
  buffIn.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
}
}

/**
* 下载文件(单个)
*
* @param remoteFileName
*  --服务器上的文件名
* @param localFileName
*  --本地文件名
*/
public boolean downloadFile(String remoteFileName, String localFileName) {
this.ftpClient.enterLocalPassiveMode();// 被动模式连接
BufferedOutputStream buffOut = null;
try {
 buffOut = new BufferedOutputStream(new FileOutputStream(
  localFileName));
 boolean ifDownload = ftpClient
  .retrieveFile(remoteFileName, buffOut);
 if (!ifDownload) {
 System.out.println("下载文件失败。。。");
 return false;
 } else {
 System.out.println("下载文件成功。。。");
 }
} catch (Exception e) {
 e.printStackTrace();
 return false;
} finally {
 try {
 if (buffOut != null)
  buffOut.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
}
return true;
}

/**
* 关闭FTP连接
*/
public void close() {
try {
 if (ftpClient != null) {
 ftpClient.logout();
 ftpClient.disconnect();
 }
} catch (Exception e) {
 e.printStackTrace();
}
}

/**
* 冒泡排序字符串(从大到小)
*/
public String[] sort(String[] str_Array) {
if (str_Array == null) {
 throw new NullPointerException("The str_Array can not be null!");
}
String tmp = "";
for (int i = 0; i < str_Array.length; i++) {
 for (int j = 0; j < str_Array.length - i - 1; j++) {
 if (str_Array[j].compareTo(str_Array[j + 1]) < 0) {
  tmp = str_Array[j];
  str_Array[j] = str_Array[j + 1];
  str_Array[j + 1] = tmp;
 }
 }
}
return str_Array;
}

public static void main(String[] strs) {
FtpUtil2 FtpUtil2 = new FtpUtil2("192.168.1.112", 20011, "test1",
 "test1", "/", true);
FtpUtil2.downloadFile("test.txt", "d:\\test.txt");
}

}

下面是ZIP工具类:


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;

import com.ibatis.common.logging.Log;
import com.ibatis.common.logging.LogFactory;

public class ZipUtil {
private static final Log log = LogFactory.getLog(ZipUtil.class);

/**
* 压缩文件
*
* @param srcfile File[] 需要压缩的文件列表
* @param zipfile File 压缩后的文件
*/
public static OutputStream zipFiles(List<File> srcfile, OutputStream outputStream) {
byte[] buf = new byte[1024];
try {    
 // Create the ZIP file
 ZipOutputStream out = new ZipOutputStream(outputStream);
 // Compress the files
 for (int i = 0; i < srcfile.size(); i++) {
 File file = srcfile.get(i);
 FileInputStream in = new FileInputStream(file);
 // Add ZIP entry to output stream.
 out.putNextEntry(new ZipEntry(file.getName()));

// Transfer bytes from the file to the ZIP file
 int len;
 while ((len = in.read(buf)) > 0) {
  //System.out.println(len+"==============");

out.write(buf, 0, len);
 }
 // Complete the entry
 out.closeEntry();
 in.close();
 }
 // Complete the ZIP file
 out.close();
} catch (IOException e) {
 log.error("ZipUtil zipFiles exception:"+e);
}
return outputStream;
}

/**
* 压缩文件
*
* @param srcfile File[] 需要压缩的文件列表
* @param zipfile File 压缩后的文件
*/
public static void zipFiles(List<File> srcfile, File zipfile) {
byte[] buf = new byte[1024];
try {
 // Create the ZIP file
 ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipfile));
 // Compress the files
 for (int i = 0; i < srcfile.size(); i++) {
 File file = srcfile.get(i);
 FileInputStream in = new FileInputStream(file);
 // Add ZIP entry to output stream.
 out.putNextEntry(new ZipEntry(file.getName()));
 // Transfer bytes from the file to the ZIP file
 int len;
 while ((len = in.read(buf)) > 0) {
  out.write(buf, 0, len);
 }
 // Complete the entry
 out.closeEntry();
 in.close();
 }
 // Complete the ZIP file
 out.close();
} catch (IOException e) {
 log.error("ZipUtil zipFiles exception:"+e);
}
}

/**
* 压缩文件
* srcfile:key:文件名,value:文件对应的输入流
* @param srcfile
* @param zipfile
* @see
*/
public static void zipByStream(Map<String,InputStream> srcfile, File zipfile) {
try {
 // Create the ZIP file
 ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipfile));
 // Compress the files
 System.out.println(srcfile.entrySet().size());
 for (Map.Entry<String, InputStream> fileEntry:srcfile.entrySet()) {
 InputStream in = fileEntry.getValue();
 // Add ZIP entry to output stream.
 System.out.println(in.available());
 out.putNextEntry(new ZipEntry(fileEntry.getKey()));
 // Transfer bytes from the file to the ZIP file
 byte[] bytes=IOUtils.toByteArray(in);
 out.write(bytes);
 out.closeEntry();
 in.close();
 }
 // Complete the ZIP file
 out.close();
} catch (IOException e) {
 log.error("ZipUtil zipFiles exception:"+e);
 System.out.println(e.getMessage());
}
}

/**
* 压缩文件
* srcfile:key:文件名,value:文件对应的字节数组
* @param srcfile
* @param zipfile
* @see
*/
public static void zipByArray(Map<String,byte[]> srcfile, File zipfile) {
byte[] buf = new byte[1024];
try {
 // Create the ZIP file
 ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipfile));
 // Compress the files
 System.out.println(srcfile.entrySet().size());
 for (Map.Entry<String, byte[]> fileEntry:srcfile.entrySet()) {
 //InputStream in = fileEntry.getValue();
 // Add ZIP entry to output stream.
 out.putNextEntry(new ZipEntry(fileEntry.getKey()));
 // Transfer bytes from the file to the ZIP file
 byte[] bytes=fileEntry.getValue();//IOUtils.toByteArray(in);
 out.write(bytes);
 out.closeEntry();
 //in.close();
 }
 // Complete the ZIP file
 out.close();
} catch (IOException e) {
 log.error("ZipUtil zipFiles exception:"+e);
 System.out.println(e.getMessage());
}
}

/**
* 解压缩
*
* @param zipfile File 需要解压缩的文件
* @param descDir String 解压后的目标目录
*/
public static void unZipFiles(File zipfile, String descDir) {
try {
 // Open the ZIP file
 ZipFile zf = new ZipFile(zipfile);
 for (Enumeration entries = zf.entries(); entries.hasMoreElements();) {
 // Get the entry name
 ZipEntry entry = ((ZipEntry) entries.nextElement());
 String zipEntryName = entry.getName();
 InputStream in = zf.getInputStream(entry);
 // System.out.println(zipEntryName);
 OutputStream out = new FileOutputStream(descDir + zipEntryName);
 byte[] buf1 = new byte[1024];
 int len;
 while ((len = in.read(buf1)) > 0) {
  out.write(buf1, 0, len);
 }
 // Close the file and stream
 in.close();
 out.close();
 }
} catch (IOException e) {
 log.error("ZipUtil unZipFiles exception:"+e);
}
}

/**
* Main
*
* @param args
*/
public static void main(String[] args) {
List<File> srcfile=new ArrayList<File>();
srcfile.add(new File("d:\\1.jpg"));
srcfile.add(new File("d:\\2.jpg"));
srcfile.add(new File("d:\\3.jpg"));
srcfile.add(new File("d:\\4.jpg"));
File zipfile = new File("d:\\pic.zip");
ZipUtil.zipFiles(srcfile, zipfile);
}
}

来源:https://blog.csdn.net/dongyuxu342719/article/details/80280726

标签:java,下载图片,压缩图片
0
投稿

猜你喜欢

  • Qt for Android开发实例教程

    2023-06-27 10:00:39
  • 详解如何将Spring Boot应用跑在Docker容器中

    2023-04-25 08:08:58
  • Spring Security实现基于角色的访问控制框架

    2022-03-19 05:40:53
  • Spring activiti如何实现指定任务处理者

    2023-04-08 05:53:16
  • C# WinForm RichTextBox文本动态滚动显示文本方式

    2021-09-12 20:44:31
  • MyBatis-Plus实现多数据源的示例代码

    2023-11-11 12:58:11
  • 实例代码讲解JAVA 观察者模式

    2023-09-11 19:22:18
  • 基于C#解决库存扣减及订单创建时防止并发死锁的问题

    2023-03-16 20:59:53
  • Android开发apk反编译和二次打包教程

    2022-09-19 12:01:16
  • 基于C#的图表控件库 ScottPlot编译visual studio 2022

    2022-05-02 10:24:13
  • Android 创建依赖库的方法(保姆级教程)

    2023-03-12 03:55:51
  • Spring注解与P/C命名空间超详细解析

    2022-08-04 19:42:34
  • Spring Security 实现用户名密码登录流程源码详解

    2023-05-31 11:13:59
  • winform中的ListBox和ComboBox绑定数据用法实例

    2023-09-24 05:02:13
  • JVM中的GC初识

    2022-08-04 08:37:13
  • Java实现顺序栈原理解析

    2021-08-26 15:47:16
  • 浅谈Java编程中的synthetic关键字

    2022-01-27 18:48:41
  • android实现歌词自动滚动效果

    2022-03-16 17:09:10
  • 深入浅析Android坐标系统

    2021-12-15 05:26:34
  • IntelliJ IDEA安装目录和设置目录的说明(IntelliJ IDEA快速入门)

    2021-08-16 21:17:08
  • asp之家 软件编程 m.aspxhome.com