前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

1、File

1.1、创建文件

1、路径不存在,fileNameTxt.createNewFile() 会抛出异常

2、只有路径,不会创建出文件,也不会报错,无法创建

3、路径存在,文件不存在,创建成功

4、没有路径,只有文件文件名,和则创建的文件和src一个级别,创建成功

/**
* 1、路径不存在,fileNameTxt.createNewFile() 会抛出异常
* 2、只有路径,不会创建出文件,也不会报错,无法创建,但是不会报错
* 3、路径存在,文件不存在,创建成功
* 4、没有路径,只有文件文件名,和则创建的文件和src一个级别,创建成功
*/
@Test
public void createFile() throws IOException {
    String filePath = "newFile.txt";
    File fileNameTxt = new File(filePath);
    if (!fileNameTxt.exists()) {
        fileNameTxt.createNewFile();
    }
    log.info("创建成功");
}

1.2、具备目录,创建文件

/**
* 2、创建有目录的文件
* 通过父的目录引入文件   directoryName 是父类
*/
@Test
public void createHavaDirectoryFile() throws IOException {
    String directoryName = "D:/test/file/d02_createHavaDirectoryFile";
    String fileName = "directoryName.txt";
    File directoryNameTxt = new File(directoryName, fileName);
    if (!directoryNameTxt.exists()) {
        directoryNameTxt.createNewFile();
    }
    log.info("创建有目录的文件成功");
}

1.3、判断文件、目录

/**
* 3、 判断文件、目录
*/
@Test
public void judgeFileOrDirectory() {
    File file = new File("D:/test/file/d02_createHavaDirectoryFile");
    log.info("测试开始");
    if (file.isFile()) {
        log.info(file.getPath() + "是一个文件");
    } else if (file.isDirectory()) {
        log.info(file.getPath() + "是一个目录");
    } else {
        log.info(file.getPath() + "不是文件也不是目录");
    }
}

1.4、创建目录

创建目录,即使路径不存在,也会创建相关路径,因为是mkdirs

   /**
     * 3、创建目录
     * 判断是不是目录
     * 创建目录,即使路径不存在,也会创建相关路径,因为是mkdirs
     */
    @Test
    public void createDirectory() {
        //引入目录
        File directoryName = new File("D:/test/healerjean/file");
        if (!directoryName.exists()) {
            directoryName.mkdirs();
            log.info(directoryName.getPath() + "创建目录成功");
        }
    }

1.5、普通复制文件

第一个文件以及路径必须存在, 否则fileInputStream会报错

第二个文件可以不存在,但是路径必须存在,如果路径不存在则new FileOutputStream会报错

/**
 * 5、复制文件
 * Input第一个文件以及路径必须存在, 否则 FileOutputStream out = new FileOutputStream(outFilePath); FileNotFoundException 异常
* Output第二个文件可以不存在,但是路径必须存在,如果路径不存在则FileOutSteam会报错
*/
public void copyFile() throws IOException {
    String inFilePath = "D:/test/file/d03_copyFile/exist/file.txt";
    String outFilePath = "D:/test/file/d03_copyFile/no_exist/newfile.txt";

    FileInputStream ins = new FileInputStream(inFilePath);
    FileOutputStream out = new FileOutputStream(outFilePath);
    byte[] b = new byte[1024];
    int n = 0;
    while ((n = ins.read(b)) != -1) {
        out.write(b, 0, n);
    }
    ins.close();
    out.close();
    log.info("复制文件成功");
}

1.6、递归目录

1.6.1、递归整个目录

file.listFiles() 是获取file这个对象也就是file这个目录下面的文件和文件夹的集合

/**
* 6、递归遍历整个目录的文件
* file.listFiles()是获取file这个对象也就是file这个目录下面的文件和文件夹的集合
*/
public void cycleFiles(File file) {
    File[] files = file.listFiles();
    for (File sonFile : files) {
        if (sonFile.isDirectory()) {
            cycleFiles(sonFile);
        } else {
            log.info(sonFile.getAbsolutePath());
        }
    }
    log.info(file.getAbsolutePath());
}
@Test
public void testCycleFiles() {
    File file = new File("D:/test");
    cycleFiles(file);
}

1.6.2、 递归递归整个目录,并读取文件内容进行匹配

/**
* 6.2 、递归目录,读取文件内容进行匹配
* size :用于保留统计记录的个数
*/
private static int size = 1;
public void cycleFileContent(File file, String content) throws Exception {
    File[] files = file.listFiles();
    for (File f : files) {
        if (f.isDirectory()) {
            cycleFileContent(f, content);
        } else {
            String path = f.getPath();
            FileInputStream fileInputStream = new FileInputStream(f);
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fileInputStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            String str = new String(outStream.toByteArray(), "utf-8");
            if (str.contains(content)) {
                log.info("第【{}】个文件匹配到内容,路径为:{}", size, f.getPath());
                size++;
            }
        }
    }
}
@Test
public void testCycleFileContent() throws Exception {
    File file = new File("D:/test");
    String content = "co";
    cycleFileContent(file, content);
}

1.7、读取文件内容

1.7.1、读取整个文件内容,

转化为ByteArrayOutputStream读取

/**
* 7.1、读取文件内容:转化为ByteArrayOutputStream读取
*/
@Test
public void readFileContent() throws Exception {
    File file = new File("D:\\test\\file\\file.txt");
    FileInputStream fileInputStream = new FileInputStream(file);
    ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
    int len = 0;
    byte[] buffer = new byte[1024];
    while ((len = fileInputStream.read(buffer)) != -1) {
        byteOutputStream.write(buffer, 0, len);
    }
    String txtValue = new String(byteOutputStream.toByteArray(), "utf-8");
    log.info(txtValue);
}

1.7.2、一行一行读取文件内容

new BufferedReader(new InputStreamReader(new ByteArrayInputStream(txtByteArray), “utf-8”));

    /**
     * 7.读取文件内容
     * 7.2、按照行,一行一行读取txt内容
     */
    @Test
    public void readFileLineContent() throws Exception {
        File file = new File("D:\\test\\file\\file.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        int len = 0;
        byte[] buffer = new byte[1024];
        while ((len = fileInputStream.read(buffer)) != -1) {
            byteOutputStream.write(buffer, 0, len);
        }
        byte[] txtByteArray = (byteOutputStream.toByteArray());
        BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(txtByteArray), "utf-8"));
        String lineContent = null;
        int line = 0;
        while ((lineContent = reader.readLine()) != null) {
            line++;
            log.info("第【{}】行的内容为:{}", line, lineContent);
        }
    }

1.8、根据字符串生成内容

  /**
     * 8、根据内容生成文件
     */
    @Test
    public void contentToFile() throws Exception {
        String content = "我是大好人";
        String fileName = UUID.randomUUID().toString().replace("-", "") + ".txt";
        FileOutputStream outputStream = new FileOutputStream("D:/test/" + fileName);
        byte[] buffer = content.getBytes("utf-8");
        outputStream.write(buffer);
        log.info("文件【{}】创建成功", fileName);
        outputStream.close();
    }

1.9、局域网文件调用

File file = new File("//192.168.31.1/file");

1.10、临时目录


/**
* 9、File.createTempFile 所在目录 C:\Users\HealerJean\AppData\Local\Temp
* 注意:1、 prefix必须大于3个字符,2、suffix需要带上 . , 比如:.png、.zip
*
 */
@Test
public void test() {
    try {
        //创建文件 a_name.4788216370145255403.jpg ,中间是随机生成的
        // File jpgFile = File.createTempFile("a_name.", ".jpg");


        //创建目录 scf.contract.4137975757793800315.dir
        File directory = File.createTempFile("scf.contract.", ".dir");
        directory.delete();
        directory.mkdirs();

        //指定目录中创建文件
        File pdfFile = File.createTempFile("scf.contract.", ".pdf", new File("C:\\Users\\HealerJean\\AppData\\Local\\Temp\\healerjean"));

    } catch (IOException e) {
        e.printStackTrace();
    }
}

2、上传下载

2.1、上传


@ApiOperation(
    value = "文件上传",
    notes = "文件上传",
    consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
    produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
    response = String.class)
@PostMapping(value = "upload", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String upload(MultipartFile file) {
    log.info("文件管理--------文件上传--------请求参数{}", file);
    //1、确定文件存储目录
    String javaIoTmpdir = System.getProperty("java.io.tmpdir");
    File tempFile = new File(javaIoTmpdir);
    if (!tempFile.exists()) {
        tempFile.mkdirs();
    }

    // 2、文件上传
    String fileName = file.getOriginalFilename();
    File outFile = new File(tempFile, fileName);
    // InputStream inputStream = null;
    // FileOutputStream fileOutputStream = null;
    try {
        // 1、inputstream -> 本地文件
        FileUtils.copyInputStreamToFile(file.getInputStream(), outFile);

        // 2、MultipartFile文件  -> 本地文件
        // file.transferTo(outFile);

        // 3、MultipartFile 文件获取字节 -> OutputStream
        // byte[] bytes = file.getBytes();
        // fileOutputStream = (new FileOutputStream(outFile));
        // fileOutputStream.write(bytes);

        // 4、InputStream -> OutputStream
        // inputStream = file.getInputStream();
        // fileOutputStream = (new FileOutputStream(outFile));
        // IOUtils.copy(inputStream, fileOutputStream);

        log.info("文件管理--------文件上传成功--------上传文件名{}", file.getOriginalFilename());
    } catch (IOException e) {
        log.info("文件上传失败");
        throw new RuntimeException("文件上传失败", e);
    } finally {
        log.info("准备开始关闭流");
        // try {
        //     if (fileOutputStream != null) {
        //         fileOutputStream.close();
        //     }
        // } catch (IOException e) {
        //     log.error("流关闭失败", e);
        // }
        // try {
        //     if (inputStream != null) {
        //         inputStream.close();
        //     }
        // } catch (IOException e) {
        //     log.error("流关闭失败", e);
        // }
    }
    return fileName;
}

2.2、下载

    @ApiOperation(
            value = "文件下载",
            notes = "文件下载",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
            response = String.class)
    @GetMapping(value = "download/{fileName}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void downLoad(HttpServletResponse response, @PathVariable String fileName, Boolean preview) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            log.info("文件管理--------文件下载--------请求参数{}", fileName);
            String javaIoTmpdir = System.getProperty("java.io.tmpdir");
            File file = new File(javaIoTmpdir, fileName);
            if (!file.exists()) {
                throw new BusinessException("文件不存在");
            }
            inputStream = new FileInputStream(file);
            outputStream = response.getOutputStream();
            if (preview != null && !preview) {
                //强制浏览器下载
                log.info("文件管理--------强制浏览器下载--------文件名{}", fileName);
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } else {
                log.info("文件管理--------文件预览--------文件名{}", fileName);
                //浏览器尝试打开,支持office online或浏览器预览pdf功能
                response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            }
            IOUtils.copy(inputStream, outputStream);
        } catch (Exception e) {
            log.info("文件:{},下载失败", fileName, e);
            throw new RuntimeException("文件上传失败", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("inputStream未正确关闭");
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("outputStream未正确关闭");
                }
            }
        }
    }

2.3、多个文件上传

@Transient
private String attachPics;
@Transient
private String attachFileNames;
@Transient
private String attachFileTypes;
@Transient
private List<OldBackGroundBalanceTransferUserAttachment> attachmentList;





@Override
public void saveOldBackGroundBalanceTransferUser(Long adminId, OldBackGroundBalanceTransferUser data) {

    data.setAdminId(adminId);
    data.setAuditStatus(EnumOldBackGroundBalanceTranferStatus.待审核.status);

    OldBackrUser tempData=   oldBackGroundBalanceTransferUserDAO.save(data);
    oldBackrUserAttachmentDAO.deleteByEventId(tempData.getId());
    if(tempData!=null) {
        String[] pics = tempData.getAttachPics().split(",");
        String[] fileNames = tempData.getAttachFileNames().split(",");
        String[] fileTypes = tempData.getAttachFileTypes().split(",");
        //保存附件
        if(StringUtils.isNotBlank(pics[0])) {
            for (int i = 0; i < pics.length; i++) {
                OldBackGroundBalanceTransferUserAttachment attachment = new OldBackGroundBalanceTransferUserAttachment();
                attachment.setFileUrl(pics[i]);
                attachment.setFileName(fileNames[i]);
                attachment.setFileType(Integer.valueOf(fileTypes[i]));
                attachment.setEventId(tempData.getId());
                attachment.setCdate(new Date());
                oldBackGroundBalanceTransferUserAttachmentDAO.save(attachment);
            }
        }
    }
}

2.4、文件压缩下载


    @ApiOperation(
            value = "zip文件下载",
            notes = "zip文件下载",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
            response = String.class)
    @GetMapping(value = "zipDownload", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void zipDownload(HttpServletResponse response,  String fileDir) throws IOException {
        log.info("文件管理--------zip文件下载--------请求参数{}", fileDir);
        File dirFile = new File(fileDir);
        if (!dirFile.exists()) {
            throw new BusinessException("文件不存在");
        }

        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(dirFile.getName() + ".zip", "UTF-8"));
        List<Pair<String, InputStream>> filePairs = getFilePairs(dirFile);
        ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream());
        for (Pair<String, InputStream> pair : filePairs) {
            ZipEntry zipEntry = new ZipEntry(pair.getLeft());
            zipOut.putNextEntry(zipEntry);
            InputStream inputStream = pair.getRight();
            byte[] bytes = new byte[1024];
            int length;
            while ((length = inputStream.read(bytes)) != -1) {
                zipOut.write(bytes, 0, length);
            }
            inputStream.close();
        }
        zipOut.close();
    }

    private List<Pair<String, InputStream>> getFilePairs(File dirFile) throws FileNotFoundException {
        List<Pair<String, InputStream>> pairList = new ArrayList<>();
        File[] files = dirFile.listFiles();
        for (File file : files) {
            Pair<String, InputStream> pair = Pair.of(file.getName(), new FileInputStream(file));
            pairList.add(pair);
        }
        return pairList;
    }


    @ApiOperation(
            value = "zip文件下载2",
            notes = "zip文件下载2",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
            response = String.class)
    @GetMapping(value = "zipDownload2", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void zipDownload2(HttpServletResponse response,  String fileDir) {
        log.info("文件管理--------zip文件下载2--------请求参数{}", fileDir);
        File dirFile = new File(fileDir);
        if (!dirFile.exists()) {
            throw new BusinessException("文件不存在");
        }

        //2、压缩
        ZipUtils.compress(fileDir);
        //3、压缩文件下载
        File file = new File(fileDir + ".zip");
        InputStream inputStream = null;
        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName() + ".zip", "UTF-8"));
            inputStream = new FileInputStream(file);
            IOUtils.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            log.error("文件下载异常", e);
            throw new RuntimeException("文件下载异常");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("inputStream流关闭失败", e);
                }
            }
        }

    }

3、流相关

3.1、流/文件复制

3.1.1、Inputstream -> 本地文件

copyInputStreamToFile(InputStream source, File destination)

File localFile = new File(tempFile,fileName);
FileUtils.copyInputStreamToFile(file.getInputStream(),localFile);

3.1.2 源文件 -> 本地文件

3.1.2.1、MultipartFile文件 -> 本地文件

transferTo(File var1)

File localFile = new File(tempFile,fileName);
file.transferTo(localFile);

3.1.2.2、File文件 ->本地文件

 FileUtils.copyFile(File srcFile, File destFile)

3.1.3、MultipartFile 文件获取字节 -> OutputStream

byte[] bytes = file.getBytes();
File localFile = new File(tempFile,fileName);
BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(localFile));
stream.write(bytes);

3.1.4、InputStream -> OutputStream

IOUtils.copy(inputStream, outputStream);

IOUtils.copy(inputStream, outputStream);
inputStream.close();
outputStream.close();

3.1.5、文件Url -> InputStream

URL url = new URL(StringEscapeUtils.unescapeHtml(netUrl));

HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(30*1000);
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");

InputStream  stream = conn.getInputStream();

3.1.6、图片Url -> OutStream

URL urlStr = new URL(url);
BufferedImage bufferedImage = ImageIO.read(urlStr);

ImageIO.write(bufferedImage, "jpg", response.getOutputStream());

3.1.7、Inputstream –>String

IOUtils.toString(super.getInputStream(), "utf-8");


InputStream is = new FileInputStream("D://test1.txt");
List<String> lines = IOUtils.readLines(is);

3.1.8、Inputstream –> byte[]

IOUtils.toByteArray(inputStream)

3.1.9、String –> Inputstream

 InputStream is = IOUtils.toInputStream("hello world");

3.1.10、String –> Outputstream

OutputStream os = new FileOutputStream("E:/test.txt");
IOUtils.write("hello write!",os);

4、流关闭

4.1、流为什么一定要关闭

首先需要明白的一点是GC是回收内存的,关于文件。网络Io,它是属于系统资源的 ,GC只管内存不管别的资源,GC只能回收内存。假如有内存以外的其它资源依附在Java对象上,比如文件,比如输入输出设备(键盘/屏幕等),等等。这些是不能自动关闭,需要我们手动关闭

流不单在内存中分配了空间,也在操作系统占有了资源,java的gc是能从内存中回收不使用的对象,但对操作系统分配的资源是无能为力的,所以就要调用close()方法来通知OS来释放这个资源,然后才可以GC回收。

close()方法是关闭,而GC是销毁。

其实这很象垃圾桶和垃圾工厂的关系,我们把垃圾放入垃圾桶,垃圾并没有被销毁,只是表明:这东西我不要(close())了。而垃圾工厂才是真的把垃圾给处理了。如果你不把垃圾放入垃圾桶(没有close()),垃圾工厂是不敢冲到你家,把垃圾拿去销毁的。

一般情况下是:先打开的后关闭,后打开的先关闭

  1. 包装关系:可以只关闭处理流(包装流),不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。

  2. 依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如,处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader.
处理流(包装流):是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

4.2、流的正确关闭姿势

4.2.1、try catch,要在finaly 中关闭流

 /**
     * 1.1、try catch,要在finaly 中关闭流
     */
    @Test
    public void test1() {

        File file = new File("/Users/healerjean/Desktop/test/file.txt");
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file);
            //TODO 操作代码
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    log.error("fileOutputStream未正确关闭");
                }
            }
        }
    }

4.2.2、应该在循环中关闭流,不应该在循环外


/**
* 1.2、应该在循环中关闭流,如下
*/
@Test
public void closeStream2() {
    for (int i = 0; i < 100; i++) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("/Users/healerjean/Desktop/test/file.txt");
            //TODO 操作代码
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    log.error("fileOutputStream未正确关闭");
                }
            }
        }
    }
}

4.2.3、java7解锁关闭流新姿势

引入了, OutputStream implements Closeable ,所以直接在try里面写上对于的流就可以关闭

/**
* 1.3、java7解决关闭流新姿势
* 只要实现的自动关闭接口(Closeable)的类都可以在try结构体上定义,java会自动帮我们关闭,及时在发生异常的情况下也会。可以在try结构体上定义多个,用分号隔开即可,如:
 */
@Test
public void java6CloseStream() {
    try (FileOutputStream fileOutputStream = new FileOutputStream("/Users/healerjean/Desktop/test/file.txt"); FileInputStream fileInputStream = new FileInputStream("/Users/healerjean/Desktop/test/file.txt");) {
        //TODO 操作代码
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

4.3、关闭处理流(包装流)

可以只关闭处理流(包装流),包装流的close方法是会自动关闭被包装的流

4.3.1、测试代码


@Test
public void baozhuang(){
    FileOutputStream fileOutputStream = null;
    BufferedOutputStream bufferedOutputStream = null;
    try {
        fileOutputStream = new FileOutputStream("/Users/healerjean/Desktop/test/file.txt");
        bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        bufferedOutputStream.write("test write something".getBytes());
        bufferedOutputStream.flush();

    }catch (Exception e){
        if(bufferedOutputStream !=null){
            //从包装流中关闭流
            try {
                bufferedOutputStream.close();
            } catch (IOException ex) {
                log.error("fileOutputStream未正确关闭",ex);
            }
        }
    }
    log.info("已经正确关闭了流");

}

4.3.2、测试代码拆解

观察new BufferedOutputStream(fileOutputStream);BufferedOutputStream 继承自FilterOutputStream (一定要看清不是FileOutputStream),它并没有重写close方法,而是直接使用的FilterOutputStream里面的close方法,

一定要看清在try里面使用了关闭流的新姿势


class FilterOutputStream extends OutputStream {
  @SuppressWarnings("try")
    public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
        }
    }
    

BufferedOutputStream的构造器直接将FileOutputStream作为属性传入了,通过上面的源码可以知道是在flush()结束之后关闭的是FileOutputStream 文件流



public class BufferedOutputStream extends FilterOutputStream { ...

 public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }
    

观察FileOutputStream的close方法,可以看出它采用同步锁,而且使用了关闭标记,如果已经关闭了则不会再次操作,所以多次调用不会出现问题。也就是重复调用close()方法不会出问题


class FileOutputStream extends OutputStream{

  public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }

        if (channel != null) {
            channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

4.3.3、结论

包装的流都会自动调用被包装的流的关闭方法,无需自己调用,也就是说包装流并不是什么流,归根结底,最底层的文件流才是正儿八经的流,通过上面的源码也可以看出,有关包装流重复调用包装流和文件流close()方法不会出问题。

4.4、依赖关系关闭是是有顺序的

4.4.1、测试代码

直接调用会抛异常


/**
     * 2、流的关闭顺序
     * 2.2、依赖关系
     */
@Test
public void baozhuang2() throws Exception {
    FileOutputStream fileOutputStream = null;
    BufferedOutputStream bufferedOutputStream = null;
    fileOutputStream = new FileOutputStream("/Users/healerjean/Desktop/test/file.txt");
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
    BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
    bufferedWriter.write("java IO close test");

    // 从内带外顺序顺序会报异常
    fileOutputStream.close();
    outputStreamWriter();
    bufferedWriter.close();//会抛异常
    log.info("已经正确关闭了流");

}




java.io.IOException: Stream Closed

    at java.io.FileOutputStream.writeBytes(Native Method)
    at java.io.FileOutputStream.write(FileOutputStream.java:326)
    at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
    at sun.nio.cs.StreamEncoder.implClose(StreamEncoder.java:316)
    at sun.nio.cs.StreamEncoder.close(StreamEncoder.java:149)
    at java.io.OutputStreamWriter.close(OutputStreamWriter.java:233)
    at java.io.BufferedWriter.close(BufferedWriter.java:266)
    at com.healerjean.proj.a_test.d03_流关闭.baozhuang2(d03_流关闭.java:128)

4.4.2、拆解分析

观察BufferedWriter的关闭源码,可以看到它是直接调用的out也就是文件流FileOutputStream流,而我们先关闭了FileOutputStream流,所以肯定会抛出异常,流已经关闭

public class BufferedWriter extends Writer 

    public void close() throws IOException {
    synchronized (lock) {
        if (out == null) {
            return;
        }
        try (Writer w = out) {
            flushBuffer();
        } finally {
            out = null;
            cb = null;
        }
    }
}

void flushBuffer() throws IOException {
    synchronized (lock) {
        ensureOpen();
        if (nextChar == 0)
            return;
        out.write(cb, 0, nextChar);
        nextChar = 0;
    }
}

}

正确写法



/**
* 2、流的关闭顺序
* 1、依赖关系
*/
@Test
public void baozhuang2() throws Exception {
    FileOutputStream fileOutputStream = null;
    BufferedOutputStream bufferedOutputStream = null;
    fileOutputStream = new FileOutputStream("/Users/healerjean/Desktop/test/file.txt");
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
    BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
    bufferedWriter.write("java IO close test");

    // // 从内带外顺序顺序会报异常
    // fileOutputStream.close();
    // outputStreamWriter.close();
    // bufferedWriter.close(); //会抛异常


    // 正确关闭姿势
    bufferedWriter.close();
    outputStreamWriter.close();
    fileOutputStream.close();
    log.info("已经正确关闭了流");

}




4.4.3、结论

依赖关系:如果流a依赖流b,应该先关闭流a,再关闭流b。例如,处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

4.5、Flush是干什么的

Stream也提供了close()方法关闭输出流,以便释放系统资源。要特别注意:OutputStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地。

4.5.1、为什么要有flush

因为向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组) ,等到缓冲区写满了,再一次性写入文件或者网络,·OutputStream有个flush()方法,能强制把缓冲区内容输出

4.5.2、一定要调用吗

通常情况下,我们不需要调用这个flush()方法,因为缓冲区写满了OutputStream会自动调用它,并且,在调用close()方法关闭OutputStream之前,也会自动调用flush()方法。

但是,在某些情况下,我们必须手动调用flush()方法

小明正在开发一款在线聊天软件,当用户输入一句话后,就通过OutputStreamwrite()方法写入网络流。小明测试的时候发现,发送方输入后,接收方根本收不到任何信息?

原因就在于写入网络流是先写入内存缓冲区,等缓冲区满了才会一次性发送到网络。如果缓冲区大小是4K,则发送方要敲几千个字符后,操作系统才会把缓冲区的内容发送出去,这个时候,接收方会一次性收到大量消息。

4.5.3、close 和 flush (结论)

flush()方法
		 1.用来刷新缓冲区,刷新后可以再次写出,这个典型的应用可以脑补下QQ即时聊天场景就好
close()方法
		 2.用来关闭流释放资源
	     3.如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭之后不能再写入


所以,不能用close来代替flush 

一般情况下,我们也可以使用close来进行刷新,通过看close方法的源码,发现close方法里面包含一个flush方法。flush就是刷新缓冲区的功能,所以,我们可以总结close方法的作用或者特点是:

1、具备刷新功能,在关闭流之前,就会先刷新一次缓存区,将缓冲区的字节全都刷新到文件上,再关闭流。

2、没有close方法,也就是没有进行刷新操作,文件有可能会变小。文件变小,说明了还有一部分内容没有完成写入到文件。前面一篇,我们介绍了缓冲区默认大小是8192字节,上面文件在最后一次写入到文件的缓冲区里,里面字节数没有8192大小,所以不会触发自动写入操作,从而留下一部分字节没有写入到文件。只要文件不是8192字节的N倍大小,如果最后不进行close操作,肯定会丢失一部分数据。

3、一般建议加上flush

4.5.4、fulsh的使用

InputStream input =  httpconn.getInputStream();  

ByteArrayOutputStream baos = new ByteArrayOutputStream();  
byte[] buffer = new byte[1024];  
int len;  
while ((len = input.read(buffer)) > -1 ) {  
    baos.write(buffer, 0, len);  
}  
baos.flush();                

InputStream stream1 = new ByteArrayInputStream(baos.toByteArray());  

//TODO:显示到前台  

InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());  

//TODO:本地缓存  

工具类

1、SFTP

package com.healerjean.proj.util.file;

import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

@Component
@Slf4j
public class SFTPUtil {

    private static final String encoding = "UTF-8";

    @Value("${sftp.host}")
    private String host;

    @Value("${sftp.port}")
    private String portStr;

    @Value("${sftp.username}")
    private String username;

    @Value("${sftp.password}")
    private String password;

    @Value("${sftp.remotePath}")
    private String remotePath;

    @Value("${sftp.rsa}")
    private String rsa;


    /**
     * 使用配置文件连接
     */
    private ChannelSftp connect() {
        ChannelSftp sftp = null;
        try {
            JSch jsch = new JSch();
            if (StringUtils.isNotBlank(rsa)) {
                jsch.addIdentity(rsa);
            }
            int port = Integer.valueOf(portStr);
            Session sshSession = jsch.getSession(username, host, port);
            log.info("Session created");
            if (StringUtils.isBlank(rsa)) {
                sshSession.setPassword(password);
            }
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            sshSession.setConfig(sshConfig);
            sshSession.connect();
            log.info("Session connected");
            log.info("Opening Channel");
            Channel channel = sshSession.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            log.info("Connected to {}", host);
        } catch (Exception e) {
            log.info("Connected to {} failed", host, e);
            throw new RuntimeException(e.getMessage());
        }
        return sftp;
    }

    /**
     * 手动连接SFTP
     */
    private ChannelSftp handConnect(String rsa, String host, String portStr, String username, String password) {
        ChannelSftp sftp = null;
        try {
            JSch jsch = new JSch();
            if (StringUtils.isNotBlank(rsa)) {
                jsch.addIdentity(rsa);
            }
            int port = Integer.valueOf(portStr);
            Session sshSession = jsch.getSession(username, host, port);
            log.info("Session created");
            if (StringUtils.isBlank(rsa)) {
                sshSession.setPassword(password);
            }
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            sshSession.setConfig(sshConfig);
            sshSession.connect();
            log.info("Session connected");
            log.info("Opening Channel");
            Channel channel = sshSession.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            log.info("Connected to {}", host);
        } catch (Exception e) {
            log.info("Connected to {} failed", host, e);
            throw new RuntimeException(e.getMessage());
        }
        return sftp;
    }

    /**
     * 断开连接
     */
    private void disconnect(ChannelSftp sftp) {
        if (sftp != null) {
            if (sftp.isConnected()) {
                sftp.disconnect();
                log.info("sftp is disconnecting");
            } else if (sftp.isClosed()) {
                log.info("sftp is already closed");
            }
            try {
                if (sftp.getSession().isConnected()) {
                    sftp.getSession().disconnect();
                }
            } catch (Exception e) {
                log.error("sftp  session is disconnect", e);
                throw new RuntimeException(e.getMessage());
            }
        }

    }


    /**
     * 去读Sfto文件
     *
     * @param directory      目录
     * @param remoteFileName 远程文件名
     * @return
     */
    public String readSFTPFile(String directory, String remoteFileName) {
        ChannelSftp sftp = connect();
        return readSFTPFile(remotePath + directory, remoteFileName, sftp);
    }

    /**
     * @param remoteDirectory 远程目录 比如 /asset/test
     * @param remoteFileName  远程文件名
     * @param sftp
     * @return
     */
    private String readSFTPFile(String remoteDirectory, String remoteFileName, ChannelSftp sftp) {
        if (sftp == null) {
            log.error("connect to sftp failed");
            return "false";
        }

        StringBuilder builder = new StringBuilder();
        BufferedReader reader = null;
        InputStream ins = null;

        try {
            sftp.cd(remoteDirectory);
            ins = sftp.get(remoteFileName);
            if (ins == null) {
                log.error("filename is not exit");
                return "";
            }
            reader = new BufferedReader(new InputStreamReader(ins, encoding));
            String inLine = reader.readLine();
            while (inLine != null) {
                builder.append(inLine);
                builder.append("\n");
                inLine = reader.readLine();
            }
        } catch (SftpException se) {
            log.info("read sftp {} error ", remoteFileName, se);
            throw new RuntimeException(se.getMessage());
        } catch (Exception e) {
            log.info("read sftp {} error ", remoteFileName, e);
            throw new RuntimeException(e.getMessage());
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
                if (ins != null) {
                    ins.close();
                }
            } catch (IOException e) {
                log.error("read ftpFile close  InputStream or BufferedReader error ", e);
                throw new RuntimeException(e.getMessage());
            }
            disconnect(sftp);
        }
        return builder.toString();
    }


    /**
     * 下载单个文件到本地文件
     *
     * @param directory         远程目录
     * @param remoteFileName    远程文件名
     * @param downLoadLocalPath 本地下载后存储路径
     * @param localFileName     本地下载后的文件名
     * @return
     */
    public boolean downloadFile(String directory, String remoteFileName, String downLoadLocalPath, String localFileName) {
        ChannelSftp sftp = this.connect();
        return downloadFile(remotePath + directory, remoteFileName, downLoadLocalPath, localFileName, sftp);
    }

    /**
     * 下载单个文件到本地文件
     *
     * @param remoteDirectory   远程目录 比如 /asset/test
     * @param remoteFileName    远程文件名
     * @param downLoadLocalPath 本地下载后存储路径
     * @param localFileName     本地下载后的文件名
     * @return
     */
    private boolean downloadFile(String remoteDirectory, String remoteFileName, String downLoadLocalPath, String localFileName, ChannelSftp sftp) {
        FileOutputStream out = null;
        try {
            File file = new File(downLoadLocalPath, localFileName);
            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }
            out = new FileOutputStream(file);
            sftp.get(remoteDirectory + "/" + remoteFileName, out);
            log.info("down load single file success , {}", remoteFileName);
            return true;
        } catch (Exception e) {
            log.error("get file error", e);
            throw new RuntimeException(e.getMessage());
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    log.error("download single file close error", e);
                    throw new RuntimeException(e.getMessage());
                }
            }
            this.disconnect(sftp);
        }
    }


    /**
     * 批量下载文件
     *
     * @param remoteDirectory   远程下载的目录
     * @param downLoadLocalPath 下载的本地路径
     * @param fileStartFormat   文件名开头格式
     * @param del
     * @return
     */
    public boolean batchDownLoadFile(String remoteDirectory, String fileStartFormat, String downLoadLocalPath, boolean del, ChannelSftp sftp) {
        try {
            File directory = new File(downLoadLocalPath);
            if (!directory.exists()) {
                directory.mkdirs();
            }
            Vector v = listFiles(remoteDirectory, sftp);
            if (v.size() > 0) {
                log.info("batch down files start");
                Iterator iterator = v.iterator();
                while (iterator.hasNext()) {
                    ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) iterator.next();
                    final String filename = lsEntry.getFilename();
                    SftpATTRS attrs = lsEntry.getAttrs();
                    //不是目录
                    if (!attrs.isDir()) {
                        //文件格式
                        if (StringUtils.isNotEmpty(fileStartFormat)) {
                            if (filename.startsWith(fileStartFormat)) {
                                if (downloadFile(remoteDirectory, filename, downLoadLocalPath, filename, sftp) && del) {
                                    deleteSFTPFile(remoteDirectory, filename, sftp);
                                }
                            }
                        } else {
                            if (downloadFile(remoteDirectory, filename, downLoadLocalPath, filename, sftp) && del) {
                                deleteSFTPFile(remoteDirectory, filename, sftp);
                            }
                        }
                    }
                }//while end
            }
        } catch (Exception e) {
            log.error("batch down files error", e);
            return false;
        } finally {
            this.disconnect(sftp);
        }
        return true;
    }


    /**
     * 批量下载文件
     *
     * @param directory         目录
     * @param downLoadLocalPath 下载的本地路径
     * @param fileStartFormat   文件名开头格式
     * @param del
     * @return
     */
    public boolean batchDownLoadFile(String directory, String fileStartFormat, String downLoadLocalPath, boolean del) {
        ChannelSftp sftp = this.connect();
        return batchDownLoadFile(remotePath, fileStartFormat, downLoadLocalPath, del, sftp);
    }


    /**
     * @param remoteDirectory 远程上传目录
     * @param uploadFileName  上传的文件名
     * @param inputStream     上传的流
     * @param sftp
     * @return
     */
    public boolean uploadFile(String remoteDirectory, String uploadFileName, InputStream inputStream, ChannelSftp sftp) {
        boolean flag = false;
        for (int i = 1; i < 3; i++) {
            log.info("开始第" + i + "次上传文件,名称 {}", uploadFileName);
            try {
                createDirAndCd(remoteDirectory, sftp);
                sftp.put(inputStream, uploadFileName);
                flag = true;
                log.info("第" + i + "次上传文件成功,名称 {}", uploadFileName);
                break;
            } catch (Exception e) {
                log.error("occurs exception", e);
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        log.error("occurs exception", e);
                    }
                }
                this.disconnect(sftp);
            }
            if (i == 3 && !flag) {
                log.info("上传3次均失败,文件名称 {}", uploadFileName);
            }
        }
        return flag;
    }


    /**
     * @param directory      目录
     * @param remoteFileName 上传的文件名
     * @param inputStream
     * @return
     */
    public boolean uploadFile(String directory, String remoteFileName, InputStream inputStream) {
        boolean flag = false;
        for (int i = 1; i < 4; i++) {
            log.info("开始第" + i + "次上传文件,名称 {}", remoteFileName);
            ChannelSftp sftp = this.connect();
            FileInputStream in = null;
            try {
                createDirAndCd(remotePath + "/" + directory, sftp);
                sftp.put(inputStream, remoteFileName);
                flag = true;
                log.info("第" + i + "次上传文件成功,名称 {}", remoteFileName);
                break;
            } catch (Exception e) {
                log.error("occurs exception", e);
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        log.error("occurs exception", e);
                    }
                }
                this.disconnect(sftp);
            }
            if (i == 3 && !flag) {
                log.info("上传3次均失败,商户 {},文件名称 {}", remoteFileName);
                // AlarmMailLog.logTask("上传3次均失败,商户 " + merchantId + ",文件 "+localFileName);
            }
        }//for 循环结束
        return flag;
    }


    /**
     * 创建目录 并切换到创建的目录下面
     */
    private boolean createDirAndCd(String createpath, ChannelSftp sftp) {
        try {
            if (isDirExist(createpath, sftp)) {
                sftp.cd(createpath);
                return true;
            }
            String pathArry[] = createpath.split("/");
            StringBuffer filePath = new StringBuffer("");
            for (String path : pathArry) {
                if (path.equals("")) {
                    continue;
                }
                filePath.append(path + "/");
                if (isDirExist(filePath.toString(), sftp)) {
                    sftp.cd(filePath.toString());
                    filePath.delete(0, filePath.length());
                } else {
                    // 建立目录
                    sftp.mkdir(filePath.toString());
                    // 进入并设置为当前目录
                    sftp.cd(filePath.toString());
                    filePath.delete(0, filePath.length());
                }
            }
            return true;
        } catch (SftpException e) {
            log.error("occurs exception", e);
        }
        return false;
    }


    private Vector listFiles(String remoteDirectory, ChannelSftp sftp) throws SftpException {
        return sftp.ls(remoteDirectory);
    }

    public Vector listFiles(String directory) {
        ChannelSftp sftp = this.connect();
        try {
            return listFiles(remotePath + directory, sftp);
        } catch (Exception e) {
            log.error("列举远程目录下文件出错", e);
            return null;
        } finally {
            this.disconnect(sftp);
        }
    }


    private void deleteSFTPFile(String directory, String deleteFile, ChannelSftp sftp) {
        try {
            sftp.cd(directory);
            sftp.rm(deleteFile);
            log.info("delete remote file success {}", deleteFile);
        } catch (Exception e) {
            log.error("delete remote file fail", e);
        }
    }

    /**
     * 判断目录是否存在
     */
    private boolean isDirExist(String directory, ChannelSftp sftp) {
        boolean isDirExistFlag = false;
        try {
            SftpATTRS sftpATTRS = sftp.lstat(directory);
            isDirExistFlag = true;
            return sftpATTRS.isDir();
        } catch (Exception e) {
            if (e.getMessage().toLowerCase().equals("no such file")) {
                isDirExistFlag = false;
            }
        }
        return isDirExistFlag;
    }


    /**
     * 测试
     * 1、连接Sftp
     * 2、读取sftp文件内容
     * 3、下载sftp文件
     * 4、上传文件
     * 5、批量下载文件
     */
    public static void main(String[] args) {
        SFTPUtil sftpUtil = new SFTPUtil();
        //1、连接Sftp
        ChannelSftp sftp = sftpUtil.handConnect("D:/work/sftp/id_rsa", "127.0.0.1", "22", "asset", null);

        //2、读取sftp文件内容
        //远程目录
        // String remoteDirectory = "/asset/test";
        //文件名
        // String remoteFileName = "check_info_result.txt";
        // String content = sftpUtil.readSFTPFile(remoteDirectory,remoteFileName,sftp);
        // System.out.println(content);

        // 3、下载sftp文件
        // String downLoadLocalPath = "D:/work/sftp/temp";
        // String downLoadFileName = "NEW.TXT" ;
        // sftpUtil.downloadFile(remoteDirectory,remoteFileName,downLoadLocalPath,downLoadFileName,sftp);


        // 4、上传文件
        // String uploadFileName = "upload_NEW.TXT";
        // try {
        //     FileInputStream inputStream = new FileInputStream("D:/work/sftp/temp/NEW.TXT");
        //     sftpUtil.uploadFile(remoteDirectory,uploadFileName,inputStream,sftp);
        // } catch (FileNotFoundException e) {
        //     e.printStackTrace();
        // }


        //5、批量下载文件
        // String downLoadLocalPath = "D:/work/sftp/temp";
        // sftpUtil.batchDownLoadFile(remoteDirectory,"u",downLoadLocalPath,false,sftp);

        //断开连接
        sftpUtil.disconnect(sftp);


    }
}

2、FTP

package com.healerjean.proj.util.file;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.*;

@Slf4j
public class FtpUtil {

    private FTPClient ftpClient;

    public static void main(String[] args) throws Exception {
        FtpUtil t = new FtpUtil();
        t.connect("E:\resourse", "10.3.250.74", 21, "HealerJean", "147094");
        File file = new File("D:/test");
        t.upload(file);
    }


    /**
     * @param path     上传到ftp服务器哪个路径下
     * @param addr     地址
     * @param port     端口号
     * @param username 用户名
     * @param password 密码
     * @return
     * @throws Exception
     */
    private boolean connect(String path, String addr, int port, String username, String password) throws Exception {
        boolean result = false;
        ftpClient = new FTPClient();
        int reply;
        //连接ftp  默认是21 不写port也可以
        ftpClient.connect(addr, port);
        ftpClient.connect(addr);
        //ftp登录
        ftpClient.login(username, password);
        //文件类型为二进制文件
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        reply = ftpClient.getReplyCode();
        //保存到ftp路径下
        if (!FTPReply.isPositiveCompletion(reply)) {
            ftpClient.disconnect();
            return result;
        }
        ftpClient.changeWorkingDirectory(path);
        result = true;

        return result;
    }


    /**
     * 关闭FTP连接
     */
    private void closeConnect() {
        if (ftpClient != null && ftpClient.isConnected()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException e) {
                log.error("关闭FTP连接失败", e);
            }
        }
    }


    /**
     * 上传
     */
    private void upload(File file) throws Exception {
        if (file.isDirectory()) {
            ftpClient.makeDirectory(file.getName());
            ftpClient.changeWorkingDirectory(file.getName());
            String[] files = file.list();
            for (int i = 0; i < files.length; i++) {
                File file1 = new File(file.getPath() + "\\" + files[i]);
                if (file1.isDirectory()) {
                    upload(file1);
                    //上传目录
                    ftpClient.changeToParentDirectory();
                } else {
                    File file2 = new File(file.getPath() + "\\" + files[i]);
                    FileInputStream input = new FileInputStream(file2);
                    //上传文件
                    ftpClient.storeFile(file2.getName(), input);
                    input.close();
                }
            }
        } else {
            File file2 = new File(file.getPath());
            FileInputStream input = new FileInputStream(file2);
            //上传文件
            ftpClient.storeFile(file2.getName(), input);
            input.close();
        }
    }


    /**
     * 下载该目录下所有文件到本地
     * @param ftpPath  FTP服务器上的相对路径,例如:test/123
     * @param savePath 保存文件到本地的路径,例如:D:/test
     */
    public void downloadFiles(String ftpPath, String savePath) {
        // 登录
        if (ftpClient != null) {
            try {
                // 判断是否存在该目录
                if (!ftpClient.changeWorkingDirectory(ftpPath)) {
                    throw new RuntimeException("ftp目录不存在");
                }
                // 设置被动模式,开通一个端口来传输数据
                ftpClient.enterLocalPassiveMode();
                String[] listNames = ftpClient.listNames();
                // 判断该目录下是否有文件
                if (listNames == null || listNames.length == 0) {
                    throw new RuntimeException("ftp目录下文件不存在");
                }
                for (String fileNmae : listNames) {
                    File file = new File(savePath + '/' + fileNmae);
                    try (OutputStream os = new FileOutputStream(file)) {
                        ftpClient.retrieveFile(fileNmae, os);
                    } catch (Exception e) {
                        throw new RuntimeException("文件下载出错", e);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException("文件下载出错", e);
            }
        }
    }


}

3、文件ContentType枚举

package com.hlj.util.Z017_文件ContentType;

/**
 * @author HealerJean
 * @ClassName FileEnum
 * @date 2019/11/7  19:58.
 * @Description
 */
public interface FileEnum {

    /**
     * 文件类型枚举
     */
    enum FileContentTypeEnum {

        doc(".doc","application/msword"),
        docx(".docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
        xls(".xls","application/vnd.ms-excel"),
        xlsx(".xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
        csv(".csv","text/csv"),
        ppt(".ppt","application/vnd.ms-powerpoint"),


        ttf(".ttf","font/ttf"),
        js(".js","text/javascript"),
        css(".css","text/css"),
        xml(".xml","text/xml"),
        html(".html","text/html"),
        htm(".htm","text/html"),
        json(".json","application/json"),
        xhtml(".xhtml","application/xhtml+xml"),

        jpeg(".jpeg","image/jpeg"),
        jpg(".jpg","image/jpeg"),
        png(".png","image/png"),
        gif(".gif","image/gif"),
        ico(".ico","image/vnd.microsoft.icon"),
        tif(".tif","image/tiff"),
        tiff(".tiff","image/tiff"),

        jar(".jar","application/java-archive"),
        zip(".zip","application/zip"),
        tar(".tar","application/x-tar"),
        sh(".sh","application/x-sh"),

        mp3(".mp3","audio/mpeg"),


        ;
        private String code;
        private String mime;

        FileContentTypeEnum(String code, String mime) {
            this.code = code;
            this.mime = mime;
        }

        public String getCode() {
            return code;
        }
        public void setCode(String code) {
            this.code = code;
        }
        public String getMime() {
            return mime;
        }
        public void setMime(String mime) {
            this.mime = mime;
        }

        public static FileContentTypeEnum toEnum(String code) {
            for (FileContentTypeEnum item : FileContentTypeEnum.values()) {
                if (item.getCode().equals(code)) {
                    return item;
                }
            }
            return null;
        }
    }

}

4、目录压缩

package com.fintech.scf.utils.zip;


import lombok.extern.slf4j.Slf4j;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Zip;
import org.apache.tools.ant.types.FileSet;

import java.io.File;

/**
 * @author HealerJean
 * @ClassName ZipUtils
 * @Date 2019/11/14  20:48.
 * @Description 压缩工具类
 *   <!--zip压缩-->
 *         <dependency>
 *             <groupId>org.apache.ant</groupId>
 *             <artifactId>ant-apache-xalan2</artifactId>
 *             <version>1.10.1</version>
 *         </dependency>
 */
@Slf4j
public class ZipUtils {

    /**
     * 压缩目录
     */
    public static void compress(String directoryPath) {
        compress(directoryPath, directoryPath+".zip");
    }

    /**
     * 压缩目录
     * @param directoryPath 源目录
     * @param zipFilePath  目标压缩文件
     */
    public static void compress(String directoryPath, String zipFilePath) {
        File directory = new File(directoryPath);
        if (!directory.exists()) {
            log.info("需要被压缩的路径:{}不存在", directoryPath);
            throw new RuntimeException(directoryPath + "不存在!");
        }

        Project prj = new Project();
        Zip zip = new Zip();
        zip.setProject(prj);
        File zipFile = new File(zipFilePath);
        zip.setDestFile(zipFile);
        FileSet fileSet = new FileSet();
        fileSet.setProject(prj);
        fileSet.setDir(directory);
        //fileSet.setIncludes("**/*.java"); //包括哪些文件或文件夹 eg:zip.setIncludes("*.java");
        //fileSet.setExcludes(...); //排除哪些文件或文件夹
        zip.addFileset(fileSet);
        zip.execute();
    }

}

问题

1、不使用递归遍历整个目录

问题:给定一个根目录,要求遍历其中的文件及子文件夹,返回所有后缀是.txt的文件List,不能使用递归

 * 作者 HealerJean
 * 日期 2019/3/13  下午2:34.
 * 类描述给定一个根目录要求遍历其中的文件及子文件夹返回所有后缀是.txt的文件List
 * * 要求
 * * 1. 不能使用递归
 */
public class Solution {

    @Test
    public void test() {
        File root = new File("/Users/healerjean/Desktop/faceFile");
        System.out.println(findTxt(root).toString());
    }

    /**
     * @param root 为一个文件夹根目录
     */
    public List<File> findTxt(File root) {
        List<File> listTxtFiles = new ArrayList<>();
        if (root.exists()) {
            //创建一个目录集合,用于存放跟目录下面的文件夹
            LinkedList<File> directorys = new LinkedList<>();
            //获取根目录下面的子目录文件夹(如果本目录下面包含txt文件,则直接加入listTxtFiles集合中)
            File[] files = root.listFiles();
            for (File file : files) {
                //判断是否为目录,然后
                if (file.isDirectory()) {
                    directorys.add(file);
                } else if (file.getName().endsWith(".txt")) {
                    listTxtFiles.add(new File(file.getAbsolutePath()));
                }
            }

            //创建一个临时目录,用于遍历directorys 集合中一个文件夹,如果是我们需要的txt文件,则直接加入集合中,如果该目录下包含子目录,则继续添加到directorys 集合中
            File temp_file;
            while (!directorys.isEmpty()) {
                //每次遍历directorys 中的第一个目录,遍历之前将它作废
                temp_file = directorys.removeFirst();
                files = temp_file.listFiles();
                for (File file : files) {
                    if (file.isDirectory()) {
                        directorys.add(file);
                    } else if (file.getName().endsWith(".txt")) {
                        listTxtFiles.add(new File(file.getAbsolutePath()));
                    }
                }
            }
        }
        return listTxtFiles;
    }

}

2、文件路径问题

参考博客:关于获取资源文件,Class.getResource和ClassLoader.getResource的区别

2.1、Jar路径

String ttcp = this.class.getClassLoader().getResource(ttc).getPath();
    if (StringUtils.indexOf(ttcp, "jar!/") > 0) {
    ttcp = "jar:" + ttcp;
}

2.2、Class.getResource和ClassLoader.getResource

classpath: WEB-INF/classes下的文件

getResource("") 获取的是当前类所在包的路径,而 getResource("/") 获取的是 classpath 根路径;

hlj-file
┌─src
  └─main
     └─java
        └─com.healerjean.proj
           ├─test
             └─GetResourceTest.java
           └─SpringbootApplication.java
└─resource
   ├─mapper
      └─BlogDao.xml
   └─confog.properties

2.2.1、Class.getResource

1、当以 "/" 开头时,是从 classpath 路径开始匹配资源;

2、当不以 "/" 开头时,是从当前类所在包的路径开始匹配资源

3、两种方式都可以通过 "/""../" 在文件夹上下层路径切换;

@Test
public void classGetResource() {

    System.out.println(this.getClass().getResource(""));
    // file:/E:/workspace/hlj-file/target/classes/com/healerjean/proj/test/

    
    System.out.println(this.getClass().getResource("/"));
    // file:/E:/workspace/hlj-file/target/classes/

     // null
    System.out.println(this.getClass().getResource("BlogDao.xml"));
    
    System.out.println(this.getClass().getResource("/config.properties"));
     // file:/E:/workspace/hlj-file/target/classes/config.properties
    
    System.out.println(this.getClass().getResource("/mapper/BlogDao.xml"));
   // file:/E:/workspace/hlj-file/target/classes/mapper/BlogDao.xml
}

2.2.3、ClassLoader.getResource()

ClassLoader().getResource 获取路径时,不能以 "/" 开头,且路径总是从 classpath 根路径开始;

@Test
public void classLoaderGetResource() {
    // file:/E:/workspace/hlj-file/target/classes/
    System.out.println(this.getClass().getClassLoader().getResource(""));
    // null
    System.out.println(this.getClass().getClassLoader().getResource("/"));
}

2.3、webroot目录

String srcPath = this.getServletContext().getRealPath("/WEB-INF/classes/NamePath.properties");

2.4、ClassPath其他方法

ClassPathResource resource = new ClassPathResource("cert/wechat/apiclient_cert.p12");
// 获取文件
File file = resource.getFile();
InputStream certinputStream = resource.getInputStream();
ResourceUtils.getFile("classpath:template");
Resource[] resources = ArrayUtils.addAll(
applicationContext.getResources("classpath*:com/admore/dao/mybatis/**/mysql/*.xml"),
applicationContext.getResources("classpath*:com/admore

3、InputStream对象的重复使用 以及fush正确写法


InputStream input =  httpconn.getInputStream();  
                  
ByteArrayOutputStream baos = new ByteArrayOutputStream();  
byte[] buffer = new byte[1024];  
int len;  
while ((len = input.read(buffer)) > -1 ) {  
    baos.write(buffer, 0, len);  
}  
baos.flush();   


  
InputStream stream1 = new ByteArrayInputStream(baos.toByteArray());  
  
//TODO:显示到前台  
  
InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());  
  
//TODO:本地缓存  

ContactAuthor