本文主要是介绍多文件ajaxfileupload上传,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
大体步骤:
1、选择文件(可多次选择)
2、每次选择文件之后,将选中的文件ajaxfileupload上传到服务器临时文件夹,返回文件名称,预览展示
3、全局变量保存每次上传的文件名,删除上传文件时需删除预览、全局变量中的文件名和临时文件夹中的文件
4、将需要上传文件名(全局变量)传递到后台,如果有其他参数也可同时传递
5、根据文件名和临时文件夹地址将需要上传的文件从临时文件夹复制打需要上传目录下,之后删除临时文件夹中的文件
实例:
文件处理工具类:
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;/** * * <p> * Title: 文件处理工具类 * </p> * <p> * Description:实现文件的简单处理,复制文件、目录等 * </p> * <p> * Copyright: Copyright (c) 2005 * </p> * <p>o * Company: www.easyjf.com * </p> * * @author 天一 * @version 1.0 */ public class FileUtil {/** * 复制目录下的文件(不包括该目录)到指定目录,会连同子目录一起复制过去。 * * @param targetDir * @param path */ public static void copyFileFromDir(String targetDir, String path) {File file = new File(path);createFile(targetDir, false);if (file.isDirectory()) {copyFileToDir(targetDir, listFile(file));}}/** * 复制目录下的文件(不包含该目录和子目录,只复制目录下的文件)到指定目录。 * * @param targetDir * @param path */ public static void copyFileOnly(String targetDir, String path) {File file = new File(path);File targetFile = new File(targetDir);if (file.isDirectory()) {File[] files = file.listFiles();for (int i=0;i<files.length;i++) {File subFile = files[i];if (subFile.isFile()) {copyFile(targetFile, subFile);}}}}/** * 复制目录到指定目录。targetDir是目标目录,path是源目录。 * 该方法会将path以及path下的文件和子目录全部复制到目标目录 * * @param targetDir * @param path */ public static void copyDir(String targetDir, String path) {File targetFile = new File(targetDir);createFile(targetFile, false);File file = new File(path);if (targetFile.isDirectory() && file.isDirectory()) {copyFileToDir(targetFile.getAbsolutePath() + "/" + file.getName(),listFile(file));}}public static void copyFileToDir(String targetDir, String filePath) {String[] filePaths = new String[]{filePath};copyFileToDir(targetDir, filePaths);}/** * 复制一组文件到指定目录。targetDir是目标目录,filePath是需要复制的文件路径 * * @param targetDir * @param filePath */ public static void copyFileToDir(String targetDir, String[] filePath) {if (targetDir == null || "".equals(targetDir)) {System.out.println("参数错误,目标路径不能为空");return;}File targetFile = new File(targetDir);if (!targetFile.exists()) {targetFile.mkdir();} else {if (!targetFile.isDirectory()) {System.out.println("参数错误,目标路径指向的不是一个目录!");return;}}if(filePath!=null){for (int i=0;i<filePath.length;i++) {String path = filePath[i];File file = new File(path);if (file.isDirectory()) {copyFileToDir(targetDir + "/" + file.getName(), listFile(file));} else {copyFileToDir(targetDir, file, "");}}}}/** * 复制文件到指定目录。targetDir是目标目录,file是源文件名,newName是重命名的名字。 * * @param targetDir * @param file * @param newName */ public static void copyFileToDir(String targetDir, File file, String newName) {String newFile = "";if (newName != null && !"".equals(newName)) {newFile = targetDir + "/" + newName;} else {newFile = targetDir + "/" + file.getName();}File tFile = new File(newFile);copyFile(tFile, file);}/** * 复制文件。targetFile为目标文件,file为源文件 * * @param targetFile * @param file */ public static void copyFile(File targetFile, File file) {if (targetFile.exists()) {System.out.println("文件" + targetFile.getAbsolutePath()+ "已经存在,跳过该文件!");return;} else {createFile(targetFile, true);}System.out.println("复制文件" + file.getAbsolutePath() + "到" + targetFile.getAbsolutePath());try {InputStream is = new FileInputStream(file);FileOutputStream fos = new FileOutputStream(targetFile);byte[] buffer = new byte[1024];while (is.read(buffer) != -1) {fos.write(buffer);}is.close();fos.close();} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}public static String[] listFile(File dir) {String absolutPath = dir.getAbsolutePath();String[] paths = dir.list();String[] files = new String[paths.length];for (int i = 0; i < paths.length; i++) {files[i] = absolutPath + "/" + paths[i];}return files;}public static void createFile(String path, boolean isFile){createFile(new File(path), isFile);}public static void createFile(File file, boolean isFile) {if (!file.exists()) {if (!file.getParentFile().exists()) {createFile(file.getParentFile(), false);} else {if (isFile) {try {file.createNewFile();} catch (IOException e) {throw new RuntimeException(e);}} else {file.mkdir();}}}}// 验证字符串是否为正确路径名的正则表达式 private static String matches = "[A-Za-z]:\\\\[^:?\"><*]*"; // 通过 sPath.matches(matches) 方法的返回值判断是否正确 // sPath 为路径字符串 /** * 根据路径删除指定的目录或文件,无论存在与否 *@param sPath 要删除的目录或文件 *@return 删除成功返回 true,否则返回 false。 */ public static boolean deleteFolder(String sPath) { boolean flag = false; File file = new File(sPath); // 判断目录或文件是否存在 if (!file.exists()) { // 不存在返回 false return flag; } else { // 判断是否为文件 if (file.isFile()) { // 为文件时调用删除文件方法 return deleteFile(sPath); } else { // 为目录时调用删除目录方法 return deleteDirectory(sPath); } } }/** * 删除单个文件 * @param sPath 被删除文件的文件名 * @return 单个文件删除成功返回true,否则返回false */ public static boolean deleteFile(String sPath) { boolean flag = false;if(sPath != null && !"".equals(sPath)){File file = new File(sPath);// 路径为文件且不为空则进行删除 if (file.isFile() && file.exists()) {file.delete();flag = true;}}return flag; }/** * 删除目录(文件夹)以及目录下的文件 * @param sPath 被删除目录的文件路径 * @return 目录删除成功返回true,否则返回false */ public static boolean deleteDirectory(String sPath) { //如果sPath不以文件分隔符结尾,自动添加文件分隔符 if (!sPath.endsWith(File.separator)) { sPath = sPath + File.separator; } File dirFile = new File(sPath); //如果dir对应的文件不存在,或者不是一个目录,则退出 if (!dirFile.exists() || !dirFile.isDirectory()) { return false; } boolean flag = true; //删除文件夹下的所有文件(包括子目录) File[] files = dirFile.listFiles(); for (int i = 0; i < files.length; i++) { //删除子文件 if (files[i].isFile()) { flag = deleteFile(files[i].getAbsolutePath()); if (!flag) {break;}} //删除子目录 else { flag = deleteDirectory(files[i].getAbsolutePath()); if (!flag) {break;}} } if (!flag) {return false;}//删除当前目录 if (dirFile.delete()) { return true; } else { return false; } } public static void main(String[] args) { String path = "D:\\Abc\\123\\Ab1"; FileUtil.createFile(path,false); System.out.println("createFile ok"); path = "D:\\Abc\\124"; boolean result = FileUtil.deleteFolder(path); System.out.println("DeleteFolder "+result); }/** * <p> * Description: 上传文件重命名 * </p> * * @param file * 文件名 * @return 文件 * @author : gaoying * @update : * @date : 2015-7-26 */ public static File renameFile(File file) {String body = "";String ext = "";Date date = new Date();int pot = file.getName().lastIndexOf(".");if (pot != -1) { // body = date.getTime() + ""; body = UUID.randomUUID().toString().replace("-", "");ext = file.getName().substring(pot);} else {body = (new Date()).getTime() + "";ext = "";}String newName = body + ext;file = new File(file.getParent(), newName);return file;}/** * <p> * Description: 上传文件重命名2 * </p> * * @param file * 文件名 * @return 文件格式强转为.png文件 * @author : gaoying * @update : * @date : 2015-7-26 */ public static File renameFilePng(File file) {String body = "";String ext = "";Date date = new Date();int pot = file.getName().lastIndexOf(".");if (pot != -1) { // body = date.getTime() + ""; body = UUID.randomUUID().toString().replace("-", "").toUpperCase();ext = ".png";} else {body = (new Date()).getTime() + "";ext = "";}String newName = body + ext;file = new File(file.getParent(), newName);return file;}/**==========start=============文件分割工具方法================start===============**/ /** * 当前目录路径 */ public static String currentWorkDir = System.getProperty("user.dir") + "\\";/** * 左填充 * * @param str * @param length * @param ch * @return */ public static String leftPad(String str, int length, char ch) {if (str.length() >= length) {return str;}char[] chs = new char[length];Arrays.fill(chs, ch);char[] src = str.toCharArray();System.arraycopy(src, 0, chs, length - src.length, src.length);return new String(chs);}/** * 删除文件 * * @param fileName * 待删除的完整文件名 * @return */ public static boolean delete(String fileName) {boolean result = false;File f = new File(fileName);if (f.exists()) {result = f.delete();} else {result = true;}return result;}/*** * 递归获取指定目录下的所有的文件(不包括文件夹) * * @param dirPath * @return */ public static ArrayList<File> getAllFiles(String dirPath) {File dir = new File(dirPath);ArrayList<File> files = new ArrayList<File>();if (dir.isDirectory()) {File[] fileArr = dir.listFiles();for (int i = 0; i < fileArr.length; i++) {File f = fileArr[i];if (f.isFile()) {files.add(f);} else {files.addAll(getAllFiles(f.getPath()));}}}return files;}/** * 获取指定目录下的所有文件(不包括子文件夹) * * @param dirPath * @return */ public static ArrayList<File> getDirFiles(String dirPath) {File path = new File(dirPath);File[] fileArr = path.listFiles();ArrayList<File> files = new ArrayList<File>();for (File f : fileArr) {if (f.isFile()) {files.add(f);}}return files;}/** * 获取指定目录下特定文件后缀名的文件列表(不包括子文件夹) * * @param dirPath * 目录路径 * @param suffix * 文件后缀 * @return */ public static ArrayList<File> getDirFiles(String dirPath,final String suffix) {File path = new File(dirPath);File[] fileArr = path.listFiles(new FilenameFilter() {@Override public boolean accept(File dir, String name) {String lowerName = name.toLowerCase();String lowerSuffix = suffix.toLowerCase();if (lowerName.endsWith(lowerSuffix)) {return true;}return false;}});ArrayList<File> files = new ArrayList<File>();for (File f : fileArr) {if (f.isFile()) {files.add(f);}}return files;}/** * 读取文件内容 * * @param fileName * 待读取的完整文件名 * @return 文件内容 * @throws IOException */ public static String read(String fileName) throws IOException {File f = new File(fileName);FileInputStream fs = new FileInputStream(f);String result = null;byte[] b = new byte[fs.available()];fs.read(b);fs.close();result = new String(b);return result;}/** * 写文件 * * @param fileName * 目标文件名 * @param fileContent * 写入的内容 * @return * @throws IOException */ public static boolean write(String fileName, String fileContent)throws IOException {boolean result = false;File f = new File(fileName);FileOutputStream fs = new FileOutputStream(f);byte[] b = fileContent.getBytes();fs.write(b);fs.flush();fs.close();result = true;return result;}/** * 追加内容到指定文件 * * @param fileName * @param fileContent * @return * @throws IOException */ public static boolean append(String fileName, String fileContent)throws IOException {boolean result = false;File f = new File(fileName);if (f.exists()) {RandomAccessFile rFile = new RandomAccessFile(f, "rw");byte[] b = fileContent.getBytes();long originLen = f.length();rFile.setLength(originLen + b.length);rFile.seek(originLen);rFile.write(b);rFile.close();}result = true;return result;}/** * 拆分文件 * * @param fileName * 待拆分的完整文件名 * @param byteSize * 按多少字节大小拆分 * @return 拆分后的文件名列表 * @throws IOException */ public List<String> splitBySize(String fileName, int byteSize)throws IOException {List<String> parts = new ArrayList<String>();File file = new File(fileName);int count = (int) Math.ceil(file.length() / (double) byteSize);int countLen = (count + "").length();ThreadPoolExecutor threadPool = new ThreadPoolExecutor(count, count * 3, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(count * 2));for (int i = 0; i < count; i++) {String partFileName = file.getName() + "." + leftPad((i + 1) + "", countLen, '0') + ".part";threadPool.execute(new SplitRunnable(byteSize, i * byteSize,partFileName, file));parts.add(partFileName);}return parts;}/** * 合并文件 * * @param dirPath * 拆分文件所在目录名 * @param partFileSuffix * 拆分文件后缀名 * @param partFileSize * 拆分文件的字节数大小 * @param mergeFileName * 合并后的文件名 * @throws IOException */ public void mergePartFiles(String dirPath, String partFileSuffix,int partFileSize, String mergeFileName) throws IOException {ArrayList<File> partFiles = FileUtil.getDirFiles(dirPath,partFileSuffix);Collections.sort(partFiles, new FileComparator());RandomAccessFile randomAccessFile = new RandomAccessFile(mergeFileName,"rw");randomAccessFile.setLength(partFileSize * (partFiles.size() - 1)+ partFiles.get(partFiles.size() - 1).length());randomAccessFile.close();ThreadPoolExecutor threadPool = new ThreadPoolExecutor( partFiles.size(), partFiles.size() * 3, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(partFiles.size() * 2));for (int i = 0; i < partFiles.size(); i++) {threadPool.execute(new MergeRunnable(i * partFileSize,mergeFileName, partFiles.get(i)));}}/** * 根据文件名,比较文件 * * @author yjmyzz@126.com * */ private class FileComparator implements Comparator<File> {@Override public int compare(File o1, File o2) {return o1.getName().compareToIgnoreCase(o2.getName());}}/** * 分割处理Runnable * * @author yjmyzz@126.com * */ private class SplitRunnable implements Runnable {int byteSize;String partFileName;File originFile;int startPos;public SplitRunnable(int byteSize, int startPos, String partFileName,File originFile) {this.startPos = startPos;this.byteSize = byteSize;this.partFileName = partFileName;this.originFile = originFile;}@Override public void run() {RandomAccessFile rFile;OutputStream os;try {rFile = new RandomAccessFile(originFile, "r");byte[] b = new byte[byteSize];rFile.seek(startPos);// 移动指针到每“段”开头 int s = rFile.read(b);os = new FileOutputStream(partFileName);os.write(b, 0, s);os.flush();os.close();} catch (IOException e) {e.printStackTrace();}}}/** * 合并处理Runnable * * @author yjmyzz@126.com * */ private class MergeRunnable implements Runnable {long startPos;String mergeFileName;File partFile;public MergeRunnable(long startPos, String mergeFileName, File partFile) {this.startPos = startPos;this.mergeFileName = mergeFileName;this.partFile = partFile;}@Override public void run() {RandomAccessFile rFile;try {rFile = new RandomAccessFile(mergeFileName, "rw");rFile.seek(startPos);FileInputStream fs = new FileInputStream(partFile);byte[] b = new byte[fs.available()];fs.read(b);fs.close();rFile.write(b);rFile.close();} catch (IOException e) {e.printStackTrace();}}}/**==========end===============文件分割工具方法================end================= * @throws Exception **/ /** * 文件上传 * @param f 需要上传文件 * @param path folderutil获取路径 * @throws Exception */ public static void fileUpload(File f , String path) throws Exception{InputStream is = new FileInputStream(f);OutputStream out = new FileOutputStream(path);byte[] buffer = new byte[1024];int r;while ((r = is.read(buffer)) > 0) {out.write(buffer, 0, r);}is.close();out.close();}/** * 文件下载 * @param path 文件路径 * @param out 输出流 * @throws Exception */ public static void fileDownLoad(String path,OutputStream out) throws Exception{File f = new File(path);InputStream is = new FileInputStream(f);byte[] buffer = new byte[1024];int r ;while ((r=is.read(buffer))>=0) {out.write(buffer, 0, r);}out.close();is.close();}/** * <P> * Description:获取config.properties文件中的参数 * </p> * @param * @return * @author : zlz * @date : 2016年9月01日 */ public static String getConfigProperties(String propertiesName){Properties properties = ConfigUtil.PROPERTIES;return properties.getProperty(propertiesName);}}
前端:
jsp:
<div class="form-group clearfix"><div class="uploadDiv"><!--<input type="file" class="form-control" >--> <div id="uploader-file" class="wu-example"><%-- <!--用来存放文件信息--> <div id="thelist" class="uploader-list"></div> <div class="btns"> <div id="picker">选择文件</div> <button id="ctlBtn" class="btn btn-default">开始上传</button> </div>--%> <span id="folder" class="glyphicon glyphicon-folder-open" style="font-size: 28px;color: grey;"></span><%--<input type="file" name="file" multiple="multiple"/>--%> <input type="file" multiple="multiple" style="display: none;" id="fileNine" name="fileName" οnchange="fileChange(this)"/><p style="color: red">*只支持上传png、jpg、jpeg、bmp、pdf格式</p><p>pdf文件:</p><div id="pdf"></div><p>图片预览:</p><div style="border: 1px solid red;width:752px;height: 302px;" id="preview"></div></div></div> </div>js:
var fileLength = 0;//上传文件数 var totalFileNames = "";//上传文件服务器保存名称 var totalImgNames = "";//上传文件真实名称
$("#folder").on("click", function () {var files = $("#fileNine");files.click(); });
function fileChange(e) {//fileInput选择文件后上传到临时文件夹var files = e.files;fileLength += files.length;if ( files.length > 0 && fileLength <= 9) {debugger for (var i = 0; i < files.length; i++) {var file = files[i];var name = file.name;var point = name.lastIndexOf(".");var type = name.substr(point).toLowerCase();if (type != ".png" && type != ".jpg" && type != ".jpeg" && type != ".bmp" && type != ".pdf") {alert("添加文件格式不正确!");$("#fileNine").val("");return;}}$.ajaxFileUpload({url: "/tribuneInfo/test",secureuri: false, //是否需要安全协议,一般设置为false fileElementId: 'fileNine', //文件上传域的ID dataType: 'JSON', //返回值类型 一般设置为json success: function (data) {var temp = eval("("+data+")");//服务器成功响应处理函数 if (temp.code == 1) {var pic = $("#preview");var pdf = $("#pdf");var imgNames = temp.imgNames.split(",");var imgRealNames = temp.imgRealNames.split(",");for (var i = 0; i < imgNames.length-1; i++) {if(imgNames[i] != null && imgNames[i] != undefined && imgNames[i] != "" ){totalFileNames+=imgNames[i]+",";totalRealNames+=imgRealNames[i]+",";pic.append("<div id=\"" + imgNames[i] + "\" style=\"height: 150px;width: 150px;float:left\"><a href=\"javascript:void(0);\" οnclick=\"delFile('" + imgNames[i] +"','" + imgRealNames[i] + "');\" class=\"close close1\">×</a><img src=\"${BASE_FRONT}/resource/temp/" + imgNames[i] +"\" style=\"height: 100%;width: 100%;\"/></div>");}}var fileNames = temp.fileNames.split(",");var fileRealNames = temp.fileRealNames.split(",");for (var i = 0; i < fileNames.length-1; i++) {if (fileNames[i] != null && fileNames[i] != undefined && fileNames[i] != "") {totalFileNames+=fileNames[i]+",";totalRealNames+=fileRealNames[i]+",";pdf.append("<div id=\"" + fileNames[i] + "\"><span>" + fileRealNames[i] + "<a href=\"javascript:void(0);\" οnclick=\"delFile('" + fileNames[i] +"','" + fileRealNames[i] + "');\" style=\"color: red;float: right;\" class=\"close\">×</a></span></div>");}}} else {alert("预览失败");}},error: function (data, status, e) {//服务器响应失败处理函数 alert(e);}});} else {fileLength -= files.length;$("#fileNine").val("");alert("您最多能上传9个文件!");} }; function delFile(name,realName){//删除文件预览$.ajax({type: 'post',dataType: "json",data: {fileName:name},url: "/tribuneInfo/delFile",cache: false,async: false,success: function (data) {if ((data.code == 1)) {debugger fileLength -= 1;totalFileNames = totalFileNames.replace(name+",","");totalRealNames = totalRealNames.replace(realName+",","");document.getElementById(name).remove();} else {alert("删除失败!");}},error: function () {alert("系统异常,删除失败!");}}); }$("#upload").on("click",function() {//上传所有文件 $.ajax({url: "/tribuneInfo/saveFileData",data: {fileName: totalFileNames,realName: totalRealNames },type: 'post', cache: false,async: false,success: function (data) {if (data.code == '1') {alert("上传成功!");} else {alert("上传失败")}},error: function (data, e) {alert(e);alert("系统错误");}});});
后台:
//上传文件到临时文件夹,返回上传文件真实名称和服务器上传名称,图片进行预览
@RequestMapping(value = "/test") @ResponseBody public void test(HttpServletRequest request,HttpServletResponse response) throws IOException {HashMap map = new HashMap();DefaultMultipartHttpServletRequest multipartHttpServletRequest = (DefaultMultipartHttpServletRequest) request;List<MultipartFile> multipartFileList = multipartHttpServletRequest.getMultiFileMap().get("fileName");//多个文件上传Properties properties = ConfigUtil.PROPERTIES;String path = properties.getProperty("imageResourcrsPath");path = path + "/temp";//临时文件夹服务器路径String imgNames = "";String imgRealNames = "";String fileNames = "";String fileRealNames = "";for (int i = 0; i < multipartFileList.size(); i++) {String fileName2 = multipartFileList.get(i).getOriginalFilename();if (fileName2 == null || "".equals(fileName2)) {continue;}File file2 = new File(fileName2);File newFile2 = FileUtil.renameFile(file2);//重命名上传文件,防止文件名重复String newFileName2 = newFile2.getName();File targetFile2 = new File(path, newFileName2);if (!targetFile2.exists()) {targetFile2.mkdirs();}multipartFileList.get(i).transferTo(targetFile2);if (newFileName2.endsWith(".pdf")) {fileNames += newFileName2 + ",";fileRealNames += fileName2 + ",";} else {imgRealNames += fileName2 + ",";imgNames += newFileName2 + ",";}}map.put("code", 1);map.put("imgNames", imgNames);map.put("imgRealNames", imgRealNames);map.put("fileNames", fileNames);map.put("fileRealNames", fileRealNames);String m = JsonUtils.encode(map);response.setContentType("text/html;charset=utf-8");response.getWriter().write(m); }//删除临时文件夹中的文件
@RequestMapping(value = "/delFile") @ResponseBody public HashMap delFile(HttpServletRequest request,HttpServletResponse response){String name = request.getParameter("fileName");HashMap map = new HashMap(1);Properties properties = ConfigUtil.PROPERTIES;String path = properties.getProperty("imageResourcrsPath");path = path + "/temp/" + name;boolean flag = FileUtil.deleteFile(path);if(flag){map.put("code",1);}else{map.put("code",-1);}return map; }
//将临时文件夹中的文件复制到服务器上传文件的目录下,并将临时文件夹中的相应文件删除
@RequestMapping(value = "/saveFileData", method = RequestMethod.POST) @ResponseBody public HashMap saveFileData(HttpServletRequest request, HttpServletResponse response, TribuneInfoModel tribuneInfoModel) throws IOException {String fileNames = request.getParameter("fileName");String realNames = request.getParameter("realName");Properties properties = ConfigUtil.PROPERTIES;String path = properties.getProperty("imageResourcrsPath");String realPath = path + "/temp";//临时文件夹路径path = path + "/file";//上传文件目录路径 String[] fileArr = fileNames.split(",");String[] realArr = realNames.split(",");for (int i = 0; i < fileArr.length; i++) {String fileName =fileArr[i];String realName = realArr[i];if (fileName == null || "".equals(fileName)) {continue;}try {FileUtil.copyFileToDir(path,realPath+"/"+fileName);//复制文件FileUtil.deleteFile(realPath+"/"+fileName);//删除文件} catch (Exception e) {e.printStackTrace();}}HashMap map = new HashMap();map.put("code", 1);return map; }
这篇关于多文件ajaxfileupload上传的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!