从本地文件复制到网络文件上传

文件复制和文件上传

最近在看文件和IO流相关的东西,写了一些代码,发现这个有很多很有趣的地方。特别是对 File 和 IO 流的使用之后,我对这部分知识有了更深入的理解。今天就尝试了从本地文件复制到网络文件上传,发现这部分其实是很相似的,都是将文件从一个地方转移到另一个地方,这也是流的特点之一。 相信,看我博客之后,你也会有相同的理解。

文件复制

文件复制: 将一个本地文件从一个目录,复制到另一个目录。(通过本地文件系统)

主要代码

package dragon;

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

/**
 * 本地文件复制:
 * 将文件从一个地方复制到另一个地方。
 * 
 * @author Alfred
 * */
public class FileCopy {
	
	public FileCopy() {}
	
	public void fileCopy(String target, String output) throws IOException {
		File targetFile = new File(target);
		File outputPath = new File(output);
		
		this.init(targetFile, outputPath);
		
		/**注意这里使用了 try with resource 语句,所以不需要显示的关闭流了。
		 * 而且,再关闭流操作中,会自动调用 flush 方法,如果不放心,
		 * 可以在每个write 方法后面,强制刷新一下。
		 * */
		try (
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(targetFile));                //创建输出文件
			BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputPath, "copy"+targetFile.getName())))){
			int hasRead = 0;
			byte[] b = new byte[1024];
			while ((hasRead = bis.read(b)) != -1) {
				bos.write(b, 0, hasRead);
			}
		}
		System.out.println("文件复制成功");
	}
	
	//数据校验及初始化工作
	private void init(File targetFile, File outputPath) throws FileNotFoundException {
		if (!targetFile.exists()) {
			throw new FileNotFoundException("目标文件不存在:"+targetFile.getAbsolutePath());
		} else {
			if (!targetFile.isFile()) {
				throw new FileNotFoundException("目标文件是一个目录:"+targetFile.getAbsolutePath());
			}
		}	
		
		if (!outputPath.exists()) {
			if (!outputPath.mkdirs()) {   
				throw new FileNotFoundException("无法创建输出路径:"+outputPath.getAbsolutePath());
			}
		} else {
			if (!outputPath.isDirectory()) {
				throw new FileNotFoundException("输出路径不是一个目录:"+outputPath.getAbsolutePath());
			}
		}
	}
}

测试类

package dragon;

import java.io.IOException;

public class FileCopyTest {
	public static void main(String[] args) throws IOException {
		String target = "D:/DB/BuilderPattern.png";
		String output = "D:/DBC/dragon/";
		FileCopy copy = new FileCopy();
		copy.fileCopy(target, output);
	}
}

执行结果

注意:右边文件是复制的结果,左边的不是。(下面会提到!)
在这里插入图片描述

说明

上面的代码只是将一个本地文件从一个目录,复制到另一个目录,还是比较简单的,这只是一个原理性的代码,来说明输入输出流的应用。将文件从一个地方复制到另一个地方。



网络文件传输(TCP)

**网络文件传输(TCP):**使用套接字(TCP)进行演示,文件从一个地方复制到另一个地方。(通过网络的方式。)

主要代码

Server

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws IOException {
		try (
			ServerSocket server = new ServerSocket(8080)){
			Socket client = server.accept();			
			//开始读取文件
			try (
				BufferedInputStream bis = new BufferedInputStream(client.getInputStream());
				BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("D:/DBC/dragon", System.currentTimeMillis()+".jpg")))){
				int hasRead = 0;
				byte[] b = new byte[1024];
				while ((hasRead = bis.read(b)) != -1) {
					bos.write(b, 0, hasRead);
				}
			}
			System.out.println("文件上传成功。");
		}
	}
}

Client

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {
		try (Socket client = new Socket("127.0.0.1", 8080)){
			File file = new File("D:/DB/netFile/001.jpg");	
			//开始写入文件
			try (
				BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
				BufferedOutputStream bos = new BufferedOutputStream(client.getOutputStream())){
				int hasRead = 0;
				byte[] b = new byte[1024];
				while ((hasRead = bis.read(b)) != -1) {
					bos.write(b, 0, hasRead);
				}
			}
		}
	}
}

执行效果

执行程序
在这里插入图片描述

注意:这个上传文件的目录和本地文件复制是在同一个目录,但是使用的方式不一样,文件的命名方式不一样,使用的是当前的毫秒数。
复制前文件
在这里插入图片描述

复制后文件
在这里插入图片描述

说明

通过网络的方式使用流,使用传输层的TCP协议,绑定了 8080 端口,这里需要一些网络的知识,不过都是最基本的知识。可以看出来,上面这个 Server端和 Client端的代码很简单,甚至都没有实现传输文件的后缀名!(哈哈,其实是我对套接字编程不太熟悉,传输文件名的话,我一开始尝试,但是没有成功。不过这个不影响这个例子,套接字我会抽时间来看的。哈!)
注意这里我要表达的意思通过网络将文件从一个地方复制到另一个地方。(使用较为的是传输层的协议)

扫描二维码关注公众号,回复: 8960106 查看本文章

网络文件传输(HTTP)

HTTP 是建立在 TCP/IP 协议之上的应用层协议,传输层协议使用起来感觉还是比较麻烦的,不如应用层协议用起来方便。

网络文件传输(HTTP):
这里使用 Servlet(3.0以上)(JSP)技术来举例,就以我们最常使用的文件上传为例。使用 HTTP 协议将文件从一个地方复制到另一个地方。

使用 apache 组件实现文件上传

注意:因为原始的通过 Servlet 上传文件较为麻烦,现在都是使用一些组件来达成这个文件上传的功能的。(我没有找到文件上传最原始的写法,想必应该是很繁琐的吧!)
这里使用两个jar包:
commons-fileupload-1.4.jar
commons-io-2.6.jar

注意:在 apache 网站可以下载到。
上传文件的 Servlet

package com.study;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

/**
 * Servlet implementation class UploadServlet
 */
@WebServlet("/UploadServlet")
public class UploadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//如果不是文件上传的话,直接不处理,这样比较省事
		if (ServletFileUpload.isMultipartContent(request)) {
			//获取(或者创建)上传文件的路径
			String path = request.getServletContext().getRealPath("/image");
			File uploadPath = new File(path);
			if (!uploadPath.exists()) {
				uploadPath.mkdir();
			}
			
			FileItemFactory factory = new DiskFileItemFactory();
			ServletFileUpload upload = new ServletFileUpload(factory);
			List<FileItem> items;
			try {
				items = upload.parseRequest(request);
				Iterator<FileItem> it = items.iterator();
				while (it.hasNext()) {
					FileItem item = it.next();
					//处理上传文件
					if (!item.isFormField()) {
						String filename = new File(item.getName()).getName();
						System.out.println(filename);
						File file = new File(uploadPath, filename);
						item.write(file);
						response.sendRedirect("success.jsp");
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

上传文件的jsp中,只需要一个form表单即可。

<h1>文件上传</h1>
<form action="NewUpload" method="post"  enctype="multipart/form-data">
    <input type="file" name="image">
    <input type="submit" value="上传">
</form>

运行效果

说明

虽然这样处理对于上传文件很好,但是因为使用的都是较为成熟的技术,对于想了解输入输出流的我们来说,就不是那么好了。从这个例子中,基本上看不到输入输出流的用法了,都被封装起来了。

使用 Servlet 3.0 以后的新技术实现文件上传

package com.study;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

/**
 * Servlet implementation class FileUpload
 */
@MultipartConfig
@WebServlet("/FileUpload")
public class FileUpload extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Part part = request.getPart("image");
		String header = part.getHeader("Content-Disposition");
		System.out.println(header);
		String filename = header.substring(header.lastIndexOf("filename=\"")+10, header.lastIndexOf("\""));
		
		String fileSuffix = filename.lastIndexOf(".") != -1 ? filename.substring(filename.lastIndexOf(".")) : "";
		String uploadPath = request.getServletContext().getRealPath("/image");
		File path = new File(uploadPath);
		if (!path.exists()) {
			path.mkdir();
		}
		
		filename = UUID.randomUUID()+fileSuffix;
		try (
			BufferedInputStream bis = new BufferedInputStream(part.getInputStream());
			BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(path, filename)))){
			int hasRead = 0;
			byte[] b = new byte[1024];
			while ((hasRead = bis.read(b)) != -1) {
				bos.write(b, 0, hasRead);
			}
		}
		response.sendRedirect("success.jsp");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

使用 Servlet 3.0 的新特性实现,这里使用了 @MultipartConfig注解。(如果不使用这个注解,会无法正常工作!感兴趣的,可以多去了解一下。)

注意:下面这段代码,这里我舍近求远了,但是这正是我想要看到的。同样是输入输出流,注意这个和上面的几个例子进行对比。

try (
	BufferedInputStream bis = new BufferedInputStream(part.getInputStream());
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(path, filename)))){
		int hasRead = 0;
		byte[] b = new byte[1024];
		while ((hasRead = bis.read(b)) != -1) {
			bos.write(b, 0, hasRead);
		}
	}


不使用 apache 组件的更为简单的方式是下面这种:

package com.study;

import java.io.File;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

/**
 * Servlet implementation class NewUpload
 */
@MultipartConfig
@WebServlet("/NewUpload")
public class NewUpload extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Part part = request.getPart("image");
		String header = part.getHeader("Content-Disposition");
		System.out.println(header);
		String filename = header.substring(header.lastIndexOf("filename=\"")+10, header.lastIndexOf("\""));
		String fileSuffix = filename.lastIndexOf(".") != -1 ? filename.substring(filename.lastIndexOf(".")) : "";
		String uploadPath = request.getServletContext().getRealPath("/image");
		File path = new File(uploadPath);
		if (!path.exists()) {
			path.mkdir();
		}
		filename = uploadPath+File.separator+System.currentTimeMillis()+UUID.randomUUID().toString()+fileSuffix;
		part.write(filename);
		response.sendRedirect("success.jsp");
	}

}

真正写入文件的只有这一步了,前面全是处理文件名和上传文件路径相关的代码。使用 HTTP 的这三种方式都有处理文件名和上传文件路径这段代码。
如果通过这段代码,那就更看不出什么东西来了,只知道这样做,一个文件就会被写入相应的路径。

part.write(filename);

总结

这里从本地文件复制 -> 传输层文件复制 -> 应用层文件复制,一起回顾了流的使用以及它们之间的共通点。这里说复制似乎不是很好,但是一时也想不出来什么好的说法,就凑合着看吧。这里的复制指的是:通过流将一个文件从一个地方传到另一个地方。无论是本地文件系统还是通过 TCP协议或者 HTTP协议实现。 这几种方式虽然用法都是不同的,但是仔细观察还是能看出来相通之处,无论使用那种方式都离不开输入、输出流的使用。使用 HTTP 的方式,大多使用较为成熟的技术,一般不会使用这么原始的代码来进行文件上传(复制),但是第二种方式(舍近求远)中也是使用了流进行文件上传演示,所以也是可以看出来的,虽然使用HTTP的三种方式不一样,但是底层实现,也是无法脱离 IO流的。
这里也可以看出来,Java EE 技术也是需要很好的 Java SE作为基础的,如果你能熟练掌握 Java SE的文件复制(单个文件复制、整个目录复制)、文件合并等操作,学习 Java EE的文件上传下载也是很轻松的。(但是,必要的网络基础知识还是需要掌握的,这样有助于理解这个网络的体系。)

结语: 熟练的掌握IO流的使用(我也没有做到,哈哈!),可以帮助我们做很多事情,比如使用 Python 编写爬虫很简单,但是如果掌握爬虫的基本原理,我们也可以通过Java来实现。(这就对你对JavaIO流要有一定的掌握,否则会感觉到很难做到。)今天这个博客,主要是想说明,上面几种方式之间的共通性,不知到你有没有理解到。(具体的细节我没有怎么说明,不过这都是一些基础知识,应该是不难的。)

发布了21 篇原创文章 · 获赞 32 · 访问量 1614

猜你喜欢

转载自blog.csdn.net/qq_40734247/article/details/103828554
今日推荐