• [技术干货] java:ftpclient实现文件上传下载,使用原文件名
     import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream;  import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile;  public class FtpFileTransferWithOriginalName {      public static void main(String[] args) {         String server = "your_server";         int port = 21;         String user = "your_user";         String pass = "your_password";          // 下载文件         downloadFile(server, port, user, pass, "/remote_path/file.txt", "/local_path");          // 上传文件         uploadFile(server, port, user, pass, "/local_path/file.txt", "/remote_path");     }      public static void downloadFile(String server, int port, String user, String pass, String remotePath, String localPath) {         FTPClient ftpClient = new FTPClient();         try {             ftpClient.connect(server, port);             ftpClient.login(user, pass);             ftpClient.enterLocalPassiveMode();              FTPFile[] files = ftpClient.listFiles(remotePath);             if (files.length > 0) {                 FTPFile file = files[0];                 String originalFileName = file.getName();  // 获取原文件名                 File localFile = new File(localPath + File.separator + originalFileName);  // 使用原文件名创建本地文件                 OutputStream outputStream = new FileOutputStream(localFile);                 ftpClient.retrieveFile(remotePath, outputStream);                 outputStream.close();             }              ftpClient.logout();         } catch (IOException e) {             e.printStackTrace();         } finally {             try {                 if (ftpClient.isConnected()) {                     ftpClient.disconnect();                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     }      public static void uploadFile(String server, int port, String user, String pass, String localPath, String remotePath) {         FTPClient ftpClient = new FTPClient();         try {             ftpClient.connect(server, port);             ftpClient.login(user, pass);             ftpClient.enterLocalPassiveMode();              File localFile = new File(localPath);             String originalFileName = localFile.getName();  // 获取原文件名             // 检查服务器端是否存在同名文件,如果存在则添加后缀避免冲突             boolean fileExists = checkFileExists(ftpClient, remotePath + File.separator + originalFileName);             if (fileExists) {                 originalFileName = addSuffixToFileName(originalFileName);             }             ftpClient.storeFile(remotePath + File.separator + originalFileName, new FileInputStream(localFile));              ftpClient.logout();         } catch (IOException e) {             e.printStackTrace();         } finally {             try {                 if (ftpClient.isConnected()) {                     ftpClient.disconnect();                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     }      // 检查服务器端指定路径是否存在文件     public static boolean checkFileExists(FTPClient ftpClient, String filePath) throws IOException {         FTPFile[] files = ftpClient.listFiles(filePath);         return files.length > 0;     }      // 给文件名添加后缀以避免冲突     public static String addSuffixToFileName(String fileName) {         return fileName + "_new";     } } ————————————————               原文链接:https://blog.csdn.net/hongyuxiongji/article/details/140848594 
  • [技术干货] Java从FTP下载文件到本地前端+后端
    一、前端 1.首先创建下载文件按钮 <template slot-scope="scope">   <div class="Style_center">      <el-button @click="downloadFile(scope.row)" type="text" >下载文件</el-button>   </div> </template> 2.其次编写手动触发事件 downloadFile(row){    this.axios.post("/Zlhzjbxxb/downloadFile", {wjdz:row.wjdz, wjmc:row.wjmc},{       headers: {   //解决后端接收时多了一个 =        'Content-Type': 'application/json'       }    }).then(res => {        this.$message.success("成功下载至桌面");       }) }         wjdz和wjmc是要传给后端的值。后端获取到文件的FTP路径的时候,路径末尾会莫名的多一个等于(=)的符号,反正我的是出现了,把headers加上就完美解决了  二、后端         结尾处贴附后端全码          1.用Map来接收前端传过来的数据  public void downloadFile(@RequestBody Map<String,String> map) throws UnsupportedEncodingException {           String wjdz = map.get("wjdz");         String wjmc = map.get("wjmc"); }         2.获取的文件路径,比如C:\Users\Administrator\Desktop,中间的 \ 符号会被转义,所以需要转义回来,其它符号的转义可以百度一下哦。  //解决文件名中间的符号被转义(如“/”被转义成%2F) wjdz = wjdz.replaceAll("%(?![0-9a-fA-F]{2})", "%2F"); String wjdzStr = URLDecoder.decode(wjdz, "UTF-8");         3.在上传FTP文件的时候,为了防止重复的文件名在FTP服务器里出现,于是给每一个文件都设置了不同的文件编号,格式为xxx/2021-05-09/**************(*是uuid创建拼接在后面的),在FTP服务器里面下载文件的话需要找到具体的目录,然后再去下载具体的文件。接下来开始获取FTP文件的名称以及目录名   //获取第一个“/”的位置 int index = wjdzStr.indexOf("/");   //获取第二个 “/” 的位置 index = wjdzStr.indexOf("/", index + 1);   //获取第二个 “/” 的位置后面的字符串 String result = wjdzStr.substring(index + 1);   //获取第二个 “/” 的位置之前的字符串 String menuDz = wjdzStr.substring(0,index + 1);         通过以上方式来获取文件的目录和文件名称,result就是需要下载文件的名称,menuDz就是文件所在的FTP服务器里面的目录          4.接下来就是设置文件的保存地址了  FileOutputStream out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\" + wjmc )         这里我是给设置到了桌面,后面的wjmc是该文件的中文名称,总不能给用户展示文件的路径吧......          5.开始连接FTP服务器并且登录  FTPClient ftpClient = new FTPClient(); ftpClient.connect(hostName, port);//连接 ftpClient.login(userName, password);//登陆         这里的hostname是自己的IP地址,port是端口号(默认21),userName是用户名,password就是密码了,如果在yml文件里面封装的有,便可以直接调用,没有就自己手动填上去也是一样的          6.登上去之后便开始下载文件了,首先找到文件目录,然后再去下载  ftpClient.changeWorkingDirectory(menuDz);//切换目录   //下载文件( 获取FTP服务器指定目录的文件) //参数result,服务器指定文件 //参数out,本地输出流(负责下载后写入) ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE); ftpClient.retrieveFile(result,out);          ftpClient.setFileType这个就是用来配置文件传输形式的,默认ASCII,我们一般都是配置用二进制形式传送          7.最后最后就是退出登录和关闭连接了  ftpClient.logout();// 退出登录 ftpClient.disconnect();// 断开连接         8.接下来就是后端的全码了  @PostMapping(value = "/downloadFile") @ResponseBody public void downloadFile(@RequestBody Map<String,String> map) throws UnsupportedEncodingException {           String wjdz = map.get("wjdz");         String wjmc = map.get("wjmc");           //解决文件名中间的符号被转义(如“/”被转义成%2F)         wjdz = wjdz.replaceAll("%(?![0-9a-fA-F]{2})", "%2F");         String wjdzStr = URLDecoder.decode(wjdz, "UTF-8");           FTPClient ftpClient = new FTPClient();         int index = wjdzStr.indexOf("/"); //获取第一个“/”的位置         index = wjdzStr.indexOf("/", index + 1);//获取第二个 “/” 的位置         String result = wjdzStr.substring(index + 1);//获取第二个 “/” 的位置后面的字符串         String menuDz = wjdzStr.substring(0,index + 1);//获取第二个 “/” 的位置之前的字符串         try (FileOutputStream out = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\" + wjmc )) {               FtpUtil ftpUtil = new FtpUtil();             ftpClient.connect(ftpUtil.hostName, ftpUtil.port);//连接             ftpClient.login(ftpUtil.userName, ftpUtil.password);//登陆             ftpClient.changeWorkingDirectory(menuDz);//切换目录               //下载文件( 获取FTP服务器指定目录的文件)             //参数result,服务器指定文件             //参数out,本地输出流(负责下载后写入)             ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);             ftpClient.retrieveFile(result,out);           } catch (SocketException e) {             e.printStackTrace();         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         } finally {             try {                 ftpClient.logout();// 退出登录                 ftpClient.disconnect();// 断开连接             } catch (IOException e) {                 e.printStackTrace();             }         }     }          记录这些方便自己的同时也想能帮助一下和我遇到同样困惑的人,有什么不足或者不对的地方还望大佬们指出 ————————————————                    原文链接:https://blog.csdn.net/glacierW/article/details/130581947 
  • [技术干货] JAVA实现FTP文件传输
    一、FTP介绍 FTP是File Transfer Protocol(文件传输协议)的英文简称,即文件协议。用于Internet上的控制文件的双向传输。同时,它是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有的应用程序都遵守同一种协议以传输文件。在FTP的使用中,用户经常遇到两个概念:下载(Download)和上次(Upload)。下载文件就是从远程主机拷贝文件至自己的计算机上;删除文件就是将文件从自己的计算机中拷贝至远程主机上。用Internet语言来说,用户可通过客户机程序向远程主机上传、下载文件(摘自百度百科)。  二、准备工作 1、在服务器安装FTP服务 FTP服务需要下FileZilla_Server服务安装程序,大家可以在网上搜索,也可以通过我的资源下载 点击下载 2、编写Java连接FTP源码 大家也可以通过CSDN资源下载我的源码 点击下载 三、实现步骤 1、编写FTP-Config.properties配置属性文件 FTP_IP=localhost FTP_PORT=21 FTP_USER=wangjunwei FTP_PASSWORD=wangjunwei FTP_PATH=/FTP FTP_DRIVELETTER=E: 2、编写FTPUtil读取FTP配置属性文件 /**      * FTP服务地址      */     private static String FTP_IP;     /**      * FTP端口号      */     private static Integer FTP_PORT;     /**      * FTP用户名      */     private static String FTP_USER;     /**      * FTP密码      */     private static String FTP_PASSWORD;     /**      * FTP根路径      */     private static String FTP_PATH;     /**      * 映射盘符      */     private static String FTP_DRIVELETTER;     static{         try {             Properties properties = new Properties();             InputStream inputStream = FtpUtil.class.getResourceAsStream("/FTP-Config.properties");             properties.load(inputStream);             FTP_IP = properties.getProperty("FTP_IP");             FTP_PORT = Integer.valueOf(properties.getProperty("FTP_PORT"));             FTP_USER = properties.getProperty("FTP_USER");             FTP_PASSWORD = properties.getProperty("FTP_PASSWORD");             FTP_PATH = properties.getProperty("FTP_PATH");             FTP_DRIVELETTER = properties.getProperty("FTP_DRIVELETTER");         } catch (IOException e) {             e.printStackTrace();         }     }  3、连接(登录)FTP服务端获取授权 private static FTPClient ftpClient;       public static FTPClient connectionFTPServer() {         ftpClient = new FTPClient();         try {             ftpClient.connect(FtpUtil.getFTP_IP(), FtpUtil.getFTP_PORT());             ftpClient.login(FtpUtil.getFTP_USER(), FtpUtil.getFTP_PASSWORD());             if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {                 logger.info("==============未连接到FTP,用户名或密码错误=================");                 ftpClient.disconnect();             } else {                 logger.info("==============连接到FTP成功=================");             }         } catch (SocketException e) {             e.printStackTrace();             logger.info("==============FTP的IP地址错误==============");         } catch (IOException e) {             e.printStackTrace();             logger.info("==============FTP的端口错误==============");         }         return ftpClient;     }  4、编写文件上传、下载相关方法 /**      * 切换到父目录      *       * @return 切换结果 true:成功, false:失败      */     private static boolean changeToParentDir() {         if (!ftpClient.isConnected()) {             return false;         }         try {             return ftpClient.changeToParentDirectory();         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 改变当前目录到指定目录      *       * @param dir      *            目的目录      * @return 切换结果 true:成功,false:失败      */     private static boolean cd(String dir) {         if (!ftpClient.isConnected()) {             return false;         }         try {             return ftpClient.changeWorkingDirectory(dir);         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 获取目录下所有的文件名称      *       * @param filePath      *            指定的目录      * @return 文件列表,或者null      */     private static FTPFile[] getFileList(String filePath) {         if (!ftpClient.isConnected()) {             return null;         }         try {             return ftpClient.listFiles(filePath);         } catch (IOException e) {             e.printStackTrace();             return null;         }     }       /**      * 切换工作目录      *       * @param ftpPath      *            目的目录      * @return 切换结果      */     public static boolean changeDir(String ftpPath) {         if (!ftpClient.isConnected()) {             return false;         }         try {             // 将路径中的斜杠统一             char[] chars = ftpPath.toCharArray();             StringBuffer sbStr = new StringBuffer(256);             for (int i = 0; i < chars.length; i++) {                 if ('\\' == chars[i]) {                     sbStr.append('/');                 } else {                     sbStr.append(chars[i]);                 }             }             ftpPath = sbStr.toString();             if (ftpPath.indexOf('/') == -1) {                 // 只有一层目录                 ftpClient.changeWorkingDirectory(new String(ftpPath.getBytes(), "iso-8859-1"));             } else {                 // 多层目录循环创建                 String[] paths = ftpPath.split("/");                 for (int i = 0; i < paths.length; i++) {                     ftpClient.changeWorkingDirectory(new String(paths[i].getBytes(), "iso-8859-1"));                 }             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 循环创建目录,并且创建完目录后,设置工作目录为当前创建的目录下      *       * @param ftpPath      *            需要创建的目录      * @return      */     public static boolean mkDir(String ftpPath) {         if (!ftpClient.isConnected()) {             return false;         }         try {             // 将路径中的斜杠统一             char[] chars = ftpPath.toCharArray();             StringBuffer sbStr = new StringBuffer(256);             for (int i = 0; i < chars.length; i++) {                 if ('\\' == chars[i]) {                     sbStr.append('/');                 } else {                     sbStr.append(chars[i]);                 }             }             ftpPath = sbStr.toString();             if (ftpPath.indexOf('/') == -1) {                 // 只有一层目录                 ftpClient.makeDirectory(new String(ftpPath.getBytes(), "iso-8859-1"));                 ftpClient.changeWorkingDirectory(new String(ftpPath.getBytes(), "iso-8859-1"));             } else {                 // 多层目录循环创建                 String[] paths = ftpPath.split("/");                 for (int i = 0; i < paths.length; i++) {                     ftpClient.makeDirectory(new String(paths[i].getBytes(), "iso-8859-1"));                     ftpClient.changeWorkingDirectory(new String(paths[i].getBytes(), "iso-8859-1"));                 }             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 上传单个文件      *       * @param inputStream      *            单个文件流对象      * @param newFileName      *            新文件名      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static boolean uploadSingleAttachment(File file, String newFileName, String folder) {         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return false;         }         boolean result = false;         if (ftpClient != null) {             FileInputStream fileInputStream = null;             try {                 fileInputStream = new FileInputStream(file);                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 ftpClient.setBufferSize(100000);                 ftpClient.setControlEncoding("utf-8");                 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                 result = ftpClient.storeFile(new String(newFileName.getBytes(), "iso-8859-1"), fileInputStream);             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return false;             } finally {                 try {                     if (ftpClient.isConnected()) {                         FtpUtil.close();                     }                     fileInputStream.close();                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }         return result;     }     /**      * 上传多个文件      *       * @param inputStreams      *            多文件流对象数组      * @param newFileNames      *            多文件名数组(与流对象数组一一对应)      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static String[] uploadBatchAttachment(File[] files , String[] newFileNames, String folder) {         String[] filesSaveUrls = new String[files.length];         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }         if (ftpClient != null) {             FileInputStream fileInputStream = null;             try {                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 for (int i = 0; i < files.length; i++) {                     filesSaveUrls[i] = FtpUtil.FTP_PATH;                     fileInputStream = new FileInputStream(files[i]);                     if(StringUtils.isNotEmpty(folder)){                         filesSaveUrls[i] += "/"+folder;                     }                     ftpClient.setBufferSize(100000);                     ftpClient.setControlEncoding("utf-8");                     ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                     ftpClient.storeFile(new String(newFileNames[i].getBytes(), "iso-8859-1"), fileInputStream);                     filesSaveUrls[i] += "/"+newFileNames[i];                 }             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return null;             } finally {                 try {                     if (ftpClient.isConnected()) {                         FtpUtil.close();                     }                     fileInputStream.close();                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }         return filesSaveUrls;     }     /**      * 上传单个文件      *       * @param inputStreams      *            多文件流对象数组      * @param newFileNames      *            多文件名数组(与流对象数组一一对应)      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static String uploadSingleAttachment(MultipartFile multipartFile, String newFileName, String folder) {         String filesSaveUrl = null;         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }         if (ftpClient != null) {             try {                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 filesSaveUrl = FtpUtil.FTP_PATH;                 if(StringUtils.isNotEmpty(folder)){                     filesSaveUrl += "/"+folder;                 }                 ftpClient.setBufferSize(100000);                 ftpClient.setControlEncoding("utf-8");                 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                 ftpClient.storeFile(new String(newFileName.getBytes(), "iso-8859-1"), multipartFile.getInputStream());                 filesSaveUrl += "/"+newFileName;             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return null;             } finally {                 if (ftpClient.isConnected()) {                     FtpUtil.close();                 }             }         }         return filesSaveUrl;     }     /**      * 上传多个文件      *       * @param inputStreams      *            多文件流对象数组      * @param newFileNames      *            多文件名数组(与流对象数组一一对应)      * @param folder      *            自定义保存的文件夹      * @return 上传结果      */     public static String[] uploadBatchAttachment(MultipartFile[] multipartFiles , String[] newFileNames, String folder) {         String[] filesSaveUrls = new String[multipartFiles.length];         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }         if (ftpClient != null) {             InputStream inputStream = null;             try {                 if(StringUtils.isNotEmpty(folder)){                     FtpUtil.mkDir(folder);                 }                 for (int i = 0; i < multipartFiles.length; i++) {                     filesSaveUrls[i] = FtpUtil.FTP_PATH;                     inputStream = multipartFiles[i].getInputStream();                     if(StringUtils.isNotEmpty(folder)){                         filesSaveUrls[i] += "/"+folder;                     }                     ftpClient.setBufferSize(100000);                     ftpClient.setControlEncoding("utf-8");                     ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                     ftpClient.storeFile(new String(newFileNames[i].getBytes(), "iso-8859-1"), inputStream);                     filesSaveUrls[i] += "/"+newFileNames[i];                 }             } catch (Exception e) {                 FtpUtil.close();                 e.printStackTrace();                 return null;             } finally {                 try {                     if (ftpClient.isConnected()) {                         FtpUtil.close();                     }                     inputStream.close();                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }         return filesSaveUrls;     }       /**      * 根据文件url下载文件      *       * @param path 被下载文件url      * @return 文件流对象      * @throws IOException      */     public static final InputStream downLoadFile(String path,String filaName){         FtpUtil.connectionFTPServer();         if (!ftpClient.isConnected()) {             logger.info("==============FTP服务已断开==============");             return null;         }          ftpClient.enterLocalPassiveMode();          FtpUtil.changeDir(path);          try {             return ftpClient.retrieveFileStream(filaName);         } catch (IOException e) {             logger.info("==============获取文件异常==============");             e.printStackTrace();             return null;         }finally{             if (ftpClient.isConnected()) {                 FtpUtil.close();             }         }     }           /**      * 返回FTP目录下的文件列表      *       * @param pathName      * @return      */     public static String[] getFileNameList(String pathName) {         if (!ftpClient.isConnected()) {             return null;         }         try {             return ftpClient.listNames(pathName);         } catch (IOException e) {             e.printStackTrace();             return null;         }     }       /**      * 删除FTP上的文件      *       * @param ftpDirAndFileName      *            路径开头不能加/,比如应该是test/filename1      * @return      */     public static boolean deleteFile(String ftpDirAndFileName) {         if (!ftpClient.isConnected()) {             FtpUtil.connectionFTPServer();         }         try {             return ftpClient.deleteFile(ftpDirAndFileName);         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 删除FTP目录      *       * @param ftpDirectory      * @return      */     public static boolean deleteDirectory(String ftpDirectory) {         if (!ftpClient.isConnected()) {             return false;         }         try {             return ftpClient.removeDirectory(ftpDirectory);         } catch (IOException e) {             e.printStackTrace();             return false;         }     }       /**      * 关闭链接      */     public static void close() {         try {             if (ftpClient != null && ftpClient.isConnected()) {                 ftpClient.disconnect();             }         } catch (Exception e) {             e.printStackTrace();         }     } ————————————————              原文链接:https://blog.csdn.net/brianang/article/details/78727835 
  • [技术干货] Java实现文件上传到ftp服务器
    一、ftp简介 文件传输协议(File Transfer Protocol,FTP)是用于在网络上进行文件传输的一套标准协议,它工作在 OSI 模型的第七层, TCP 模型的第四层, 即应用层, 使用 TCP 传输而不是 UDP, 客户在和服务器建立连接前要经过一个“三次握手”的过程, 保证客户与服务器之间的连接是可靠的, 而且是面向连接, 为数据传输提供可靠保证。  FTP允许用户以文件操作的方式(如文件的增、删、改、查、传送等)与另一主机相互通信。然而, 用户并不真正登录到自己想要存取的计算机上面而成为完全用户, 可用FTP程序访问远程资源, 实现用户往返传输文件、目录管理以及访问电子邮件等等, 即使双方计算机可能配有不同的操作系统和文件存储方式。  1.1工作流程 在传输文件时,FTP 客户端程序先与服务器建立连接,然后向服务器发送命令。服务器收到命令后给予响应,并执行命令。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,就可以相互传输数据  1.2工作模式 FTP支持两种模式:Standard (PORT方式,主动方式),Passive (PASV,被动方式) 1.3传输方式 FTP的传输有两种方式:ASCII、二进制 二、windows开启ftp服务 在自己的电脑上可以开启一个ftp服务用于测试。 1:打开控制面板—>程序—>启动或关闭windows功能 找到互联网信息服务勾选其中的ftp服务器,web管理,万维网服务。 系统就会安装IIS服务管理器了,安装过程可能需要等待几分钟。 2:回到电脑桌面,右击“计算机”,点击 管理,进入计算机管理界面。在这里,我们就可以看到刚刚添加的IIS服务,接下来的操作就像我们在VPS主机上添加网站差不多的操作步骤了。选中IIS服务——>网站——>右键添加FTP站点 3:设置ftp基本信息。路径为远程连接的工作路径 选取本机的IP地址,SSL如果不涉密可以选无。 这里权限先这样设置之后再修改,点击完成即可。 保存后在此后即可看到添加的ftp服务。可以这样开启多个。 注意: 要想通过java程序连接FTP在这里我新建了一个FTP的用户,新建一个用户ftptest,密码:123456,如下所示 这里注意一定要把√取消掉确定完成新建,之后修改FTP服务器设置,添加这个特定用户。 点击创建的ftp名称—>ftp身份验证—>修改身份认证,禁止匿名身份 返回最开始的页面,添加刚才创建的用户,如果创建用户对勾选着可能添加不成功。 输入刚才新建的用户名和密码,完成 如何测试刚才新建的用户是否有效嘞有好多种方法常用的是CMD命令行和下面介绍的这种 打开我的电脑在最上面输入 ftp://+FTP之前设置的IP地址 我的是ftp://192.168.10.11/回车 输入用户和密码即可登录 注意:如果输入后弹出FTP文件夹错误,需要查看一下防火墙是否放行FTP服务器 解决方式: 1、打开防火墙,点击允许应用或功能通过Windows Defender防火墙 2、找到FTP服务器并勾选,然后点击确定 三、java连接ftp org.apache.commons.net提供了对FTP的开发,引入以下依赖 <dependency>     <groupId>commons-net</groupId>     <artifactId>commons-net</artifactId>     <version>3.9.0</version> </dependency> 3.1ftp工具类 连接ftp服务  @Slf4j public class FtpClient {      /**     * FTP服务地址     */    private static String FTP_IP;    /**     * FTP端口号     */    private static Integer FTP_PORT;    /**     * FTP用户名     */    private static String FTP_USER;    /**     * FTP密码     */    private static String FTP_PASSWORD;    /**     * FTP根路径     */    private static String FTP_PATH;    /**     * 映射盘符     */    private static String FTP_DRIVELETTER;    private static FTPClient ftpClient;      static {       try {          // 根据该类的class文件获取到yaml文件          Yaml yaml = new Yaml();          URL resource = FtpClient.class.getClassLoader().getResource("application-dev.yml");          assert resource != null;          // 把yaml文件加载到对象中          Object obj = yaml.load(new FileInputStream(resource.getFile()));          // 解析对象中的属性并赋值          JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(obj));          JSONObject ftp = jsonObject.getObject("fanhai", JSONObject.class).getObject("ftp", JSONObject.class);          FTP_IP = String.valueOf(ftp.get("ip"));          FTP_PORT = Integer.valueOf(String.valueOf(ftp.get("port")));          FTP_USER = String.valueOf(ftp.get("username"));          FTP_PASSWORD = String.valueOf(ftp.get("password"));       } catch (Exception e) {          e.printStackTrace();       }    }      public static FTPClient getFtpConnection() {       ftpClient = new FTPClient();       try {          //连接          ftpClient.connect(FtpClient.FTP_IP, FtpClient.FTP_PORT);          //设置编码          ftpClient.setControlEncoding("UTF-8");          //设置传输文件类型          ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);          //登录          ftpClient.login(FtpClient.FTP_USER, FtpClient.FTP_PASSWORD);          if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {             log.info("==============未连接到FTP,用户名或密码错误=================");             //拒绝连接             ftpClient.disconnect();          } else {             log.info("==============连接到FTP成功=================");          }       } catch (SocketException e) {          e.printStackTrace();          log.info("==============FTP的IP地址错误==============");       } catch (IOException e) {          e.printStackTrace();          log.info("==============FTP的端口错误==============");       }       return ftpClient;    }      public static void closeConnect() {       log.warn("关闭ftp服务器");       try {          if (ftpClient != null) {             ftpClient.logout();             ftpClient.disconnect();          }       } catch (Exception e) {          e.printStackTrace();       }    } }  ftp工具类,进行文件的上传、下载操作 ————————————————               原文链接:https://blog.csdn.net/weixin_44486583/article/details/130319858 
  • [技术干货] java实现把文件上传至ftp服务器
    用java实现ftp文件上传。我使用的是commons-net-1.4.1.zip。其中包含了众多的java网络编程的工具包。 1 把commons-net-1.4.1.jar包加载到项目工程中去。 2 看如下代码:  import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply;   public class FileTool {       /**      * Description: 向FTP服务器上传文件      * @Version      1.0      * @param url FTP服务器hostname      * @param port  FTP服务器端口      * @param username FTP登录账号      * @param password  FTP登录密码      * @param path  FTP服务器保存目录      * @param filename  上传到FTP服务器上的文件名      * @param input   输入流      * @return 成功返回true,否则返回false *      */     public static boolean uploadFile(String url,// FTP服务器hostname             int port,// FTP服务器端口             String username, // FTP登录账号             String password, // FTP登录密码             String path, // FTP服务器保存目录             String filename, // 上传到FTP服务器上的文件名             InputStream input // 输入流     ){         boolean success = false;         FTPClient ftp = new FTPClient();         ftp.setControlEncoding("GBK");         try {             int reply;             ftp.connect(url, port);// 连接FTP服务器             // 如果采用默认端口,可以使用ftp.connect(url)的方式直接连接FTP服务器             ftp.login(username, password);// 登录             reply = ftp.getReplyCode();             if (!FTPReply.isPositiveCompletion(reply)) {                 ftp.disconnect();                 return success;             }             ftp.setFileType(FTPClient.BINARY_FILE_TYPE);             ftp.makeDirectory(path);             ftp.changeWorkingDirectory(path);             ftp.storeFile(filename, input);             input.close();             ftp.logout();             success = true;         } catch (IOException e) {             e.printStackTrace();         } finally {             if (ftp.isConnected()) {                 try {                     ftp.disconnect();                 } catch (IOException ioe) {                 }             }         }         return success;     }       /**      * 将本地文件上传到FTP服务器上 *      */     public static void upLoadFromProduction(String url,// FTP服务器hostname             int port,// FTP服务器端口             String username, // FTP登录账号             String password, // FTP登录密码             String path, // FTP服务器保存目录             String filename, // 上传到FTP服务器上的文件名             String orginfilename // 输入流文件名        ) {         try {             FileInputStream in = new FileInputStream(new File(orginfilename));             boolean flag = uploadFile(url, port, username, password, path,filename, in);             System.out.println(flag);         } catch (Exception e) {             e.printStackTrace();         }     }      //测试     public static void main(String[] args) {                  upLoadFromProduction("192.168.13.32", 21, "hanshibo", "han", "韩士波测试", "hanshibo.doc", "E:/temp/H2数据库使用.doc");     } } 3 直接运行。即可把指定的文件上传到ftp服务器.有需要jar包的可以到我的资源中去下载。 结束! ————————————————         原文链接:https://blog.csdn.net/atomcrazy/article/details/8943194 
  • [技术干货] java ftp文件上传下载删除
    Java FTP 文件上传、下载与删除在现代软件开发中,文件传输是一项常见的需求。FTP(File Transfer Protocol)是一个用于在网络上进行文件传输的标准协议。在Java中,我们可以利用Apache Commons Net库来实现FTP的文件上传、下载和删除操作。本文将逐步讲解如何使用Java进行FTP操作,并提供相应的代码示例。准备工作在开始之前,我们需要引入Apache Commons Net库。在你的Java项目中添加以下依赖(如果使用Maven):登录后复制<dependency><groupId>commons-net</groupId><artifactId>commons-net</artifactId><version>3.8.0</version></dependency>FTP客户端基本操作1. 连接FTP服务器首先,我们需要连接到FTP服务器。以下代码示范了如何建立连接并登陆到FTP服务器:登录后复制import org.apache.commons.net.ftp.FTP;import org.apache.commons.net.ftp.FTPClient;import java.io.IOException;public class FTPExample {private FTPClient ftpClient;public void connect(String server, int port, String user, String password) throws IOException {ftpClient = new FTPClient();ftpClient.connect(server, port);ftpClient.login(user, password);ftpClient.enterLocalPassiveMode(); // 被动模式ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // 设置二进制文件类型}}2. 文件上传文件上传是FTP操作中最常见的需求之一。我们可以使用storeFile方法将文件上传到服务器。以下是上传文件的代码示例:登录后复制import java.io.FileInputStream;import java.io.IOException;public void uploadFile(String localFilePath, String remoteFilePath) throws IOException {try (FileInputStream inputStream = new FileInputStream(localFilePath)) {boolean done = ftpClient.storeFile(remoteFilePath, inputStream);if (done) {System.out.println("File uploaded successfully.");} else {System.out.println("File upload failed.");}}}3. 文件下载与文件上传类似,下载文件可以使用retrieveFile方法。以下是下载文件的代码示例:登录后复制import java.io.FileOutputStream;public void downloadFile(String remoteFilePath, String localFilePath) throws IOException {try (FileOutputStream outputStream = new FileOutputStream(localFilePath)) {boolean done = ftpClient.retrieveFile(remoteFilePath, outputStream);if (done) {System.out.println("File downloaded successfully.");} else {System.out.println("File download failed.");}}}4. 文件删除删除文件可以通过deleteFile方法来实现。下面的示例代码演示了如何删除FTP服务器上的文件:登录后复制public void deleteFile(String remoteFilePath) throws IOException {boolean done = ftpClient.deleteFile(remoteFilePath);if (done) {System.out.println("File deleted successfully.");} else {System.out.println("File deletion failed.");}}完整示例以下是一个完整的Java FTP客户端示例,组合了上述所有操作:登录后复制public class FTPClientExample {private FTPClient ftpClient;public FTPClientExample() {ftpClient = new FTPClient();}public void connect(String server, int port, String user, String password) throws IOException {ftpClient.connect(server, port);ftpClient.login(user, password);ftpClient.enterLocalPassiveMode();ftpClient.setFileType(FTP.BINARY_FILE_TYPE);}public void uploadFile(String localFilePath, String remoteFilePath) throws IOException {try (FileInputStream inputStream = new FileInputStream(localFilePath)) {boolean done = ftpClient.storeFile(remoteFilePath, inputStream);if (done) {System.out.println("File uploaded successfully.");}}}public void downloadFile(String remoteFilePath, String localFilePath) throws IOException {try (FileOutputStream outputStream = new FileOutputStream(localFilePath)) {boolean done = ftpClient.retrieveFile(remoteFilePath, outputStream);if (done) {System.out.println("File downloaded successfully.");}}}public void deleteFile(String remoteFilePath) throws IOException {boolean done = ftpClient.deleteFile(remoteFilePath);if (done) {System.out.println("File deleted successfully.");}}public void disconnect() throws IOException {if (ftpClient.isConnected()) {ftpClient.logout();ftpClient.disconnect();}}}状态图示例我们可以使用状态图来描述FTP操作的状态变化。以下是一个简单的状态图示例,描述了文件上传、下载和删除的过程:DisconnectedConnectingConnectedUploadingUploadCompleteDownloadingDownloadCompleteDeletingDeleteCompleteDisconnecting数据统计在实际应用中,我们可能需要监控不同操作的状态,下面是用饼状图示例展示的一些简单统计数据。例如,统计在某一时间段内的文件操作:FTP 文件操作统计上传下载删除结尾通过本系列的示例代码,您可以在Java中轻松实现FTP文件的上传、下载和删除操作。在构建需要文件传输功能的应用时,掌握FTP的基本操作是非常重要的。这不仅可以帮助提高应用的处理效率,也能提升用户体验。原文链接:https://blog.51cto.com/u_16175522/11615875
  • [技术干货] Java实现FTP客户端的简单功能
    概要         在实现网络编程时,需要了解很多协议,FTP协议就是很重要的一个,这篇文章主要写对于Java在基于FTP协议实现网络编程时的一些简单操作即工具类的一些简单应用方式,例如,文件的上传下载,寻找目录和创建等。  实现过程(操作代码和相关解释)     首先你要知道ftp的ip,路径,端口,有操作权限的账号和密码  导入jar包 导入commons-net  下载commons-net https://mvnrepository.com/artifact/commons-net/commons-net/3.6 或者  <dependency>     <groupId>commons-net</groupId>     <artifactId>commons-net</artifactId>     <version>3.6</version> </dependency> 连接FTP服务器         String ip = "192.168.59.215";         int port = 21;                 String userName = "admin";         String passWord = "123456";                  //    创建基于FTP协议访问文件客户端的对象         FTPClient client = new FTPClient();         try {             client.connect(ip,21);             client.login(userName,passWord);         } catch (SocketException e1) {             e1.printStackTrace();         } catch (IOException e1) {             e1.printStackTrace();         } 获取当前文件夹目录                          //  设置日期格式             SimpleDateFormat fmt = new SimpleDateFormat("yyyy年MM月dd日");             //    获取当前文件夹目录中的所有文件             FTPFile[] ftpFileList = client.listFiles();             for (FTPFile ftpFile : ftpFileList) {                 //    文件名称中包含中文乱码                 String fileName = ftpFile.getName();             /**              * FTP协议里面,规定文件名编码为iso-8859-1              * gb2312--编码集              **/                 fileName = new String(fileName.getBytes("iso-8859-1"),"gb2312");                 System.out.println("文件名称:"+fileName);                                  Date createDate = ftpFile.getTimestamp().getTime();                 System.out.println("文件创建时间:"+fmt.format(createDate));                                  System.out.println("文件大小:"+ftpFile.getSize());             }  查找和创建文件夹             String fileName = "FJQ_shuaizhao";             //  创建并切换至个人             Boolean ischange = ftpClient.changeWorkingDirectory(fileName);                          //    判断文件存在与否             if (!ischange) {                 //    创建文件夹                 ftpClient.makeDirectory(fileName);                                  //    切换文件夹                 ftpClient.changeWorkingDirectory(fileName);             }else{                 ftpClient.changeWorkingDirectory(fileName);             } 文件上传 try(FileInputStream in = new  FileInputStream("本地文件上传路径")) {             //  文件上传             ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                          //    存文件             ftpClient.storeFile(System.currentTimeMillis()+".jpg", in); } 文件下载 try(FileOutputStream out = new FileOutputStream("本地文件保存路径"))  {     //    下载指定格式文件-二进制格式BINARY_FILE_TYPE     ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);                      //    下载文件     Boolean isRetrieve = ftpClient.retrieveFile("fjq.jpg", out);                      System.out.println("下载成功?"+isRetrieve);                  } catch (IOException e) {     e.printStackTrace(); }    finally {     try {         ftpClient.disconnect();     } catch (IOException e) {         e.printStackTrace();     } }   关闭连接 try {     ftpClient.disconnect(); } catch (IOException e) {     e.printStackTrace(); } 技术名词解释 FTP(File Transfer Protocol):是应用层的一个文件传输协议 。主要作用是在服务器和客户端之间实现文件的传输和共享。FTP协议运行在TCP连接上,保证了文件传输的可靠性。在传输时,传输双方的操作系统、磁盘文件系统类型可以不同。  FTP协议:允许TCP/IP网络上的两台计算机之间进行文件传输。而FTP服务是基于FTP协议的文件传输服务。工作时,一台计算机上运行FTP客户端应用程序,另一台计算机上需要运行FTP服务器端程序。只有拥有了FTP服务,客户端才能进行文件传输。 小结 基于Java和FTP的网络编程,一些简单的操作,对于初学者来说有一定帮助。 ————————————————                  原文链接:https://blog.csdn.net/q_qwp_p/article/details/131612866 
  • [技术干货] Java的FTP协议级客户端实现详解
    清华大学出版社《Java程序员,上班那点事儿》作者:钟声——第10章《高手有多高菜鸟有多菜》部分节选。        大家也许都用过FTP上传下载工具,比如“LeapFTP”这个工具是一个很方便的FTP服务器上传下载工具,如图所示。这个工具很方便,输入用户名密码以后,就可以看到FTP服务器端的文件列表,便于进行上传与下载操作。         你是否试过自己用Java编写一个FTP的文件上传与下载应用程序?         Java也可以开发出这样的程序来,并不复杂,我们先看看,利用Java的FTP类工具包制作FTP应用程序的开发是怎么做的,请看如下程序: import sun.net.*; import sun.net.ftp.*; public class FTP {         public static void main(String[] args) {               String server="192.168.0.12";   //输入的FTP服务器的IP地址         String user="useway";            //登录FTP服务器的用户名         String password=" !@#$%abce";   //登录FTP服务器的用户名的口令         String path="/home/useway";      //FTP服务器上的路径                 try {                     FtpClient ftpClient=new FtpClient();  //创建FtpClient对象           ftpClient.openServer(server);        //连接FTP服务器           ftpClient.login(user, password);     //登录FTP服务器                     if (path.length()!=0) ftpClient.cd(path);                     TelnetInputStream is=ftpClient.list();           int c;           while ((c=is.read())!=-1) {              System.out.print((char)c);           }           is.close();           ftpClient.closeServer();//退出FTP服务器         }         catch(Exception ex){         }      } }         如果你感兴趣的话,可以自己写一个这个程序,当本程序运行以后,我们看到如图所示的情况,列出了服务器端程序的目录内容。         这个程序是一个简单的得到FTP服务器端文件列表的程序,但不要误会,这个程序可称不上“网络应用层协议”程序的开发!         这个程序仅仅是利用“sun.net.*;”和“sun.net.ftp.*;”中的相关类进行的对FTP端的操作的,我们根本没有利用Java的Socket的在网络层面向FTP服务器端发送任何请求,而是通过Java提供的工具包,向服务器端发送的链接请求。         利用Java的FTP包来链接FTP服务器的好处在于我们不需要关心网络层面发送数据的具体细节,而只要调用相应的方法就行了。利用Java的FTP包来链接FTP服务器的缺点是使开发者不知道应用层协议收发的来龙去脉,搞不清楚其中原理,对底层数据的把握程度非常弱。          讲到这里有程序员会问:“那么FTP在网络层面和PC与服务器间是如何交互的呢?”,好,就给大家列出FTP协议交互过程。 请看下面的一段FTP协议交互的例子: FTP服务器: 220 (vsFTPd 2.0.1) FTP客户端: USER useway FTP服务器: 331 Please specify the password. FTP客户端: PASS !@#$%abce FTP服务器: 230 Login successful. FTP客户端: CWD /home/useway FTP服务器: 250 Directory successfully changed. FTP客户端: EPSV ALL FTP服务器: 200 EPSV ALL ok. FTP客户端: EPSV FTP服务器: 229 Entering Extended Passive Mode (|||62501|) FTP客户端: LIST FTP服务器: 150 Here comes the directory listing. FTP服务器: 226 Directory send OK. FTP客户端: QUIT FTP服务器: 221 Goodbye.         以上这段文字其实就是FTP服务器和FTP客户端之间相互交互的过程,它们之间传递信息的协议是TCP协议,互相发送的内容就是上面这段文字所写的内容。         我们下面逐步的去解释每一句话的含义: FTP服务器: 220 (vsFTPd 2.0.1)                                         |说明:链接成功 FTP客户端: USER useway                                                 |说明:输入用户名 FTP服务器: 331 Please specify the password.                |说明:请输入密码 FTP客户端: PASS !@#$%abce                                         |说明:输入密码 FTP服务器: 230 Login successful.                                   |说明:登录成功 FTP客户端: CWD /home/useway                                    |说明:切换目录 FTP服务器: 250 Directory successfully changed.          |说明:目录切换成功 FTP客户端: EPSV ALL                                                      |说明:为EPSV被动链接方式 FTP服务器: 200 EPSV ALL ok.                                        |说明:OK FTP客户端: EPSV                                                               |说明:链接 FTP服务器: 229 Entering Extended Passive Mode (|||62501|)  |说明:被动链接端口为62501 FTP客户端: LIST                                                              |说明:执行LIST显示文件列表 FTP服务器: 150 Here comes the directory listing.      |说明:列表从62501端口被发送 FTP服务器: 226 Directory send OK.                             |说明:发送完成 FTP客户端: QUIT                                                            |说明:退出FTP FTP服务器: 221 Goodbye.                                              |说明:再见         有了以上文字的内容,我们不需要任何工具也可以得到FTP文件列表了,不信你跟着我一起做一遍。         第一步:首先打开CMD进入DOS命令行模式,键入: telnet 192.168.0.1 21[回车]         说明:Telnet 到Ftp服务器的21端口。         执行该命令        大家发现什么问题了吗?         提示的内容正好就是,我们上面一段文字的第一句:220 (vsFTPd 2.0.1),这说明FTP服务器已经接受了我们的链接,已经可以进行下一步操作了。         第二步:将后面的一系列发送内容逐个键入: USER useway[回车] PASS !@#$%abce[回车] CWD /home/useway[回车] EPSV ALL[回车] EPSV[回车]         好,这回FTP服务器给出了一系列的回应,在最后给出了一个新的端口号"58143"。         第三步:再打开一个新的CMD窗口,键入: telnet 192.168.0.1 58143[ 回车 ]         注意,这次Telnet请求链接服务器的端口号是“58143”,是FTP服务器给我们的一个链接端口。链接后,窗口为空白没有任何提示,如图所示。         第四步:回到第一个CMD窗口,键入: LIST[ 回车 ]         第五步:这时候第二CMD窗口就接收到了文件列表:         第二个窗口接收到了文件列表如图所示。           第六步:退出操作  QUIT[ 回车 ]           执行完成后,失去与主机的链接。        大家看到了吧,FTP协议就是这样的一个交互过程,利用系统自带的Telnet工具也可以完成FTP的这些基本命令的操作。如果,你想用Java的Socket完成以上操作就只需要一步一步的按照上述内容发送字符串给FTP服务器端就行了。        我们下面也给出例子代码:   import java.io.InputStream;   import java.io.OutputStream;   import java.net.Socket;   public class FTPClient{     public static void main(String[] args) throws Exception{           Socket socket = new Socket("192.168.0.1",21);           InputStream is = socket.getInputStream();           OutputStream os = socket.getOutputStream();         //接收初始链接信息         byte[] buffer = new byte[100];         int length = is.read(buffer);         String s = new String(buffer, 0, length);         System.out.println(s);         //发送用户名         String str = "USER useway\n";         os.write(str.getBytes());         //得到返回值         length = is.read(buffer);         s = new String(buffer, 0, length);         System.out.println(s);                //发送密码         str = "PASS !@#$%abcd\n";         os.write(str.getBytes()); //得到返回值         length = is.read(buffer);         s = new String(buffer, 0, length);         System.out.println(s);         //发送切换文件夹指令         str = "CWD /home/useway\n";         os.write(str.getBytes());         //得到返回值         length = is.read(buffer);         s = new String(buffer, 0, length);         System.out.println(s);         //设置模式         str = "EPSV ALL\n";         os.write(str.getBytes());         //得到返回值         length = is.read(buffer);         s = new String(buffer, 0, length);         System.out.println(s);                //得到被动监听信息         str = "EPSV\n";         os.write(str.getBytes());         //得到返回值         length = is.read(buffer);         s = new String(buffer, 0, length);         System.out.println(s);         //取得FTP被动监听的端口号         String portlist=s.substring(s.indexOf("(|||")+4,s.indexOf("|)"));         System.out.println(portlist);         //实例化ShowList线程类,链接FTP被动监听端口号         ShowList sl=new ShowList();         sl.port=Integer.parseInt(portlist);         sl.start();         //执行LIST命令         str = "LIST\n";         os.write(str.getBytes());         //得到返回值         length = is.read(buffer);         s = new String(buffer, 0, length);         System.out.println(s);         //关闭链接         is.close();         os.close();         socket.close();     } } //得到被动链接信息类,这个类是多线程的 class ShowList extends Thread{  public int port=0;     public void run(){      try{          Socket socket = new Socket("192.168.0.1",this.port);          InputStream is = socket.getInputStream();          OutputStream os = socket.getOutputStream();          byte[] buffer = new byte[10000];          int length = is.read(buffer);          String s = new String(buffer, 0, length);          System.out.println(s);             //关闭链接             is.close();             os.close();             socket.close();      }      catch(Exception ex){      }     } }           该程序运行后得到的运行结果如图所示,基本上和上面的运行效果相同吧,底层又如何,无非是将那些封装好的方法解开来运行,只要了解到了它们运行的规则,我们自己可以开发出一样的程序来 ————————————————         原文链接:https://blog.csdn.net/weixin_33854644/article/details/85128720 
  • [技术干货] Java 连接操作FTP与SFTP 详细指南
    引言 在Java开发中,文件传输协议(FTP)和安全文件传输协议(SFTP)是处理文件传输的两种常见方式。FTP是标准的网络文件传输协议,而SFTP则在FTP基础上增加了安全层(SSH),提供了更加安全的文件传输方式。本文将详细介绍如何在Java中实现与FTP和SFTP服务器的连接,并深入讲解各种函数的用法,以及如何进行二次封装,以提升代码的可复用性和可维护性。  文章目录 引言 一、Java连接FTP的实现 1. 配置与依赖 2. 基本连接与文件操作 3. 常用函数详解 4. 二次封装的工具类 二、Java连接SFTP的实现 1. 配置与依赖 2. 基本连接与文件操作 3.常用函数详解 4. 二次封装的工具类 FTP工具类的级联创建目录封装 SFTP工具类的级联创建目录封装 使用注意事项 三、Java连接FTP与SFTP的对比 1. 安全性 2. 性能 3. 支持与兼容性 四、总结与个人看法 一、Java连接FTP的实现 FTP协议(File Transfer Protocol)是一种用于在网络中传输文件的标准协议。Java提供了丰富的库来实现FTP连接,其中最常用的是Apache Commons Net库。该库提供了一个FTPClient类,简化了与FTP服务器的交互。  1. 配置与依赖 在使用FTPClient类之前,首先需要在项目中引入Apache Commons Net库的依赖。在Maven项目中,可以通过在pom.xml文件中添加以下依赖项来实现:  <dependency>     <groupId>commons-net</groupId>     <artifactId>commons-net</artifactId>     <version>3.8.0</version> </dependency> 2. 基本连接与文件操作 使用FTPClient连接到FTP服务器的基本流程如下:  创建FTPClient对象。 使用connect方法连接到FTP服务器。 使用login方法进行身份验证。 使用相应的方法执行文件操作,如上传、下载、删除文件等。 使用logout方法注销。 最后关闭连接。 以下是一个简单的示例,展示了如何连接到FTP服务器并上传文件:  import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream;  public class FTPExample {     public static void main(String[] args) {         FTPClient ftpClient = new FTPClient();         try {             // 连接到FTP服务器             ftpClient.connect("ftp.example.com", 21);             // 登录到FTP服务器             ftpClient.login("username", "password");              // 设置文件传输类型为二进制文件             ftpClient.setFileType(FTP.BINARY_FILE_TYPE);              // 上传文件             try (InputStream inputStream = new FileInputStream("local/file/path")) {                 boolean done = ftpClient.storeFile("/remote/file/path", inputStream);                 if (done) {                     System.out.println("The file is uploaded successfully.");                 }             }              // 注销并关闭连接             ftpClient.logout();         } catch (IOException ex) {             ex.printStackTrace();         } finally {             try {                 if (ftpClient.isConnected()) {                     ftpClient.disconnect();                 }             } catch (IOException ex) {                 ex.printStackTrace();             }         }     } }  在这个示例中,ftpClient.connect用于连接FTP服务器,ftpClient.login进行身份验证,ftpClient.storeFile方法用于上传文件。需要注意的是,FTP传输的默认模式是ASCII,因此在上传二进制文件时,需要调用setFileType(FTP.BINARY_FILE_TYPE)来设置传输模式。  3. 常用函数详解 FTPClient类提供了丰富的功能,以下是一些常用的FTP操作函数:  连接与登录  connect(String hostname, int port):连接到指定的FTP服务器和端口。 login(String username, String password):使用用户名和密码登录FTP服务器。 logout():注销当前用户。 文件操作  storeFile(String remote, InputStream local):将本地文件上传到服务器。 retrieveFile(String remote, OutputStream local):从服务器下载文件到本地。 deleteFile(String pathname):删除服务器上的文件。 listFiles(String pathname):列出指定目录下的文件和子目录。 目录操作  makeDirectory(String pathname):在服务器上创建一个新目录。 removeDirectory(String pathname):删除服务器上的目录。 changeWorkingDirectory(String pathname):改变当前工作目录。 printWorkingDirectory():获取当前工作目录的路径。 连接管理  disconnect():断开与FTP服务器的连接。 isConnected():检查是否已连接到服务器。 4. 二次封装的工具类 为了提高代码的可复用性,可以将上述FTP操作封装到一个工具类中。这不仅简化了使用,还可以集中处理异常和日志记录等通用逻辑。  import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient;  import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;  public class FTPUtil {     private FTPClient ftpClient;      public FTPUtil(String host, int port, String username, String password) throws IOException {         ftpClient = new FTPClient();         ftpClient.connect(host, port);         ftpClient.login(username, password);         ftpClient.setFileType(FTP.BINARY_FILE_TYPE);     }      public boolean uploadFile(String remoteFilePath, InputStream inputStream) throws IOException {         try {             return ftpClient.storeFile(remoteFilePath, inputStream);         } finally {             inputStream.close();         }     }      public boolean downloadFile(String remoteFilePath, OutputStream outputStream) throws IOException {         try {             return ftpClient.retrieveFile(remoteFilePath, outputStream);         } finally {             outputStream.close();         }     }      public boolean deleteFile(String filePath) throws IOException {         return ftpClient.deleteFile(filePath);     }      public void logoutAndDisconnect() throws IOException {         if (ftpClient.isConnected()) {             ftpClient.logout();             ftpClient.disconnect();         }     } }  这个FTPUtil类提供了基本的上传、下载、删除文件功能,并封装了连接和注销逻辑,简化了FTP操作的使用。  二、Java连接SFTP的实现 SFTP(SSH File Transfer Protocol)是基于SSH协议的文件传输协议,提供了更加安全的文件传输方式。Java中实现SFTP连接通常使用JSch库,这个库由JCraft开发,用于SSH2的Java实现。  1. 配置与依赖 在使用JSch库之前,同样需要在项目中引入依赖。在Maven项目中,添加以下依赖项:  <dependency>     <groupId>com.jcraft</groupId>     <artifactId>jsch</artifactId>     <version>0.1.55</version> </dependency> 2. 基本连接与文件操作 使用JSch连接到SFTP服务器的基本流程如下:  创建JSch对象。 使用getSession方法创建会话并进行身份验证。 使用connect方法连接到SFTP服务器。 使用ChannelSftp对象执行文件操作。 关闭连接。 以下是一个简单的示例,展示了如何连接到SFTP服务器并上传文件:  import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session;  import java.io.FileInputStream; import java.io.InputStream; import java.util.Properties;  public class SFTPExample {     public static void main(String[] args) {         String SFTPHOST = "sftp.example.com";         int SFTPPORT = 22;         String SFTPUSER = "username";         String SFTPPASS = "password";         String SFTPWORKINGDIR = "/remote/path/";          Session session = null;         ChannelSftp channelSftp = null;          try {             JSch jsch = new JSch();             session = jsch.getSession(SFTPUSER, SFTPHOST, SFTPPORT);             session.setPassword(SFTPPASS);              // 配置StrictHostKeyChecking属性             Properties config = new Properties();             config.put("StrictHostKeyChecking", "no");             session.setConfig(config);              session.connect();              channelSftp = (ChannelSftp) session.openChannel("sftp");             channelSftp.connect();              // 上传文件             try (InputStream inputStream = new FileInputStream("local/file/path")) {                 channelSftp.put(inputStream, SFTPWORKINGDIR + "fileName");             }          } catch (Exception ex) {             ex.printStackTrace();         } finally {             if (channelSftp != null) {                 channelSftp.disconnect();             }             if (session != null) {                 session.disconnect();             }         }     } }  在这个示例中,JSch类用于管理SSH连接和会话,ChannelSftp类用于执行具体的SFTP操作。需要注意的是,在实际开发中,StrictHostKeyChecking属性建议设置为yes以增强安全性。  3.常用函数详解 ChannelSftp类提供了丰富的功能,用于实现SFTP操作。以下是一些常用的SFTP操作函数:  连接与会话管理  connect():连接到SFTP服务器。 disconnect():断开与SFTP服务器的连接。 getSession():获取当前连接的会话(Session)。 文件操作  put(InputStream src, String dst):将本地文件上传到SFTP服务器的目标路径。 get(String src, OutputStream dst):从SFTP服务器下载文件到本地。 rm(String path):删除服务器上的文件。 ls(String path):列出指定目录下的文件和子目录。 目录操作  cd(String path):切换到指定目录。 mkdir(String path):在SFTP服务器上创建新目录。 rmdir(String path):删除SFTP服务器上的目录。 权限与属性管理  chmod(int permissions, String path):修改文件或目录的权限。 chown(int uid, String path):修改文件或目录的所有者。 chgrp(int gid, String path):修改文件或目录的所属组。 stat(String path):获取文件或目录的属性。 4. 二次封装的工具类 为了更好地管理SFTP连接和操作,实现更强大的功能,如级联创建目录和其他操作,我们可以将常用功能封装到一个工具类中,提高代码的可读性和可维护性的同时,确保可以顺利处理复杂的文件系统操作。比如级联创建目录,在级联创建目录时,我们需要逐级检查每个目录是否存在,如果不存在则创建它。这在FTP和SFTP操作中都非常常见。  FTP工具类的级联创建目录封装 import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient;  import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;  public class FTPUtil {     private FTPClient ftpClient;      public FTPUtil(String host, int port, String username, String password) throws IOException {         ftpClient = new FTPClient();         ftpClient.connect(host, port);         ftpClient.login(username, password);         ftpClient.setFileType(FTP.BINARY_FILE_TYPE);     }      // 上传文件     public boolean uploadFile(String remoteFilePath, InputStream inputStream) throws IOException {         try {             return ftpClient.storeFile(remoteFilePath, inputStream);         } finally {             inputStream.close();         }     }      // 下载文件     public boolean downloadFile(String remoteFilePath, OutputStream outputStream) throws IOException {         try {             return ftpClient.retrieveFile(remoteFilePath, outputStream);         } finally {             outputStream.close();         }     }      // 删除文件     public boolean deleteFile(String filePath) throws IOException {         return ftpClient.deleteFile(filePath);     }      // 级联创建目录     public boolean createDirectories(String remoteDirPath) throws IOException {         String[] directories = remoteDirPath.split("/");         String currentDir = "";         boolean dirExists = false;          for (String dir : directories) {             if (dir.isEmpty()) continue;             currentDir += "/" + dir;             dirExists = ftpClient.changeWorkingDirectory(currentDir);             if (!dirExists) {                 if (!ftpClient.makeDirectory(currentDir)) {                     throw new IOException("Failed to create directory: " + currentDir);                 }                 ftpClient.changeWorkingDirectory(currentDir);             }         }         return true;     }      // 删除目录(仅删除空目录)     public boolean deleteDirectory(String directoryPath) throws IOException {         return ftpClient.removeDirectory(directoryPath);     }      // 递归删除目录(包括子文件和子目录)     public boolean deleteDirectoryRecursively(String remoteDirPath) throws IOException {         FTPFile[] files = ftpClient.listFiles(remoteDirPath);         for (FTPFile file : files) {             String filePath = remoteDirPath + "/" + file.getName();             if (file.isDirectory()) {                 deleteDirectoryRecursively(filePath);             } else {                 deleteFile(filePath);             }         }         return deleteDirectory(remoteDirPath);     }      // 退出并断开连接     public void logoutAndDisconnect() throws IOException {         if (ftpClient.isConnected()) {             ftpClient.logout();             ftpClient.disconnect();         }     } }  SFTP工具类的级联创建目录封装 import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session;  import java.io.InputStream; import java.io.OutputStream; import java.util.Properties; import java.util.Vector;  public class SFTPUtil {     private ChannelSftp channelSftp;     private Session session;      public SFTPUtil(String host, int port, String username, String password) throws Exception {         JSch jsch = new JSch();         session = jsch.getSession(username, host, port);         session.setPassword(password);          Properties config = new Properties();         config.put("StrictHostKeyChecking", "no");         session.setConfig(config);         session.connect();          channelSftp = (ChannelSftp) session.openChannel("sftp");         channelSftp.connect();     }      // 上传文件     public void uploadFile(String remoteDir, String remoteFileName, InputStream inputStream) throws Exception {         channelSftp.cd(remoteDir);         channelSftp.put(inputStream, remoteFileName);     }      // 下载文件     public void downloadFile(String remoteFilePath, OutputStream outputStream) throws Exception {         channelSftp.get(remoteFilePath, outputStream);     }      // 删除文件     public void deleteFile(String remoteFilePath) throws Exception {         channelSftp.rm(remoteFilePath);     }      // 级联创建目录     public void createDirectories(String remoteDirPath) throws Exception {         String[] directories = remoteDirPath.split("/");         String currentDir = "";         for (String dir : directories) {             if (dir.isEmpty()) continue;             currentDir += "/" + dir;             try {                 channelSftp.cd(currentDir);             } catch (Exception e) {                 channelSftp.mkdir(currentDir);                 channelSftp.cd(currentDir);             }         }     }      // 删除目录(仅删除空目录)     public void deleteDirectory(String remoteDir) throws Exception {         channelSftp.rmdir(remoteDir);     }      // 递归删除目录(包括子文件和子目录)     public void deleteDirectoryRecursively(String remoteDirPath) throws Exception {         Vector<ChannelSftp.LsEntry> files = channelSftp.ls(remoteDirPath);         for (ChannelSftp.LsEntry entry : files) {             if (!entry.getFilename().equals(".") && !entry.getFilename().equals("..")) {                 String filePath = remoteDirPath + "/" + entry.getFilename();                 if (entry.getAttrs().isDir()) {                     deleteDirectoryRecursively(filePath);                 } else {                     deleteFile(filePath);                 }             }         }         deleteDirectory(remoteDirPath);     }      // 退出并断开连接     public void logoutAndDisconnect() {         if (channelSftp != null) {             channelSftp.disconnect();         }         if (session != null) {             session.disconnect();         }     } }  使用注意事项 级联目录创建:在处理复杂路径时,必须确保各级目录按顺序被创建。如果操作失败,应当立即停止并报告错误。 递归删除:在删除目录时,递归删除可以确保目录及其所有内容都被彻底删除。然而,必须注意,这是一项危险操作,可能会导致数据丢失,因此在执行之前应该进行仔细确认。 异常处理:在目录操作过程中,由于路径错误、权限不足或网络问题可能导致异常,应确保这些异常被捕获并妥善处理,以避免影响系统的稳定性。 三、Java连接FTP与SFTP的对比 FTP和SFTP在实际项目中各有应用场景。FTP由于其简单性和广泛支持,适用于一些对安全性要求不高的场景;而SFTP由于具备更高的安全性和数据加密特性,更适合用于涉及敏感数据的场景。  1. 安全性 SFTP在安全性方面具有明显的优势。它通过SSH协议加密数据传输,避免了数据在传输过程中的被窃听和篡改风险。相比之下,FTP采用明文传输,容易受到中间人攻击。 2. 性能 FTP通常在速度上略优于SFTP,因为它省去了加密和解密过程。不过,在现代硬件环境下,这种性能差异通常可以忽略不计。 3. 支持与兼容性 FTP在许多系统中得到了广泛的支持,几乎所有的操作系统和许多文件管理工具都支持FTP。而SFTP的支持相对较少,但在涉及安全要求的项目中,SFTP是更合适的选择。 四、总结与个人看法 通过本文,我们深入探讨了如何在Java中实现FTP和SFTP连接,并详细介绍了相关的函数用法和二次封装工具类的实现。对于两者的应用场景和差异,我们也进行了对比分析。  个人看法:在选择FTP还是SFTP时,应该根据项目的具体需求进行权衡。如果安全性是重中之重,SFTP无疑是首选。此外,随着互联网安全要求的日益提高,FTP的应用场景可能会逐渐减少。因此,在新项目中,建议优先考虑SFTP作为文件传输的解决方案。当然,在某些内网环境或临时性项目中,FTP的简单性和较低的配置成本也使其成为一个不错的选择。在处理文件传输任务时,封装常用的操作功能,不仅可以提高代码的复用性,还能减少开发过程中的重复劳动,提升项目的开发效率。对于级联操作和递归操作,虽然实现起来相对复杂,但这些功能对于许多实际场景是必不可少的,因此在开发中应当积极采用这些封装技术,确保系统的健壮性和灵活性。  希望这篇随笔能够帮助你更好地理解和掌握Java中FTP和SFTP的实现方法,并为实际项目提供一些有价值的参考。如果你在开发过程中有任何问题或需要进一步探讨,欢迎随时交流。 ————————————————                 原文链接:https://blog.csdn.net/QWERTYwqj/article/details/141160425 
  • [技术干货] Java常用的设计模式和设计原则
    1、Java常用的设计模式 总体来说设计模式分为三大类,共23种:  (1)创建型模式,共五种: Factory(工厂模式:简单工厂模式和抽象工厂模式)、Factory Method(工厂方法模式)、Singleton(单例模式)、Builder(建造者模式)、Prototype(原始模型模式)。  (2)结构型模式,共七种: Adapter(适配器模式)、Decorator(装饰器模式)、Proxy(代理模式)、Facade(外观模式)、Bridge(桥梁模式)、Composite(组合模式)、Flyweight(享元模式)。  (3)行为型模式,共十一种: Strategy(策略模式)、Template Method(模板方法模式)、Observer(观察者模式)、Iterator(迭代子模式)、Chain Of Responsibility(责任链模式)、Command(命令模式)、Memento(备忘录模式)、State(状态模式)、Visitor(访问者模式)、Mediator(中介者模式)、Interpreter(解释器模式)。  (4)其实还有两类:并发型模式和线程池模式。  2、Java遵循的设计原则 总原则:开闭原则(Open Close Principle,OCP) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等。  (1)单一职责原则(Single Responsibility Principle,SRP) 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。  (2)里氏替换原则(Liskov Substitution Principle,LSP) 面向对象设计的基本原则之一。 里氏替换原则中说,任何父类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。  (3)依赖倒转原则(Dependence Inversion Principle,DIP) 这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。  (4)接口隔离原则(Interface Segregation Principle,ISP) 这个原则的意思是:每个接口中不应存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。  (5)迪米特原则(最少知道原则)(Law of Demeter,LOD或Least Knowledge Principle,LKP) 就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。  (6)合成复用原则(Composite Reuse Principle,CRP) 原则是尽量首先使用合成/聚合的方式,而不是使用继承。 ————————————————     原文链接:https://blog.csdn.net/jsc123581/article/details/81748124 
  • [技术干货] 面向对象设计模式:23种经典模式解析
    1.背景介绍 面向对象设计模式是一种软件设计的方法,它提供了一种抽象的方式来解决常见的软件设计问题。这种方法通过将问题分解为一组可重用的、可组合的对象来解决。这些对象可以表示实体、概念或抽象,并可以通过消息传递来交互。设计模式可以帮助程序员更快地开发高质量的软件,并提高代码的可读性、可维护性和可扩展性。  在本文中,我们将讨论23种经典的面向对象设计模式,并详细解释它们的定义、原理、优缺点和应用场景。这些设计模式可以分为三个类别:创建型模式、结构型模式和行为型模式。  2.核心概念与联系 2.1 创建型模式 创建型模式是一种用于创建对象的设计模式,它们提供了一种抽象的方式来创建对象,使得代码更加可维护和可扩展。创建型模式包括以下七种:  单例模式(Singleton) 工厂方法模式(Factory Method) 抽象工厂模式(Abstract Factory) 建造者模式(Builder) 原型模式(Prototype) 模板方法模式(Template Method) 代理模式(Proxy) 2.2 结构型模式 结构型模式是一种用于定义和组织类和对象的设计模式,它们提供了一种抽象的方式来组合类和对象,使得代码更加可维护和可扩展。结构型模式包括以下七种:  适配器模式(Adapter) 桥接模式(Bridge) 组合模式(Composite) 装饰模式(Decorator) 外观模式(Facade) 享元模式(Flyweight) 代理模式(Proxy) 2.3 行为型模式 行为型模式是一种用于定义对象之间的交互和行为的设计模式,它们提供了一种抽象的方式来描述对象之间的交互,使得代码更加可维护和可扩展。行为型模式包括以下十一种:  命令模式(Command) 策略模式(Strategy) 状态模式(State) 观察者模式(Observer) 中介模式(Mediator) 迭代子模式(Iterator) 访问者模式(Visitor) 备忘录模式(Memento) 责任链模式(Chain of Responsibility) 解释器模式(Interpreter) 3.核心算法原理和具体操作步骤以及数学模型公式详细讲解 在这个部分,我们将详细讲解每种设计模式的算法原理、具体操作步骤以及数学模型公式。由于篇幅限制,我们将只讨论其中的一部分设计模式。  3.1 单例模式 单例模式是一种创建型模式,它限制一个类只能有一个实例。这种模式通常用于管理全局资源,如数据库连接、文件输出等。  算法原理:单例模式使用一个静态变量来存储一个类的实例,并提供一个公共的静态方法来访问这个实例。当第一次访问这个方法时,它会创建一个新的实例,并将其存储在静态变量中。在后续的访问中,它会返回已存储的实例。  具体操作步骤:  在类中声明一个静态变量来存储实例。 在类中声明一个私有的构造函数,以防止外部创建新的实例。 在类中声明一个公共的静态方法,用于访问实例。 在静态方法中,检查静态变量是否已经存在实例。如果不存在,创建一个新的实例并将其存储在静态变量中。如果存在,返回已存储的实例。 数学模型公式:  $$ Singleton = {S \mid \forall i,j \in S, i \neq j \Rightarrow si = sj} $$  其中,$S$ 是单例集合,$si$ 和 $sj$ 是 $S$ 中的不同元素。  3.2 工厂方法模式 工厂方法模式是一种创建型模式,它提供了一个用于创建对象的接口,但让子类决定实例化哪个具体的类。这种模式通常用于创建不同类型的对象,而不需要知道它们的具体类。  算法原理:工厂方法模式定义了一个接口用于创建对象,并将实例化过程委托给子类。子类实现这个接口,并在其中调用具体的构造函数来创建对象。  具体操作步骤:  创建一个抽象的工厂类,包含一个用于创建对象的接口。 创建一个或多个具体的工厂类,继承抽象工厂类,并实现接口。 在具体工厂类中,实现创建对象的方法,调用具体的构造函数。 使用具体工厂类来创建对象。 数学模型公式:  $$ FactoryMethod = {F \mid \forall fi \in F, fi(c) = c_i} $$  其中,$F$ 是工厂方法集合,$fi$ 是 $F$ 中的一个方法,$c$ 是类的集合,$ci$ 是 $c$ 中的一个具体类。  4.具体代码实例和详细解释说明 在这个部分,我们将通过一个具体的代码实例来演示单例模式和工厂方法模式的使用。  4.1 单例模式 ```python class Singleton: _instance = None  def __new__(cls, *args, **kwargs):     if not cls._instance:         cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)     return cls._instance   def __init__(self):     self.value = 42 s1 = Singleton() s2 = Singleton() print(s1 is s2) # True ```  在这个例子中,我们定义了一个 Singleton 类,它使用一个静态变量 _instance 来存储实例。在 __new__ 方法中,我们检查静态变量是否已经存在实例,如果不存在,创建一个新的实例并将其存储在静态变量中。如果存在,返回已存储的实例。  4.2 工厂方法模式 ```python from abc import ABC, abstractmethod  class Animal(ABC): @abstractmethod def speak(self): pass  class Dog(Animal): def speak(self): return "Woof!"  class Cat(Animal): def speak(self): return "Meow!"  class AnimalFactory: @staticmethod def createanimal(animaltype): if animaltype == "Dog": return Dog() elif animaltype == "Cat": return Cat() else: raise ValueError("Invalid animal type")  dog = AnimalFactory.createanimal("Dog") cat = AnimalFactory.createanimal("Cat") print(dog.speak()) # Woof! print(cat.speak()) # Meow! ```  在这个例子中,我们定义了一个 Animal 抽象类和两个具体的子类 Dog 和 Cat。这些类实现了一个 speak 方法,用于表示不同的动物发出的声音。我们还定义了一个 AnimalFactory 类,它包含一个静态方法 create_animal,用于根据传入的字符串创建不同类型的动物。这个方法使用了工厂方法模式,因为它提供了一个用于创建对象的接口,但让子类决定实例化哪个具体的类。  5.未来发展趋势与挑战 面向对象设计模式已经成为软件开发的一部分,它们已经广泛应用于各种类型的软件系统。未来,我们可以预见以下几个方面的发展趋势和挑战:  随着软件系统的复杂性和规模的增加,设计模式将更加重要,因为它们可以帮助我们更好地组织和管理代码。 随着编程语言和框架的发展,设计模式将更加灵活和可扩展,以适应不同的应用场景。 随着人工智能和机器学习的发展,设计模式将更加关注如何构建可扩展、可维护的机器学习模型和系统。 随着云计算和分布式系统的发展,设计模式将更加关注如何构建高性能、高可用性的分布式系统。 随着安全性和隐私问题的加剧,设计模式将更加关注如何构建安全、隐私保护的软件系统。 6.附录常见问题与解答 在这个部分,我们将回答一些常见问题:  Q:设计模式是否适用于所有的软件项目?  A:不适用。设计模式是一种软件设计的方法,它们可以帮助程序员更快地开发高质量的软件,并提高代码的可读性、可维护性和可扩展性。但是,在某些情况下,使用设计模式可能会导致代码过于复杂和难以理解。因此,在选择使用设计模式时,需要权衡其优缺点。  Q:设计模式是否会限制我的创造力?  A:不会。设计模式是一种抽象的方式来解决常见的软件设计问题,它们可以帮助程序员更快地开发高质量的软件,并提高代码的可读性、可维护性和可扩展性。使用设计模式并不意味着限制你的创造力,而是提供了一种更高效、更结构化的方式来构建软件系统。  Q:如何选择适合的设计模式?  A:选择适合的设计模式需要考虑以下几个因素:  问题的具体性:设计模式应该与问题紧密相关,不应该过度设计。 问题的复杂性:更复杂的问题可能需要多个设计模式来解决。 设计模式的可读性和可维护性:选择易于理解和维护的设计模式。 设计模式的适用性:确保选定的设计模式适用于当前的技术栈和团队能力。 Q:设计模式是否会导致代码冗余?  A:可能。使用设计模式可能会导致代码冗余,因为设计模式通常包括一些重复的代码。但是,这种冗余通常是可以接受的,因为它可以提高代码的可读性、可维护性和可扩展性。在选择使用设计模式时,需要权衡其优缺点。 ————————————————                       原文链接:https://blog.csdn.net/universsky2015/article/details/137313201 
  • [技术干货] 面向对象的23种设计模式
    设计原则:高内聚低耦合,开闭原则。 两大基础设计原则 在说面向对象设计的六大原则之前,我们先来说下程序设计的原则:模块内高内聚,模块间低耦合。我们在面向对象时只需把类看成模块,那么就容易理解封装等了。  说是七大原则,这里我先提出来一个:对扩展开放,对修改关闭。 为啥这么说,因为我们都知道软件是要改的。对扩展开放保证了可以增加功能,像泛型啦这些。对修改关闭保证了像前的兼容性,jdk7兼容jdk6这样。所以开闭原则围绕软件的整个生命周期。  从基础原则出发,产生六个具体的原则: 1.单一职责(一个方法或一个类只做一件事,为了模块内高内聚) 2.迪米特法则(也叫最少知道原则,为了模块间低耦合) 3.里氏替换(就是继承原则,子类可以无缝替代父类。很好的符合了开闭原则) 4.依赖倒置(类之间的依赖通过接口实现,低耦合的同时对扩展开放) 5.接口隔离(即把单个复杂接口拆分为多个独立接口,与上条共同实现面向接口编程) 6.合成复用原则(即尽量使用合成/聚合的方式,而不是使用继承。主要为了防止继承滥用而导致的类之间耦合严重。记住只有符合继承原则时才用继承)  设计模式 我觉得程序员最好的沟通方式是代码,所以每个设计模式都是一个例子。所有例子都很方便,可以复制直接运行。因为对java熟悉,所以下面设计模式例子都是用java语言来实现的。  创建型模式(IOC:控制反转,就是创建分离的集大成) 1.Singleton:单例模式(全局只要一个实例) 2.Prototype:原型模式(通过拷贝原对象创建新对象) 3.Factory Method:工厂方法模式(对象创建可控,隐藏具体类名等实现解耦) 4.Abstract Factory:抽象工厂模式(解决对象与其属性匹配的工厂模式) 5.Builder:建造者模式(封装降低耦合,生成的对象与构造顺序无关) 创建型模式的五种有各自的使用环境,单例和原型比较简单就不说了,工厂方法模式和建造者模式,都是封装和降低耦合有啥不同呢,其实工厂方法关注的是一个类有多个子类的对象创建(汽车类的各种品牌),而建造者模式关注的是属性较多的对象创建(能达到过程无关)。而抽象工厂模式关注的是对象和属性及属性与属性的匹配关系(如奥迪汽车与其发动机及空调的匹配)。  结构型模式(对象的组成以及对象之间的依赖关系) 1.Adapter:适配器模式(适配不同接口和类,一般解决历史遗留问题) 2.Decorator:装饰器模式(比继承更灵活,可用排列组合形成多种扩展类) 3.Proxy:代理模式(可以给类的每个方法增加逻辑,如身份验证) 4.Facade:外观模式(对模块或产品的封装,降低耦合) 5.Bridge:桥接模式(就是接口模式,抽象与实现分离) 6.Plyweight:享元模式(相同对象的重用) 7.Composite:组合模式(整体和部分相同时,如文件夹包含文件夹) 我们可以看到适配器模式、装饰器模式、代理模式都可以用包装对象来实现(把对象作为一个属性放在用的对象里),所以模式关注的并不是实现,而是解决的问题。模式更多体现的是类与类之间的逻辑关系,比如代理模式和装饰器模式很像。但从字面就知道,代理是访问不了实际工作对象的,这是他们的区别。  行为型模式(即方法及其调用关系) 1.Strategy:策略模式(提供功能不同实现方式,且实现可选) 2.Template Method:模板方法模式(相同流程用一个模板方法) 3.Observer:观察者模式(用订阅-发布实现的被观察者变化时回调) 4.Iterator:迭代器模式(一种内部实现无关的集合遍历模式) 5.Chain of Responsibility:责任链模式(事件处理的分层结构产生的责任链条) 6.Command:命令模式(将命令者与被命令者分离) 7.Memento:备忘录模式(需要撤销与恢复操作时使用) 8.State:状态模式 (当对象两种状态差别很大时使用) 9.Visitor:访问者模式 (当对同一对象有多种不同操作时使用) 10.Mediator:中介者模式(以中介为中心,将网状关系变成星型关系) 11.Interpreter:解释器模式(常用于纯文本的表达式执行)  写完设计模式之后感觉设计模式更多的一种逻辑关系,如果代码中有这种逻辑关系就可以用了。记得需要时候再用,不能为了设计模式而设计模式。没有什么就是好的,最主要用起来舒服吧。 ———————————————— 原文链接:https://blog.csdn.net/wanyouzhi/article/details/77248710 
  • [技术干货] 常用的设计模式
    什么是设计模式? 设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。面向对象设计模式通常以类或者对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。  设计模式的基本原则 1. 依赖倒置原则  高层模块不应该依赖低层模块,二者都应该依赖抽象; 抽象不应该依赖具体实现,具体实现应该依赖于抽象;  举个例子,自动驾驶系统公司是高层,汽⻋生产厂商为低层,它们不应该互相依赖,一方变动另一方也会跟着变动;而应该抽象一个自动驾驶行业标准,高层和低层都依赖它;这样以来就解耦了两方的变动;自动驾驶系统、汽⻋生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象)。  2. 开放封闭原则 一个类应该对扩展开放,对修改关闭。也就是对一个类尽量做扩展而不是修改,扩展一般是用继承或者组合的方式。 3. 面向接口编程 不将变量类型声明为某个特定的具体类,而是声明为某个接口。比如某个类成员是另一个类的具体对象,那么应该改成指向这个类的指针,因为根据多态原理,这个类指针实际上成为了一个接口。 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。  4. 封装变化点 将稳定点和变化点分离,扩展修改变化点;让稳定点与变化点的实现层次分离。 5.  单一职责原则 一个类应该仅有一个引起它变化的原因。 6. 里式替换原则 子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可 能出现错误;覆盖了父类方法却没实现父类方法的职责。也就是子类如果声明了父类存在的函数 就要把功能实现完全,父类有的子类也一定要有。 7. 接口隔离原则 不应该强迫客户依赖于他们不用的方法。 一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责。 所以要正确使用public,protected,private这三种权限。 8. 对象组合优于类继承 继承耦合度高,组合耦合度低(继承不能换爹,组合可以换爹)。 如何找到设计模式 从重构中获得:  1. 静态转变为动态 2. 早绑定转为晚绑定 3. 继承转为组合 4. 编译时依赖转变为运行时依赖 5. 紧耦合转变为松耦合 掌握设计模式的原则比具体的设计模式更重要,只有当你对业务非常熟悉的时候,才能信手拈来的写出适合的设计模式。  常见的设计模式和使用场景 1. 模板模式 定义一个操作中的算法的⻣架 ,而将一些步骤延迟到子类中。 Template Method使得子类可以不 改变一个算法的结构即可重定义该算法的某些特定步骤。  下面来看一个例子:  class IGame { public:     Play()     {         Process1();         Process2();         Process3();         Process4();     } protected:     virtual void Process1(){}     virtual void Process2(){}     virtual void Process3(){}     virtual void Process4(){} };   class Game1:public IGame { protected:     virtual void Process2(){}     virtual void Process4(){} };   class Game2:public IGame { protected:     virtual void Process1(){}     virtual void Process3(){} };  某个游戏IGame类有固定的流程,然后Game1和Game2分别继承自IGame,但是在具体某个步骤上各有不同,因此将Process1~4写成虚函数,由各个子类自己去实现。  这样写的前提当然是主框架流程是固定的,Play函数对外开放给用户使用,但是具体流程的函数是protected,提供子类去修改。这体现了接口隔离原则。 流程的接口在基类都抽象成了虚函数,由各个子类具体实现,这体现了依赖倒置原则。  2. 观察者模式 观察者模式在分布式架构中应用广泛,比如actor框架,skynet,redis,zoomkeeper,订阅和发布等。 观察者模式定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有 依赖于它的对象都得到通知并自动更新。  // 终端基类 class ITerminal  { public:     virtual void show(int data){} };   class DataCenter { public:     void Attach(ITerminal* ob){} // 注册绑定     void Detach(ITerminal* ob){} // 解绑     void Notify()     {         for(auto it = obs.begin(); it != obs.end(); ++it)         {             it->show();         }     } protected:     virtual int GetData(){return  0;} // 获取数据 private:     std::vector<ITerminal*> obs; };   class Terminal1:public ITerminal { public:     void show(int data){} } class Terminal2:public ITerminal { public:     void show(int data){} }  上面是一个发布和订阅的简单例子,假设有个数据中心提供数据给不同的终端展示,数据中心是一个稳定点,不同的终端设备是变化点,因此将终端封装成基类,提供一个展示的接口,不同的设备继承去实现不同的展示方式。  数据中心用一个vector或者set把注册过来的终端记录起来,需要展示的时候通过notify接口逐个通知展示。这样数据中心不必关注具体有哪些设备订阅了数据,是否订阅的操作交给了各个终端设备上。  3. 策略模式 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用 它的客户程序而变化。  策略模式提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法 之间进行切换; 策略模式消除了条件判断语句;就是在解耦合; 充分体现了开闭原则;单一职责; 其本质是分离算法,选择实现。  来看一个简单的例子,假设某电商网站一件商品在不同节假日的价格算法使不同的,可能会这么写:  enum VacationEnum {     chunjie, // 春节     wuyi; // 五一     qixi; // 七夕     guoqing; // 国庆 };   class Context { };   class Promotion {     VacationEnum vac; public:     double calc()     {         if(vac == chunjie){}         else if(vac == wuyi){}         else if(vac == qixi){}         else if(vac == guoqing){}     } };  这样写并不符合设计模式的单一职责原则和开放封闭原则。所以可以改成这样:  class Context {};   // 变化的  扩展的 class BaseVac { public:     virtual calc(Context &ctx){} }; class Vacchunjie: public BaseVac { public:     virtual calc(Context &ctx){} }; class Vacwuyi: public BaseVac { public:     virtual calc(Context &ctx){} }; class Vacxiqi: public BaseVac { public:     virtual calc(Context &ctx){} };   // 稳定的 单一职责的 class Promotion { public:     Promotion(BaseVac  *ss):s(ss){}     double calc(Context &ctx)     {         s->calc(ctx);     } private:     BaseVac  *s; };   int main() {     BaseVac *v = new Vacxiqi();     Promotion *p = new Promotion(v);          return 0; }  把变化点(不同节假日的不同算法)隔离出去,只保留单一的职责:获取价格。  这样做非常符合设计模式的原则,但是前提是对业务非常熟悉,因为别人来扩展维护的话,必须先了解之前已有的扩展有哪些。  4. 责任链模式 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成 一条链,并沿着这条链传递请求,直到有一个对象处理它为止。  这个模式的一些要点:  a.解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合;  b.责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;  c.责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;  d.充分体现了单一职责原则;将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展, 同时职责顺序也可以任意扩展;  本质是分离职责,动态组合。  责任链模式是比较过时的,在c语言里用的比较多,c++里有更好的数据结构(list)可以代替这个模式。  来看一个具体例子,公司请假审批规则是小于3天只给人事审批,大于3天且小于5天给经理审批,5天以上的给老板审批。下面是第一个版本的代码:  class Context { public:     std::string name;     int day; };   class LeaveRequest { public:     void HandleRequest(const Context &cxt)     {         if(cxt.day < 3)         {             HandleByHR(cxt);         }         else if(cxt.day < 5);         {             HandleByMgr(cxt);         }         else         {             HandleByBoss(cxt);         }              } private: // 接口隔离原则     void HandleByHR(const Context &cxt){}     void HandleByMgr(const Context &cxt){}     void HandleByBoss(const Context &cxt){} };  怎么重构这个代码,从封装变化点的原则来看,先把HandleRequest里的各种条件拆分出来,然后新增一个类成员指针指向上一级,子类只需要判断自己是否应该处理,不能处理的就通过指针转移到上级去处理。  class Context { public:     std::string name;     int day; };   class IHandler { public:     virtual ~ IHandler(){}     void SetNext(IHandler *n){bext = n;}     virtual bool HandleRequest(const Context &cxt)     {         if(CanHandle(cxt))         {         }         else if(GetNext())         {             GetNext()-> HandleRequest(cxt);         }         else         {             // error         }     } protected:     virtual bool CanHandle(const Context &cxt){}     IHandler * GetNext(){return next;} private:     IHandler *next; };   class HandleByMainProgram : public IHandler { protected:     virtual bool HandleRequest(const Context &ctx){         //     }     virtual bool CanHandle() {         //     } };   class HandleByProjMgr : public IHandler { protected:     virtual bool HandleRequest(const Context &ctx){         //     }     virtual bool CanHandle() {         //     } }; class HandleByBoss : public IHandler { public:     virtual bool HandleRequest(const Context &ctx){         //     } protected:     virtual bool CanHandle() {         //     } };   int main () {     IHandler * h1 = new MainProgram();     IHandler * h2 = new HandleByProjMgr();     IHandler * h3 = new HandleByBoss();     h1->SetNextHandler(h2);     h2->SetNextHandler(h3);       Context ctx;     h1->handle(ctx);     return 0; }  把不同部门的职责拆出来作为子类继承,主流程封装进基类里,上面这个版本同时使用了模板模式和责任链模式。  子类的接口都是protected的,这体现了接口隔离原则和依赖倒置原则。  责任链模式可以变形成功能链模式,比如nginx阶段处理,不同之处在于责任链每次只有一个模块会处理,功能链是按顺序执行不同的模块。  5. 装饰器模式 动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生成子类更为灵活。  这个模式看起来会和责任链模式很像。  通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。 不是解决“多子类衍生的多继承”问题,而是解决“父类在多个方向上的扩展功能”问题; 装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能;  装饰器模式本质是动态组合。  举个例子,普通员工有销售奖金,累计奖金,部⻔经理除此之外还有团队奖金;后面可能会添加环比增⻓奖 金,同时可能针对不同的职位产生不同的奖金组合。那么代码可以这样写:  class Context { public:     bool isMgr;     // User user;     // double groupsale; };   class Bonus { public:     double CalcBonus(Context &ctx) {         double bonus = 0.0;         bonus += CalcMonthBonus(ctx);         bonus += CalcSumBonus(ctx);         if (ctx.isMgr) {             bonus += CalcGroupBonus(ctx);         }         return bonus;     } private:     double CalcMonthBonus(Context &ctx) {         double bonus/* = */;         return bonus;     }     double CalcSumBonus(Context &ctx) {         double bonus/* = */;         return bonus;     }     double CalcGroupBonus(Context &ctx) {         double bonus/* = */;         return bonus;     } };   int main() {     Context ctx;     // 设置 ctx     Bonus *bonus = new Bonus;     bonus->CalcBonus(ctx); }  重构成装饰器模式后的代码:  class Context { public:     bool isMgr;     // User user;     // double groupsale; };   // 试着从职责出发,将职责抽象出来 class CalcBonus {     public:     CalcBonus(CalcBonus * c = nullptr) {}     virtual double Calc(Context &ctx) {         return 0.0; // 基本工资     }     virtual ~CalcBonus() {}   protected:     CalcBonus* cc; };   class CalcMonthBonus : public CalcBonus { public:     CalcMonthBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double mbonus /*= 计算流程忽略*/;          return mbonus + cc->Calc(ctx);     } };   class CalcSumBonus : public CalcBonus { public:     CalcSumBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double sbonus /*= 计算流程忽略*/;          return sbonus + cc->Calc(ctx);     } };   class CalcGroupBonus : public CalcBonus { public:     CalcGroupBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double gbnonus /*= 计算流程忽略*/;          return gbnonus + cc->Calc(ctx);     } };   class CalcCycleBonus : public CalcBonus { public:     CalcGroupBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double gbnonus /*= 计算流程忽略*/;          return gbnonus + cc->Calc(ctx);     } };   int main() {     // 1. 普通员工     Context ctx1;     CalcBonus *base = new CalcBonus();     CalcBonus *cb1 = new CalcMonthBonus(base);     CalcBonus *cb2 = new CalcSumBonus(cb1);     cb2->Calc(ctx1);     // 2. 部门经理     Context ctx2;     CalcBonus *cb3 = new CalcGroupBonus(cb2);     cb3->Calc(ctx2); }  在计算奖金的形式上,有点像递归,因为构造函数里设置的基类成员是给用户自己设置的,可以 灵活组合。而不是第一个版本那样用ifelse去判断是否要添加奖金 ,这样也很容易扩展和修改。  打个比方,责任链的模式看起来就像一个链表的结构,而装饰器模式看起来像一个俄罗斯套娃。  6. 单例模式 保证一个类仅有一个实例,并提供一个该实例的全局访问点。  先了解一个知识点,由C/C++编译的程序占用的内存分为以下几个部分:  1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其 操作方式类似于数据结构中的栈。  2、堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。  3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。  4、文字常量区 :常量字符串就是放在这里的。 程序结束后由系统释放  5、程序代码区:存放函数体的二进制代码。  下面来看看几种实现单例模式的例子。  版本一:     #include <mutex>   class Singleton  {      // 懒汉模式 lazy load public:     static Singleton * GetInstance()      {         //std::lock_guard<std::mutex> lock(_mutex); // 这里加锁粒度太大 切换线程开销大          if (_instance == nullptr)          {             std::lock_guard<std::mutex> lock(_mutex); // 在这里加锁             if (_instance == nullptr) // 双重判断             {                 _instance = new Singleton();                 atexit(Destructor);             }         }         return _instance;     }   private:     static void Destructor()      {         if (nullptr != _instance)          {             delete _instance;             _instance = nullptr;         }      }     Singleton(){} //构造     Singleton(const Singleton &cpy){} //拷⻉构造      Singleton& operator=(const Singleton&) {}      static Singleton * _instance;     static std::mutex _mutex; };   Singleton* Singleton::_instance = nullptr;//静态成员需要初始化  std::mutex Singleton::_mutex; //互斥锁初始化  这个例子有几个需要注意的点,第一个是加锁的粒度,当单例指针是空的时候再去加锁,减少锁粒度可以减少线程切换的开销。第二个是双重判断,防止多线程重入导致多次分配的问题。第三这是个懒汉模式,用到的时候才决定分配初始化。  但是这个例子有个问题。  c++的new操作里分为了三个步骤:分配内存,调用构造函数,赋值操作。在多线程环境下,cpu会进行指令重排,前面三个步骤可能执行起来是132的顺序。所以可能会出现某个线程判断单例指针不为空,但是还没调用构造函数,就立即返回,最后导致程序崩溃。所以上面这个例子还是有问题的。(如果在判断前就加锁是没问题的,代价是增加了开销)  版本二:    #include <mutex> #include <atomic>   class Singleton  { public:     static Singleton * GetInstance()      {         Singleton* tmp = _instance.load(std::memory_order_relaxed); //取出原子对象               std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障          if (tmp == nullptr)          {             std::lock_guard<std::mutex> lock(_mutex);             tmp = _instance.load(std::memory_order_relaxed);             if (tmp == nullptr)              {                 tmp = new Singleton;                       std::atomic_thread_fence(std::memory_order_release);//释放内存屏障                 _instance.store(tmp, std::memory_order_relaxed);// 赋值给原子对象                 atexit(Destructor);             }         }         return tmp;      }   private:     static void Destructor()      {         Singleton* tmp = _instance.load(std::memory_order_relaxed);         if (nullptr != tmp)          {             delete tmp;          }     }     Singleton(){}     Singleton(const Singleton&) {}     Singleton& operator=(const Singleton&) {}     static std::atomic<Singleton*> _instance;     static std::mutex _mutex; }; std::atomic<Singleton*> Singleton::_instance;//原子对象 静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化 // g++ Singleton.cpp -o singleton -std=c++11  上面代码用c++11特性内存屏障来解决cpu指令重排的问题。  版本三:  class Singleton { public:   ~Singleton(){}   static Singleton& GetInstance()    {     static Singleton instance;     return instance;   } private:   Singleton(){}   Singleton(const Singleton&) {}   Singleton& operator=(const Singleton&) {} }; // g++ Singleton.cpp -o singleton -std=c++11 这个版本需要在c++11或者更高级版本的编译后才能用。  c++11 magic static 特性:如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。这就解决了多线程场景带来的问题。  该版本的优点:  a. 利用静态局部变量特性,延迟加载;  b. 利用静态局部变量特性,系统自动回收内存,自动调用析构函数;  c. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作;  d. c++11 静态局部变量初始化时,具备线程安全;  版本四:    template<typename T> class Singleton  { public:     static T& GetInstance()      {         static T instance; // 这里要初始化DesignPattern,需要调用DesignPattern 构造函数,同时会调用父类的构造函数。          return instance;     }    protected:     virtual ~Singleton() {}     Singleton() {} // protected修饰构造函数,才能让别人继承 Singleton(const Singleton&) {}     Singleton& operator =(const Singleton&) {} };   class DesignPattern : public Singleton<DesignPattern>  {     friend class Singleton<DesignPattern>; // friend 能让 Singleton<T> 访 问到 DesignPattern构造函数 private:     DesignPattern(){}     DesignPattern(const DesignPattern&) {}     DesignPattern& operator=(const DesignPattern&) {} }  这个版本将单例模式封装成了模板,需要注意两个地方:  a.单例模板类的构造函数用protected修饰是为了它本身能被继承。  b. 单例模板里的GetInstance接口要获取类型T的构造函数,因此继承这个模板的类必须声明模板类为友元。  7. 工厂方法模式 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化 延迟到子类。  一般这些业务会使用工厂模式:解决创建过程比较复杂,希望对外隐藏这些细节;比如连接池,线程池; 隐藏对象真实类型; 对象创建会有很多参数来决定如何创建; 创建对象有复杂的依赖关系;  线程池的初始化需要由机器的内核数来决定线程数量,但是用户并不需要关心内核数,用户只需要获取到可用的线程就行。因此可以用工厂模式。  其本质是延迟到子类来选择实现;  举个例子,实现一个导出数据的接口,让客户选择数据的导出方式:  #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public:     virtual bool Export(const std::string &data) = 0;     virtual ~IExport(){} };   class ExportXml : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportJson : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportTxt : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   // =====1 int main() {     std::string choose/* = */;     if (choose == "txt") {         IExport *e = new ExportTxt();         e->Export("hello world");     } else if (choose == "json") {         IExport *e = new ExportJson();         e->Export("hello world");     } else if (choose == "xml") {         IExport *e = new ExportXml();         e->Export("hello world");     } }  对于数据导出有多种不同格式的类,在业务代码层面上,第一个版本里需要使用者区分判断多种格式,我们希望把这种复杂的创建操作封装起来,因此创建一个工厂类,这个类绑定了导出数据的操作和指向具体数据格式的类成员,使用者只需要创建某个具体的工厂子类,调用导出的接口就完成任务了。  重构后的代码:  #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public:     virtual bool Export(const std::string &data) = 0;     virtual ~IExport(){} };   class ExportXml : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportJson : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportTxt : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportCSV : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class IExportFactory { public:     IExportFactory() {         _export = nullptr;     }     virtual ~IExportFactory() {         if (_export) {             delete _export;             _export = nullptr;         }     }     bool Export(const std::string &data) {         if (_export == nullptr) {             _export = NewExport();         }         return _export->Export(data);     } protected:     virtual IExport * NewExport(/* ... */) = 0; private:     IExport* _export; };   class ExportXmlFactory : public IExportFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportXml();         // 可能之后有什么操作         return temp;     } }; class ExportJsonFactory : public IExportFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportJson;         // 可能之后有什么操作         return temp;     } }; class ExportTxtFactory : public IExportFactory { protected:     IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportTxt;         // 可能之后有什么操作         return temp;     } };   class ExportCSVFactory : public IExportFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportCSV;         // 可能之后有什么操作         return temp;     } };   int main () {     IExportFactory *factory = new ExportTxtFactory();     factory->Export("hello world");     return 0; }  8. 抽象工厂模式 提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。  抽象工厂类是基于工厂类实现的,实际上是一回事,抽象工厂类拥有更多的更复杂的操作接口,但是对于使用者来说,仍然只需要创建时指定某个子工厂类,无需关心具体的操作内容。  举个例子,实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式:  #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public:     virtual bool Export(const std::string &data) = 0;     virtual ~IExport(){} };   class ExportXml : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportJson : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportTxt : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportCSV : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class IImport { public:     virtual bool Import(const std::string &data) = 0;     virtual ~IImport(){} };   class ImportXml : public IImport { public:     virtual bool Import(const std::string &data) {         return true;     } };   class ImportJson : public IImport { public:     virtual bool Import(const std::string &data) {         return true;     } };   class ImportTxt : public IImport { public:     virtual bool Import(const std::string &data) {         return true;     } };   class ImportCSV : public IImport { public:     virtual bool Import(const std::string &data) {         // ....         return true;     } };   class IDataApiFactory { public:     IDataApiFactory() {         _export = nullptr;         _import = nullptr;     }     virtual ~IDataApiFactory() {         if (_export) {             delete _export;             _export = nullptr;         }         if (_import) {             delete _import;             _import = nullptr;         }     }     bool Export(const std::string &data) {         if (_export == nullptr) {             _export = NewExport();         }         return _export->Export(data);     }     bool Import(const std::string &data) {         if (_import == nullptr) {             _import = NewImport();         }         return _import->Import(data);     } protected:     virtual IExport * NewExport(/* ... */) = 0;     virtual IImport * NewImport(/* ... */) = 0; private:     IExport *_export;     IImport *_import; };   class XmlApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportXml;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportXml;         // 可能之后有什么操作         return temp;     } };   class JsonApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportJson;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportJson;         // 可能之后有什么操作         return temp;     } }; class TxtApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportTxt;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportTxt;         // 可能之后有什么操作         return temp;     } };   class CSVApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportCSV;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportCSV;         // 可能之后有什么操作         return temp;     } };   int main () {     IDataApiFactory *factory = new CSVApiFactory();     factory->Import("hello world");     factory->Export("hello world");     return 0; }  9. 适配器模式 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起 工作的那些类可以一起工作。  原来的接口是稳定的,新的外来的需求是变化的,那么可以通过继承原来的接口,让原来的接 口继续保持稳定,在子类通过组合的方式来扩展功能。  其本质是转换匹配,复用功能。  举个日志系统的例子,原来是通过写磁盘的方式进行存储,后来因为查询不便,需要额外添加往数据库写日志 的功能(写文件和数据库并存):  #include <string> #include <vector> using namespace std;   class LogSys { public:     LogSys() {}     void WriteLog(const vector<string> &) {         // ... 日志id 时间戳 服务器id 具体日志内容 roleid     }     vector<string>& ReadLog() {         // ...         vector<string> data /* = ...*/;         return data;     } };   class DB;  // 面向接口编程 而不是具体类 强依赖  耦合性高  mysql mongo   class LogSysEx : public LogSys { public:     LogSysEx(DB *db) : _db(db) {}       void AddLog(const vector<string> &data) {         LogSys::WriteLog(data);         /*             这里调用 _db 的方法将 data 数据存储到数据库         */     }       void DelLog(const int logid) {         vector<string>& data = LogSys::ReadLog();         // 从 vector<string> 中删除 logid的日志         LogSys::WriteLog(data);         // 调用 _db 的方法将 logid的日志删除     }       void UpdateLog(const int logid, const string &udt) {         vector<string>& data = LogSys::ReadLog();         // 从 vector<string> 中更新 logid的日志 udt         LogSys::WriteLog(data);         // 调用 _db 的方法将 logid的日志更改     }       string& LocateLog(const int logid) {         vector<string>& data = LogSys::ReadLog();         string log1 /* = from log file*/;         string log2 /* = from db */;         string temp = log1 + ";" + log2;         return temp;     }      private:     DB* _db; };  咱就是说在对一个类进行扩展修改的时候,尽量不要动原来的代码,而是通过继承组合的方式创建新的类,在这个新的类里实现新功能和包含旧功能。  10. 代理模式 代理模式是为其他对象提供一种代理以控制对这对象的访问。  比如远程代理(隐藏一个对象存在不同的地址空间的事实),虚代理(延迟加载lazyload),保护 代理(在代理前后做额外操作,权限管理,引用计数等); 在分布式系统中,actor模型(skynet)等会常用到代理模式。  其本质是控制对象访问。  举个例子,在有些系统中,为了某些对象的纯粹性,只进行了功能相关封装(稳定点),后期添加了其他功能 需要对该对象进行额外操作(变化点),为了隔离变化点(也就是不直接在稳定点进行修改,这样 会让稳定点也变得不稳定),可以抽象一层代理层:    class ISubject { public:     virtual void Handle() = 0;     virtual ~ISubject() {} };   // 该类在当前进程,也可能在其他进程当中 class RealSubject : public ISubject { public:     virtual void Handle() {         // 只完成功能相关的操作,不做其他模块的判断     } };   // 在当前进程当中  只会在某个模块中使用 class Proxy1 : public ISubject { public:     Proxy1(ISubject *subject) : _subject(subject) {}     virtual void Handle() {         // 在访问 RealSubject 之前做一些处理         //if (不满足条件)         //    return;         _subject->Handle();         count++;         // 在访问 RealSubject 之后做一些处理     } private:     ISubject* _subject;     static int count; }; int Proxy1::count = 0;   // 在分布式系统当中  skynet actor class Proxy2 : public ISubject { public:     virtual void Handle() {         // 在访问 RealSubject 之前做一些处理                  // 发送到数据到远端  网络处理  同步非阻塞 ntyco c协程         //IResult * val = rpc->call("RealSubject", "Handle");           // 在访问 RealSubject 之后做一些处理     } private:     /*void callback(IResult * val) {         // 在访问 RealSubject 之后做一些处理     }*/ };  上面代码中proxy类可以继承ISubject类也可以不继承,写成继承的样式是为了让程序员明白这是该类的一个代理模块,如果不是继承的,看起来会和适配器模式很像。  proxy类中的基类指针可以改成一个容器,管理多个对象,这在游戏服务器的网关里经常看到。 ———————————————— 原文链接:https://blog.csdn.net/zhoujiajie0521/article/details/122194799 
  • [技术干货] synchronized关键字的底层原理
    synchronized关键字是Java中用于控制多线程访问共享资源的一种机制,它确保同一时刻只有一个线程可以执行某个方法或代码块。其底层原理主要基于JVM(Java虚拟机)中的Monitor(监视器)机制来实现。以下是synchronized关键字的详细底层原理:1. Monitor机制Monitor(监视器):是Java虚拟机中的一种内置同步机制,每个Java对象都有一个与之关联的Monitor。Monitor通过内部的一些同步原语(如lock和unlock指令)来实现线程间的互斥访问和协调。锁的获取与释放:当一个线程尝试进入被synchronized修饰的代码块或方法时,它首先需要获取对象的Monitor。如果Monitor已被其他线程持有,则当前线程将被阻塞,直到Monitor变为可用状态。当线程完成执行后,它会释放Monitor,以便其他线程可以获取并继续执行。2. 锁的升级偏向锁(Biased Locking):Java 6及以后版本中引入的一种锁优化技术。如果某个对象锁在整个运行过程中只被一个线程持有,那么JVM会将该锁升级为偏向锁,以减少锁获取的开销。偏向锁通过CAS(Compare-And-Swap)操作将线程ID设置到对象的Mark Word中,以表示该对象锁已被当前线程占有。轻量级锁(Lightweight Locking):当存在多个线程竞争同一个锁时,偏向锁会升级为轻量级锁。轻量级锁通过CAS操作在JVM的栈帧中创建锁记录(Lock Record),并将锁对象的Mark Word指向该锁记录。如果锁记录中的Mark Word与锁对象的Mark Word一致,则当前线程获取锁成功;否则,表示存在锁竞争,可能需要升级到重量级锁。重量级锁(Heavyweight Locking):当轻量级锁无法满足需求时(如多个线程长时间竞争同一个锁),锁会升级为重量级锁。重量级锁依赖于操作系统层面的互斥量(Mutex)来实现,涉及到用户态和内核态的切换,成本较高。3. 锁的释放与等待/通知锁的释放:当线程完成synchronized代码块的执行后,它会通过unlock指令释放Monitor,并将Monitor返还给对象池,以便其他线程可以获取。等待/通知:在synchronized代码块中,线程可以通过调用对象的wait()方法进入等待状态,并释放Monitor。其他线程可以通过调用对象的notify()或notifyAll()方法唤醒等待的线程。这些操作都是通过操纵与对象关联的Monitor来实现的。4. 锁的获取流程线程尝试获取对象的Monitor。如果Monitor未被其他线程持有,则当前线程获取Monitor并继续执行。如果Monitor已被其他线程持有,则当前线程进入阻塞状态,等待Monitor变为可用。当Monitor变为可用时,当前线程重新尝试获取Monitor。5. 注意事项synchronized关键字是JVM层面的机制,其实现依赖于JVM的具体实现(如HotSpot)。锁的升级和降级是JVM自动进行的,无需开发者手动干预。在使用synchronized时,应尽量避免长时间持有锁,以减少线程阻塞和死锁的风险。综上所述,synchronized关键字的底层原理主要基于JVM中的Monitor机制,通过锁的获取、释放、升级和降级等机制来实现线程间的同步控制。
  • [技术干货] java中偏向锁、轻量级锁、重量级锁
    在Java中,锁是并发编程中用来控制多个线程对共享资源的访问的重要机制。Java的synchronized关键字和ReentrantLock等都是实现锁的方式。Java虚拟机(JVM)为了提高锁的性能,引入了偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)和重量级锁(Heavyweight Locking)三种锁状态。这三种锁状态的转换主要是为了提高锁的性能,减少线程在获取锁时的开销。1. 偏向锁(Biased Locking)偏向锁是Java 6引入的一种锁优化,它假设大多数情况下,锁不存在多线程竞争,锁总是由同一线程多次获得。当一个线程访问同步块并获取锁时,锁会进入偏向模式,此时Mark Word里会存储锁偏向的线程ID,以后该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下锁是否还指向当前线程ID即可。如果测试成功,表示线程已经获得了锁。如果失败,则尝试使用轻量级锁。偏向锁的优势在于,当锁只被一个线程访问时,可以极大地减少锁的获取和释放的开销。2. 轻量级锁(Lightweight Locking)轻量级锁是相对于使用操作系统互斥量(Mutex)来实现的传统锁而言的。它用于解决在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当锁处于偏向锁状态,但发生线程竞争,或者锁一开始就不是偏向锁时,就会尝试使用轻量级锁。轻量级锁通过CAS(Compare-And-Swap)操作来尝试把锁对象的Mark Word更新为指向当前线程的栈帧中的Lock Record的指针。如果成功,当前线程就获得了锁;如果失败,表示有其他线程竞争锁,锁会膨胀为重量级锁。3. 重量级锁(Heavyweight Locking)重量级锁是传统意义上的锁,它依赖于底层操作系统的Mutex Lock来实现。当锁膨胀为重量级锁时,意味着线程竞争非常激烈,JVM会调用操作系统的互斥量来实现同步控制。线程阻塞和唤醒都需要操作系统的帮助,因此开销较大。总结偏向锁、轻量级锁和重量级锁是Java虚拟机为了提高锁的性能而引入的三种锁状态。它们之间的转换是JVM根据锁的竞争情况自动进行的。偏向锁适用于锁只被一个线程访问的场景;轻量级锁适用于锁被少量线程竞争的场景;重量级锁适用于锁被多个线程激烈竞争的场景。这三种锁状态的存在,使得Java的锁机制能够灵活应对不同场景下的并发需求,从而提高程序的性能。
总条数:696 到第
上滑加载中