近期工作过程中,遇到了一个需要解析压缩包的功能开发。对于这个功能,如果频繁的解压压缩包,在性能上多少有些损耗,且解压文件,会产生大量中间文件,不适合后续管理。特别是现在都是基于容器化部署,如果文件操作处理不当,导致删除失败,将很快打满容器磁盘容量,引起容器崩溃重启。
综合考虑之后,采用ArchiveInputStream,在不解压文件的前提下,解析压缩包。
具体实践如下。
- 从SFTP获取压缩包
- 读取压缩包文件内容
- 删除本地压缩包文件
添加项目依赖,使用jcraft包来连接SFTP服务器。
com.jcraft jsch 0.1.54
引入依赖后,需要实现一个工具类来连接SFTP。此处只列出关键代码,如果详细工具类,可以网上找一下,有很多。
public class SFTPUtil {
public void connect() {
try {
JSch jsch = new JSch();
jsch.getSession(username, host, port);
sshSession = jsch.getSession(username, host, port);
if (log.isInfoEnabled()) {
log.info("Session created.");
}
sshSession.setPassword(password);
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
if (log.isInfoEnabled()) {
log.info("Session connected.");
}
Channel channel = sshSession.openChannel("sftp");
channel.connect();
if (log.isInfoEnabled()) {
log.info("Opening Channel.");
}
sftp = (ChannelSftp) channel;
if (log.isInfoEnabled()) {
log.info("Connected to " + host + ".");
}
}
catch (Exception e) {
log.error("Connected to " + host + "failed.", e);
}
}
public void disconnect() {
if (this.sftp != null) {
if (this.sftp.isConnected()) {
this.sftp.disconnect();
if (log.isInfoEnabled()) {
log.info("sftp is closed already");
}
}
}
if (this.sshSession != null) {
if (this.sshSession.isConnected()) {
this.sshSession.disconnect();
if (log.isInfoEnabled()) {
log.info("sshSession is closed already");
}
}
}
}
public boolean downloadFile(String remotePath, String remoteFileName, String localPath, String localFileName) {
FileOutputStream fieloutput = null;
if (!remotePath.endsWith("/")) {
remotePath += "/";
}
if (!localPath.endsWith("/")) {
localPath += "/";
}
String remoteFilePath = remotePath + remoteFileName;
try {
String filePath = localPath + localFileName;
SftpATTRS attrs = sftp.lstat(remoteFilePath);
if (attrs.isFifo()){
}
// sftp.cd(remotePath);
mkdirs(localPath);
File file = new File(filePath);
fieloutput = new FileOutputStream(file);
sftp.get(remoteFilePath, fieloutput);
if (log.isInfoEnabled()) {
log.info("===DownloadFile:" + remoteFileName + " success from sftp.");
}
return true;
} catch (FileNotFoundException e) {
log.warn("file not exists ,file name is " + remoteFilePath);
} catch ( SftpException | baseAppException e){
log.error("download file failed.file name is " + remoteFilePath, e);
} finally {
if (null != fieloutput) {
try {
fieloutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
}
从压缩包中获取文件
核心的代码就是下面这段
public final class TarFileUtil {
public static byte[] getBytesFromTarFile(File tarFile, String targetFilePath, String targetFileName) {
ArchiveInputStream archiveInputStream = null;
try {
archiveInputStream = getArchiveInputStream(tarFile);
TarArchiveEntry entry = null;
while ((entry = (TarArchiveEntry) archiveInputStream.getNextEntry()) != null) {
if (entry.getSize() <= 0) {
continue;
}
if (!StringUtils.isEmpty(targetFilePath) && !entry.getName().startsWith(targetFilePath)) {
continue;
}
if (!StringUtils.isEmpty(targetFileName) && !entry.getName().endsWith(targetFileName)) {
continue;
}
return FileUtil.getContent(archiveInputStream);
}
} catch (Exception e) {
LOGGER.error("获取压缩包文件失败!", e);
} finally {
if (null != archiveInputStream) {
try {
archiveInputStream.close();
} catch (IOException e) {
LOGGER.error("file close error!", e);
}
}
}
return null;
}
private static ArchiveInputStream getArchiveInputStream(File tarFile) throws IOException, ArchiveException {
if (StringUtils.endsWithIgnoreCase(tarFile.getName(), ".gz")) {
return new ArchiveStreamFactory()
.createArchiveInputStream("tar", new GZIPInputStream(new BufferedInputStream(new FileInputStream(tarFile))));
} else {
return new ArchiveStreamFactory()
.createArchiveInputStream("tar", new BufferedInputStream(new FileInputStream(tarFile)));
}
}
}
通过个方法还可以做一些文件操作的其他方法实现。比如我在工具类中加入了
// 将目标文件的内容转化为String,并返回 public static String readTarFileToStr(File tarFile, String targetFilePath, String targetFileName); // 将目标文件内容读取出来,写入到本地文件,并返回 public static File readTarFileToFile(File tarFile, String targetFilePath, String targetFileName); // 列举指定目录下,匹配上文件名的所有文件列表 public static ListlistFilesInPath(File tarFile, String targetFilePath, String targetFileName); // 将文件按行读出,并返回内容列表 public static List getLinesFromTarFile(File tarFile, String targetFilePath, String targetFileName);
你也可以根据自己的需要,新增其他方法。因为这些方法实现逻辑类似,此处不再赘述。
注意:ArchiveInputStream的读取和关闭,应保证在一个方法体里,尽量不要将ArchiveInputStream对象作为结果返回出去,在外层做关闭操作,以免文件流关闭失败,导致临时文件无法删除。
删除临时文件压缩包解析完成后,切记删除下载的压缩包。删除前,确认所有的文件流都关闭了。
public class ResolveHandel{
public void resolveTarFileMethod() {
try{
sftpUtil.connect();
sftpUtil.downloadFile(filePath + fileName);
// do something to resolve package
} catch (Exception e){
// deal with exception
} finally {
// 删除本地缓存文件
sftpUtil.deleteFile(filePath + fileName);
// 断开连接
sftpUtil.disconnect();
}
}
}
此处指列出了关键代码,如有不明白的地方,可以留言交流。



