Java压缩技术(七) TAR——Commons实现

在linux下,tar是一个归档命令。当然,如果配合gzip、bzip2就可以达到归档+压缩的效果! 
我们通过tar获得归档压缩文件其实恰恰包含了归档压缩两个操作,并且其操作次序也是先做归档操作,再做压缩操作! 通常我们忽略了归档的概念,将归档压缩文件简称为压缩文件!~ 

顺便复习一遍linux命令: 
tar cf <file.tar> <file>将由文件<file>创建名为<file.tar>归档文件,同时保留原文件。 
tar xf <file.tar>将由归档文件<file.tar>创建名为<file>的文件/目录,同时保留原文件。 

对于归档压缩,需分为gzip和bzip2,相应的linux为: 
gzip 
tar czf <file.tar.gz> <file>
将由文件<file>创建名为<file.tar.gz>归档压缩文件,同时保留原文件。 
tar xzf <file.tar.gz>将由归档压缩文件<file.tar.gz>创建名为<file>的文件/目录,同时保留原文件。 

bzip2 
tar cjf <file.tar.bz2> <file>
将由文件<file>创建名为<file.tar.bz2>归档压缩文件,同时保留原文件。 
tar xjf <file.tar.bz2>将由归档压缩文件<file.tar.bz2>创建名为<file>的文件/目录,同时保留原文件。 

今天的主角是Apache Commons Compress下用于Tar操作的三元干将 
TarArchiveEntry 类似于Java 原生的ZipEntry,可以理解为Tar归档添加项。 
TarArchiveOutputStream Tar归档输出流,用于归档。 
TarArchiveInputStream Tar归档输入流,用于解归档。 

至于jar,其实现方式与tar非常接近,我就不在这里废话了! 
JarArchiveEntry
 类似于Java 原生的ZipEntry,可以理解为Jar归档添加项。 
JarArchiveOutputStream Jar归档输出流,用于归档。 
JarArchiveInputStream Jar归档输入流,用于解归档。 

读过Java压缩技术(二)Java压缩技术(三)会发现,其实Tar的实现与Java原生的Zip实现方式基本上没有差别! 
先说归档,依旧需要考虑待归档的对象是文件还是目录: 

Java代码  收藏代码

  1. /** 
  2.  * 归档 
  3.  *  
  4.  * @param srcFile 
  5.  *            源路径 
  6.  * @param taos 
  7.  *            TarArchiveOutputStream 
  8.  * @param basePath 
  9.  *            归档包内相对路径 
  10.  * @throws Exception 
  11.  */  
  12. private static void archive(File srcFile, TarArchiveOutputStream taos,  
  13.         String basePath) throws Exception {  
  14.     if (srcFile.isDirectory()) {  
  15.         archiveDir(srcFile, taos, basePath);  
  16.     } else {  
  17.         archiveFile(srcFile, taos, basePath);  
  18.     }  
  19. }  


对于目录,需要区分空目录和包含文件的目录。 
如果是空目录,只要简单追加一个归档项(TarArchiveEntry)即可,但注意其名字的结尾需要使用"/"作为区分目录的标识符(String PATH = "/";)。 
如果是带有子文件的目录,则需要对其迭代归档: 

Java代码  收藏代码

  1. /** 
  2.  * 目录归档 
  3.  *  
  4.  * @param dir 
  5.  * @param taos 
  6.  *            TarArchiveOutputStream 
  7.  * @param basePath 
  8.  * @throws Exception 
  9.  */  
  10. private static void archiveDir(File dir, TarArchiveOutputStream taos,  
  11.         String basePath) throws Exception {  
  12.   
  13.     File[] files = dir.listFiles();  
  14.   
  15.     if (files.length < 1) {  
  16.         TarArchiveEntry entry = new TarArchiveEntry(basePath  
  17.                 + dir.getName() + PATH);  
  18.   
  19.         taos.putArchiveEntry(entry);  
  20.         taos.closeArchiveEntry();  
  21.     }  
  22.   
  23.     for (File file : files) {  
  24.   
  25.         // 递归归档  
  26.         archive(file, taos, basePath + dir.getName() + PATH);  
  27.   
  28.     }  
  29. }  


最后,来看归档操作: 

Java代码  收藏代码

  1. /** 
  2.  * 数据归档 
  3.  *  
  4.  * @param data 
  5.  *            待归档数据 
  6.  * @param path 
  7.  *            归档数据的当前路径 
  8.  * @param name 
  9.  *            归档文件名 
  10.  * @param taos 
  11.  *            TarArchiveOutputStream 
  12.  * @throws Exception 
  13.  */  
  14. private static void archiveFile(File file, TarArchiveOutputStream taos,  
  15.         String dir) throws Exception {  
  16.   
  17.     TarArchiveEntry entry = new TarArchiveEntry(dir + file.getName());  
  18.   
  19.     entry.setSize(file.length());  
  20.   
  21.     taos.putArchiveEntry(entry);  
  22.   
  23.     BufferedInputStream bis = new BufferedInputStream(new FileInputStream(  
  24.             file));  
  25.     int count;  
  26.     byte data[] = new byte[BUFFER];  
  27.     while ((count = bis.read(data, 0, BUFFER)) != -1) {  
  28.         taos.write(data, 0, count);  
  29.     }  
  30.   
  31.     bis.close();  
  32.   
  33.     taos.closeArchiveEntry();  
  34. }  


注意执行归档操作后,执行closeArchiveEntry()方法。 
Tar解归档,与Zip解压相似,一样要遍历获得归档项: 

Java代码  收藏代码

  1. /** 
  2.  * 文件 解归档 
  3.  *  
  4.  * @param destFile 
  5.  *            目标文件 
  6.  * @param tais 
  7.  *            ZipInputStream 
  8.  * @throws Exception 
  9.  */  
  10. private static void dearchive(File destFile, TarArchiveInputStream tais)  
  11.         throws Exception {  
  12.   
  13.     TarArchiveEntry entry = null;  
  14.     while ((entry = tais.getNextTarEntry()) != null) {  
  15.   
  16.         // 文件  
  17.         String dir = destFile.getPath() + File.separator + entry.getName();  
  18.   
  19.         File dirFile = new File(dir);  
  20.   
  21.         // 文件检查  
  22.         fileProber(dirFile);  
  23.   
  24.         if (entry.isDirectory()) {  
  25.             dirFile.mkdirs();  
  26.         } else {  
  27.             dearchiveFile(dirFile, tais);  
  28.         }  
  29.   
  30.     }  
  31. }  


最后,进行解归档: 

Java代码  收藏代码

  1. /** 
  2.  * 文件解归档 
  3.  *  
  4.  * @param destFile 
  5.  *            目标文件 
  6.  * @param tais 
  7.  *            TarArchiveInputStream 
  8.  * @throws Exception 
  9.  */  
  10. private static void dearchiveFile(File destFile, TarArchiveInputStream tais)  
  11.         throws Exception {  
  12.   
  13.     BufferedOutputStream bos = new BufferedOutputStream(  
  14.             new FileOutputStream(destFile));  
  15.   
  16.     int count;  
  17.     byte data[] = new byte[BUFFER];  
  18.     while ((count = tais.read(data, 0, BUFFER)) != -1) {  
  19.         bos.write(data, 0, count);  
  20.     }  
  21.   
  22.     bos.close();  
  23. }  


文件探针用于构建父目录: 

Java代码  收藏代码

  1. /** 
  2.  * 文件探针 
  3.  *  
  4.  * <pre> 
  5.  * 当父目录不存在时,创建目录! 
  6.  * </pre> 
  7.  *  
  8.  * @param dirFile 
  9.  */  
  10. private static void fileProber(File dirFile) {  
  11.   
  12.     File parentFile = dirFile.getParentFile();  
  13.     if (!parentFile.exists()) {  
  14.   
  15.         // 递归寻找上级目录  
  16.         fileProber(parentFile);  
  17.   
  18.         parentFile.mkdir();  
  19.     }  
  20.   
  21. }  


给出完整实现: 

Java代码  收藏代码

  1. /** 
  2.  * 2010-4-20 
  3.  */  
  4. package org.zlex.commons.compress;  
  5.   
  6. import java.io.BufferedInputStream;  
  7. import java.io.BufferedOutputStream;  
  8. import java.io.File;  
  9. import java.io.FileInputStream;  
  10. import java.io.FileOutputStream;  
  11.   
  12. import org.apache.commons.compress.archivers.tar.TarArchiveEntry;  
  13. import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;  
  14. import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;  
  15.   
  16. /** 
  17.  * TAR工具 
  18.  *  
  19.  * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a> 
  20.  * @since 1.0 
  21.  */  
  22. public abstract class TarUtils {  
  23.   
  24.     private static final String BASE_DIR = "";  
  25.   
  26.     // 符号"/"用来作为目录标识判断符  
  27.     private static final String PATH = "/";  
  28.     private static final int BUFFER = 1024;  
  29.   
  30.     private static final String EXT = ".tar";  
  31.   
  32.     /** 
  33.      * 归档 
  34.      *  
  35.      * @param srcPath 
  36.      * @param destPath 
  37.      * @throws Exception 
  38.      */  
  39.     public static void archive(String srcPath, String destPath)  
  40.             throws Exception {  
  41.   
  42.         File srcFile = new File(srcPath);  
  43.   
  44.         archive(srcFile, destPath);  
  45.   
  46.     }  
  47.   
  48.     /** 
  49.      * 归档 
  50.      *  
  51.      * @param srcFile 
  52.      *            源路径 
  53.      * @param destPath 
  54.      *            目标路径 
  55.      * @throws Exception 
  56.      */  
  57.     public static void archive(File srcFile, File destFile) throws Exception {  
  58.   
  59.         TarArchiveOutputStream taos = new TarArchiveOutputStream(  
  60.                 new FileOutputStream(destFile));  
  61.   
  62.         archive(srcFile, taos, BASE_DIR);  
  63.   
  64.         taos.flush();  
  65.         taos.close();  
  66.     }  
  67.   
  68.     /** 
  69.      * 归档 
  70.      *  
  71.      * @param srcFile 
  72.      * @throws Exception 
  73.      */  
  74.     public static void archive(File srcFile) throws Exception {  
  75.         String name = srcFile.getName();  
  76.         String basePath = srcFile.getParent();  
  77.         String destPath = basePath + name + EXT;  
  78.         archive(srcFile, destPath);  
  79.     }  
  80.   
  81.     /** 
  82.      * 归档文件 
  83.      *  
  84.      * @param srcFile 
  85.      * @param destPath 
  86.      * @throws Exception 
  87.      */  
  88.     public static void archive(File srcFile, String destPath) throws Exception {  
  89.         archive(srcFile, new File(destPath));  
  90.     }  
  91.   
  92.     /** 
  93.      * 归档 
  94.      *  
  95.      * @param srcPath 
  96.      * @throws Exception 
  97.      */  
  98.     public static void archive(String srcPath) throws Exception {  
  99.         File srcFile = new File(srcPath);  
  100.   
  101.         archive(srcFile);  
  102.     }  
  103.   
  104.     /** 
  105.      * 归档 
  106.      *  
  107.      * @param srcFile 
  108.      *            源路径 
  109.      * @param taos 
  110.      *            TarArchiveOutputStream 
  111.      * @param basePath 
  112.      *            归档包内相对路径 
  113.      * @throws Exception 
  114.      */  
  115.     private static void archive(File srcFile, TarArchiveOutputStream taos,  
  116.             String basePath) throws Exception {  
  117.         if (srcFile.isDirectory()) {  
  118.             archiveDir(srcFile, taos, basePath);  
  119.         } else {  
  120.             archiveFile(srcFile, taos, basePath);  
  121.         }  
  122.     }  
  123.   
  124.     /** 
  125.      * 目录归档 
  126.      *  
  127.      * @param dir 
  128.      * @param taos 
  129.      *            TarArchiveOutputStream 
  130.      * @param basePath 
  131.      * @throws Exception 
  132.      */  
  133.     private static void archiveDir(File dir, TarArchiveOutputStream taos,  
  134.             String basePath) throws Exception {  
  135.   
  136.         File[] files = dir.listFiles();  
  137.   
  138.         if (files.length < 1) {  
  139.             TarArchiveEntry entry = new TarArchiveEntry(basePath  
  140.                     + dir.getName() + PATH);  
  141.   
  142.             taos.putArchiveEntry(entry);  
  143.             taos.closeArchiveEntry();  
  144.         }  
  145.   
  146.         for (File file : files) {  
  147.   
  148.             // 递归归档  
  149.             archive(file, taos, basePath + dir.getName() + PATH);  
  150.   
  151.         }  
  152.     }  
  153.   
  154.     /** 
  155.      * 数据归档 
  156.      *  
  157.      * @param data 
  158.      *            待归档数据 
  159.      * @param path 
  160.      *            归档数据的当前路径 
  161.      * @param name 
  162.      *            归档文件名 
  163.      * @param taos 
  164.      *            TarArchiveOutputStream 
  165.      * @throws Exception 
  166.      */  
  167.     private static void archiveFile(File file, TarArchiveOutputStream taos,  
  168.             String dir) throws Exception {  
  169.   
  170.         /** 
  171.          * 归档内文件名定义 
  172.          *  
  173.          * <pre> 
  174.          * 如果有多级目录,那么这里就需要给出包含目录的文件名 
  175.          * 如果用WinRAR打开归档包,中文名将显示为乱码 
  176.          * </pre> 
  177.          */  
  178.         TarArchiveEntry entry = new TarArchiveEntry(dir + file.getName());  
  179.   
  180.         entry.setSize(file.length());  
  181.   
  182.         taos.putArchiveEntry(entry);  
  183.   
  184.         BufferedInputStream bis = new BufferedInputStream(new FileInputStream(  
  185.                 file));  
  186.         int count;  
  187.         byte data[] = new byte[BUFFER];  
  188.         while ((count = bis.read(data, 0, BUFFER)) != -1) {  
  189.             taos.write(data, 0, count);  
  190.         }  
  191.   
  192.         bis.close();  
  193.   
  194.         taos.closeArchiveEntry();  
  195.     }  
  196.   
  197.     /** 
  198.      * 解归档 
  199.      *  
  200.      * @param srcFile 
  201.      * @throws Exception 
  202.      */  
  203.     public static void dearchive(File srcFile) throws Exception {  
  204.         String basePath = srcFile.getParent();  
  205.         dearchive(srcFile, basePath);  
  206.     }  
  207.   
  208.     /** 
  209.      * 解归档 
  210.      *  
  211.      * @param srcFile 
  212.      * @param destFile 
  213.      * @throws Exception 
  214.      */  
  215.     public static void dearchive(File srcFile, File destFile) throws Exception {  
  216.   
  217.         TarArchiveInputStream tais = new TarArchiveInputStream(  
  218.                 new FileInputStream(srcFile));  
  219.         dearchive(destFile, tais);  
  220.   
  221.         tais.close();  
  222.   
  223.     }  
  224.   
  225.     /** 
  226.      * 解归档 
  227.      *  
  228.      * @param srcFile 
  229.      * @param destPath 
  230.      * @throws Exception 
  231.      */  
  232.     public static void dearchive(File srcFile, String destPath)  
  233.             throws Exception {  
  234.         dearchive(srcFile, new File(destPath));  
  235.   
  236.     }  
  237.   
  238.     /** 
  239.      * 文件 解归档 
  240.      *  
  241.      * @param destFile 
  242.      *            目标文件 
  243.      * @param tais 
  244.      *            ZipInputStream 
  245.      * @throws Exception 
  246.      */  
  247.     private static void dearchive(File destFile, TarArchiveInputStream tais)  
  248.             throws Exception {  
  249.   
  250.         TarArchiveEntry entry = null;  
  251.         while ((entry = tais.getNextTarEntry()) != null) {  
  252.   
  253.             // 文件  
  254.             String dir = destFile.getPath() + File.separator + entry.getName();  
  255.   
  256.             File dirFile = new File(dir);  
  257.   
  258.             // 文件检查  
  259.             fileProber(dirFile);  
  260.   
  261.             if (entry.isDirectory()) {  
  262.                 dirFile.mkdirs();  
  263.             } else {  
  264.                 dearchiveFile(dirFile, tais);  
  265.             }  
  266.   
  267.         }  
  268.     }  
  269.   
  270.     /** 
  271.      * 文件 解归档 
  272.      *  
  273.      * @param srcPath 
  274.      *            源文件路径 
  275.      *  
  276.      * @throws Exception 
  277.      */  
  278.     public static void dearchive(String srcPath) throws Exception {  
  279.         File srcFile = new File(srcPath);  
  280.   
  281.         dearchive(srcFile);  
  282.     }  
  283.   
  284.     /** 
  285.      * 文件 解归档 
  286.      *  
  287.      * @param srcPath 
  288.      *            源文件路径 
  289.      * @param destPath 
  290.      *            目标文件路径 
  291.      * @throws Exception 
  292.      */  
  293.     public static void dearchive(String srcPath, String destPath)  
  294.             throws Exception {  
  295.   
  296.         File srcFile = new File(srcPath);  
  297.         dearchive(srcFile, destPath);  
  298.     }  
  299.   
  300.     /** 
  301.      * 文件解归档 
  302.      *  
  303.      * @param destFile 
  304.      *            目标文件 
  305.      * @param tais 
  306.      *            TarArchiveInputStream 
  307.      * @throws Exception 
  308.      */  
  309.     private static void dearchiveFile(File destFile, TarArchiveInputStream tais)  
  310.             throws Exception {  
  311.   
  312.         BufferedOutputStream bos = new BufferedOutputStream(  
  313.                 new FileOutputStream(destFile));  
  314.   
  315.         int count;  
  316.         byte data[] = new byte[BUFFER];  
  317.         while ((count = tais.read(data, 0, BUFFER)) != -1) {  
  318.             bos.write(data, 0, count);  
  319.         }  
  320.   
  321.         bos.close();  
  322.     }  
  323.   
  324.     /** 
  325.      * 文件探针 
  326.      *  
  327.      * <pre> 
  328.      * 当父目录不存在时,创建目录! 
  329.      * </pre> 
  330.      *  
  331.      * @param dirFile 
  332.      */  
  333.     private static void fileProber(File dirFile) {  
  334.   
  335.         File parentFile = dirFile.getParentFile();  
  336.         if (!parentFile.exists()) {  
  337.   
  338.             // 递归寻找上级目录  
  339.             fileProber(parentFile);  
  340.   
  341.             parentFile.mkdir();  
  342.         }  
  343.   
  344.     }  
  345.   
  346. }  



最后给出测试用例: 

Java代码  收藏代码

  1. /** 
  2.  * 2010-4-20 
  3.  */  
  4. package org.zlex.commons.compress;  
  5.   
  6. import static org.junit.Assert.*;  
  7.   
  8. import java.io.DataInputStream;  
  9. import java.io.File;  
  10. import java.io.FileInputStream;  
  11. import java.io.FileOutputStream;  
  12.   
  13. import org.junit.Before;  
  14. import org.junit.Test;  
  15.   
  16. /** 
  17.  * Tar测试 
  18.  *  
  19.  * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a> 
  20.  * @since 1.0 
  21.  */  
  22. public class TarUtilsTest {  
  23.     private String inputStr;  
  24.     private String name = "data.xml";  
  25.   
  26.     @Before  
  27.     public void before() {  
  28.         StringBuilder sb = new StringBuilder();  
  29.         sb.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");  
  30.         sb.append("\r\n");  
  31.         sb.append("<dataGroup>");  
  32.         sb.append("\r\n\t");  
  33.         sb.append("<dataItem>");  
  34.         sb.append("\r\n\t\t");  
  35.         sb.append("<data>");  
  36.         sb.append("Test");  
  37.         sb.append("</data>");  
  38.         sb.append("\r\n\t");  
  39.         sb.append("<dataItem>");  
  40.         sb.append("\r\n");  
  41.         sb.append("</dataGroup>");  
  42.   
  43.         inputStr = sb.toString();  
  44.     }  
  45.   
  46.     @Test  
  47.     public void testArchiveFile() throws Exception {  
  48.   
  49.         byte[] contentOfEntry = inputStr.getBytes();  
  50.   
  51.         String path = "d:/" + name;  
  52.   
  53.         FileOutputStream fos = new FileOutputStream(path);  
  54.   
  55.         fos.write(contentOfEntry);  
  56.         fos.flush();  
  57.         fos.close();  
  58.   
  59.         TarUtils.archive(path);  
  60.   
  61.         TarUtils.dearchive(path + ".tar");  
  62.   
  63.         File file = new File(path);  
  64.   
  65.         FileInputStream fis = new FileInputStream(file);  
  66.   
  67.         DataInputStream dis = new DataInputStream(fis);  
  68.   
  69.         byte[] data = new byte[(int) file.length()];  
  70.   
  71.         dis.readFully(data);  
  72.   
  73.         fis.close();  
  74.   
  75.         String outputStr = new String(data);  
  76.         assertEquals(inputStr, outputStr);  
  77.   
  78.     }  
  79.   
  80.     @Test  
  81.     public void testArchiveDir() throws Exception {  
  82.         String path = "d:/fd";  
  83.         TarUtils.archive(path);  
  84.   
  85.         TarUtils.dearchive(path + ".tar""d:/fds");  
  86.     }  
  87.   
  88. }  


执行代码,来看下效果: 
 
这是原始文件。 
 
这是归档后的文件。 
注意红框,这里没有经过任何压缩! 
除了tar、zip,其实还有很多归档算法,如ar、jar、cpio。其实现方式,与上述内容较为接近。 
至于压缩成*.tar.gz、*.tar.bz2,请朋友们参照前几篇内容! 

完整实现见附件! 

相关链接: 
Java压缩技术(一) ZLib 
Java压缩技术(二) ZIP压缩——Java原生实现 
Java压缩技术(三) ZIP解压缩——Java原生实现 
Java压缩技术(四) GZIP——Java原生实现 
Java压缩技术(五) GZIP相关——浏览器解析 
Java压缩技术(六) BZIP2——Commons实现 
Java压缩技术(七) TAR——Commons实现 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,可以使用Apache Commons Compress库来实现将文件压缩tar.gz。具体步骤如下: 1. 首先,确保已经导入了Apache Commons Compress库。你可以在Maven项目的pom.xml文件中添加以下依赖项来引入该库: ```xml <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.20</version> </dependency> ``` 2. 创建一个`TarArchiveOutputStream`对象来处理tar文件的输出流,并将其连接到`GzipCompressorOutputStream`对象上,以处理gzip压缩。代码如下: ```java import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import java.io.*; public class FileCompressor { public static void compressToTarGz(String inputFilePath, String outputFilePath) throws IOException { File inputFile = new File(inputFilePath); try (FileInputStream fis = new FileInputStream(inputFile); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream(outputFilePath); BufferedOutputStream bos = new BufferedOutputStream(fos); GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(bos); TarArchiveOutputStream tos = new TarArchiveOutputStream(gzos)) { // 设置tar文件的编码格式 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); // 创建TarArchiveEntry对象,并设置文件路径 TarArchiveEntry entry = new TarArchiveEntry(inputFile.getName()); // 将文件添加到tar输出流中 tos.putArchiveEntry(entry); // 将文件内容写入tar输出流 byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { tos.write(buffer, 0, len); } // 关闭tar输出流 tos.closeArchiveEntry(); } } } ``` 3. 在你的代码中调用这个`compressToTarGz`方法,并传入输入文件路径和输出文件路径。例如: ```java public class Main { public static void main(String[] args) { try { FileCompressor.compressToTarGz("/path/to/input/file.txt", "/path/to/output/file.tar.gz"); } catch (IOException e) { e.printStackTrace(); } } } ``` 通过以上步骤,你就可以使用Java将文件压缩tar.gz文件了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值