【网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序(万字博文)

embedded/2024/9/20 8:58:46/ 标签: java, 网络, 服务器, udp, tcp/ip

 系列文章目录

网络通信基础】网络中的常见基本概念

网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序(万字博文)

网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制(CRC算法、MD5算法)


目录

一、什么是网络编程?

二、网络编程中的基本概念

1. 客户端和服务器

2. 请求和响应

三、Socket套接字

UDP数据报套接字编程

1. DatagramSocket

2. DatagramPacket

3. UDP回显客户端服务器程序

4. UDP字典客户端服务器程序

TCP流套接字编程

1. ServerSocket

2. Socket

3. TCP回显客户端服务器程序

4. 服务器引入多线程

5. 服务器引入线程池

6. TCP字典客户端服务器程序 


一、什么是网络编程?

网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称网络数据传输)

即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。但是,我们的目的是提供网络上的不同主机,基于网络来传输数据资源。

网络编程,本质上就是学习“传输层”给“应用层”提供的API,通过代码的形式,把数据交给传输层,进一步的通过层层封装,就可以把数据通过网卡发送出去了。

二、网络编程中的基本概念

1. 客户端和服务器

客户端:主动发起通信的一方,称为客户端.

服务器被动接受的一方,称为服务器,可以提供对外服务.

同一个程序在不同场景中,可能是客户端也可能是服务器(服务器可能还需要主动向别的服务器发起通信,此时的服务器相对于被发起通信的服务器来说,就是客户端).

2. 请求和响应

请求(request):客户端给服务器发送数据。

响应(response):服务器给客户端返回数据。

一般来说,获取一个网络资源,涉及到两次网络数据传输:

  • 第一次:请求数据的发送.
  • 第二次:响应数据的发送.

就比如在快餐店点一份炒饭:

先要发起请求:点一份炒饭;再有快餐店提供的对于响应:提供一份炒饭。

三、Socket套接字

Socket套接字,是由系统提供的用于网络编程的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。

前面说过,要想进行网络编程,需要使用的系统API,本质上是由传输层提供的。

传输层涉及到的主要协议有两个:

  1. 流套接字:TCP(传输控制协议)
  2. 数据报套接字:UDP(用户数据报协议)

TCP的特点:                                                                 

  • 有连接(类似于打电话,需要接通才能通信)             
  • 可靠传输(尽可能完成数据传输,起码可以知道当前这个数据对方是否接收到了)                       
  • 面向字节流(此处字节流和文件中的字节流完全一致,网络中传输数据的基本单位就是字节)
  • 全双工(一个信道,可以双向通信)                            

 UDP的特点:

  • 无连接(类似于发微信/短信,直接发送过去)            
  • 不可靠传输(没有确认、重传机制;如果因为网络故障等原因,数据无法发到对方,UDP协议层也不会给应用层返回任何错误信息)
  • 面向数据报(每次传输的基本单位是一个数据报(由一系列的字节构成的)特定的结构)
  • 全双工(一个信道,可以双向通信)     

UDP数据报套接字编程

UDP socket API的使用

1. DatagramSocket

DatagramSocket 是 UDP Socket(套接字),用于发送和接收UDP数据报

构造方法:

重要方法:

2. DatagramPacket

DatagramPacket 是 UDP Socket(套接字)发送和接收的数据报(每次发送接收数据的基本单位)

构造方法:

3. UDP回显客户端服务器程序

通过这个程序,了解 socket api 的使用,和典型的客户端服务器基本工作流程。

对于服务器,需要指定端口号来创建 socket (类似于饭店,需要指定具体位置),主要流程如下:

  1. 接收客户端请求,并解析
  2. 根据请求,计算出响应(回显服务器,则是直接将请求的数据返回)
  3. 将响应写回给客户端

注意事项详见代码注释: 

java">import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;//Udp回显服务器
public class UdpEchoServer {//Udp套接字private DatagramSocket socket;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port); //服务器:指定端口号创建}public void start() throws IOException {System.out.println("服务器启动");while (true) {//1.接收客户端的请求,并解析DatagramPacket requestServer = new DatagramPacket(new byte[4096], 4096);socket.receive(requestServer);//2.根据请求,计算出响应String request = new String(requestServer.getData(), 0, requestServer.getLength());String response = process(request);//3.将响应写回给客户端(需要指定发送到的IP地址及端口号)DatagramPacket responseServer = new DatagramPacket(response.getBytes(), response.getBytes().length, requestServer.getSocketAddress());socket.send(responseServer);//打印日志System.out.printf("[%s:%d] request:%s response:%s\n",responseServer.getAddress(), responseServer.getPort(), request, response);}}//根据请求计算响应(由于是回显程序,直接返回请求的内容)public String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer udpEchoServer = new UdpEchoServer(9090);udpEchoServer.start();}
}

对于客户端,服务器的端口号可以由系统随机分配,但需要知道服务器的IP地址及端口号(去饭店吃饭,需要知道饭店的地址及具体是哪个门店),主要流程如下:

  1. 客户端读取用户请求
  2. 构造请求的数据报,并发送到服务器(此时就需要指定服务器的IP地址及端口号)
  3. 读取服务器的响应,并解析出响应的内容
  4. 输出服务器的响应
java">import java.io.IOException;
import java.net.*;
import java.util.Scanner;//Udp回显客户端
public class UdpEchoClient {private DatagramSocket socket;private String address;private int port;//客户端需要知道服务器的IP地址及端口号public UdpEchoClient(String address, int port) throws SocketException {this.address = address;this.port = port;socket = new DatagramSocket(); //服务器:随机端口号创建}public void start() throws IOException {System.out.println("客户端启动");Scanner in = new Scanner(System.in);while (true) {System.out.print("-> ");if (!in.hasNext()) {break;}//1.控制台读取请求内容String request = in.next();//2.构造请求的数据报,并发送到服务器(需要指定目的IP地址和目的端口号发送请求)DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(address), port);socket.send(requestPacket);//3.读取服务器的响应,并解析出响应的内容DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);String response = new String(responsePacket.getData(), 0, responsePacket.getLength());//4.将响应内容输出到控制台System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);udpEchoClient.start();}
}

先运行服务器,再运行客户端,看程序的执行效果:

  • 可以看到,服务器端能够正确接收到请求并作出响应,并打印出日志(客户端IP,客户端端口号,请求内容,响应内容)
  • 客户端也能够正确发送请求,并正确解析服务器端返回的响应

这个程序并不能直接做到“跨主机通信”,因为这台主机可能不能直接访问到另一台主机(NAT机制)。但是可以通过以下手段实现“跨主机通信”:

  1. 服务器端程序打成 jar 包
  2. 把 jar 包传到云服务器上,并运行

经过这样的操作,其他主机通过运行上述的客户端程序,就能够发起通信了。

4. UDP字典客户端服务器程序

基于上述回显服务器,还可以实现出一些其他带有一点业务逻辑的服务器

改进成一个“字典服务器”,英译汉的效果。请求是一个英文单词,响应返回对应的中文翻译。

主要逻辑其实和回显服务器基本一致,唯一的区别就在于,服务器端将客户端请求的数据,计算成响应的方式不一致。回显服务器是直接返回客户端请求的数据,这里的字典服务器则是英译汉效果。

而上述代码中,这个根据请求数据计算响应数据的操作,是通过process方法实现的。因此只需要让这个字典服务器继承回显服务器,并重写process方法即可。这里英译汉的业务逻辑通过打表的方式实现。

java">import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;//Udp字典服务器
public class UdpDictServer extends UdpEchoServer {Map<String, String> map;public UdpDictServer(int port) throws SocketException {super(port);map = new HashMap<>();map.put("cat", "小猫");map.put("dog", "小狗");map.put("animal", "动物");}//通过重写 计算响应的process方法,达成 英->汉 的效果@Overridepublic String process(String request) {return map.getOrDefault(request, "找不到该单词");}public static void main(String[] args) throws IOException {UdpDictServer udpDictServer = new UdpDictServer(9090);udpDictServer.start();}
}

还是先运行字典服务器,再运行回显客户端(这里客户端是通用的,因为回显客户端只进行发送请求和接收响应并解析的操作),看程序的执行效果:

  • 同样,服务器端能够正确接收到请求、解析请求,并计算出响应、写回给客户端,并打印出日志(客户端IP,客户端端口号,请求内容,响应内容)
  • 客户端也能够正确发送请求,并正确解析服务器端返回的响应

TCP流套接字编程

1. ServerSocket

ServerSocket 类是创建TCP服务器端Socket的API. (只能给服务器端使用)

构造方法:

重要方法:

2. Socket

Socket 类用于创建客户端 Socket,或服务器端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket. (服务器端和客户端都能使用)

构造方法:

重要方法:

3. TCP回显客户端服务器程序

使用TCP协议实现回显客户端服务器程序。与UDP协议实现的最大区别是,TCP是有连接的,和打电话一样,需要一方(客户端)拨号,一方(服务器)接通,因此TCP协议首要操作就是等待客户端连接。

和UDP回显服务器一样,对于这里的服务器,同样需要指定端口号创建TCP服务器端Socket,即ServerSocket。

  1. 服务器启动后,就需要监听当前绑定端口(accept方法),等待客户端连接。
  2. 当成功建立连接后,会返回一个Socket对象。这个对象保存了对端信息,即客户端信息,可以用来接收和发送数据(TCP是面向字节流的,通过这个Socket对象获取对应输入流和输出流,通过输入输出流进行对 socket 的读写,达成接收和发送数据的功能)。

后续流程和UCP回显服务器一致。此处由于每有一个客户端连接,就会有一个clientSocket,这里消耗的Socket会越来越多,因此每当一个客户端连接结束,就需要释放这个clientSocket。

java">import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;//TCP回显服务器
public class TcpEchoServer {private ServerSocket serverSocket;public TcpEchoServer(int port) throws IOException {//指定服务器端口号,创建一个serverSocketserverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");while (true) {//监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息Socket clientSocket = serverSocket.accept();processConnection(clientSocket);}}private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端上线\n", clientSocket.getInetAddress(), clientSocket.getPort());try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {while (true) {//1.读取客户端请求的数据//利用scanner读取客户端输入的信息Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(), clientSocket.getPort());break;}//这里的next()需要遇到\n才停止,因此需要对端写入的时候,要同时写入\n换行符String request = scanner.next();//2.解析请求的数据,并计算出响应String response = process(request);//3.将响应写回到客户端//outputStream.write(response.getBytes(), 0, response.getBytes().length);PrintWriter writer = new PrintWriter(outputStream);writer.println(response);writer.flush();//打印日志System.out.printf("[%s:%d] request:%s response:%s\n",clientSocket.getInetAddress(), clientSocket.getPort(), request, response);}} catch (IOException e) {throw new RuntimeException(e);} finally {clientSocket.close();}}//回显服务器,直接返回原数据public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}

对于客户端,需要指定服务器的IP和端口号建立连接,即使用 Socket(String host, int port) 创建Socket的时候,就开始发起与对应服务器建立连接的请求了。

主要流程和UDP回显客户端程序的流程也基本一致,只需要注意请求和响应数据的方式是不同的,是通过操作输入输出流完成的即可。

java">import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;//TCP回显客户端
public class TcpEchoClient {private Socket clientSocket;//需要指定服务器的IP和端口号public TcpEchoClient(String serverAddress, int serverPort) throws IOException {//与对应客户端建立连接clientSocket = new Socket(InetAddress.getByName(serverAddress), serverPort);}public void start() {System.out.println("客户端启动!");try (Scanner scannerConsole = new Scanner(System.in);InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {while (true) {//1.用户从控制台输入数据System.out.print("-> ");String request = scannerConsole.next();//2.将该数据作为请求,发送给服务器//outputStream.write(request.getBytes(), 0, request.getBytes().length);//outputStream.write('\n');PrintWriter writer = new PrintWriter(outputStream);writer.println(request);writer.flush(); //刷新缓冲区,确保数据发送出去//3.读取服务器的响应,并解析响应的内容Scanner scannerNetwork = new Scanner(inputStream);String response = scannerNetwork.next();//4.将响应输出到控制台System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);tcpEchoClient.start();}
}

先运行服务器,再运行客户端,看执行效果:

可以看到,服务器和客户端都能满足我们的需求,但这里其实还存在一个问题。

当我们开启多个客户端想要进行连接通信时,只有第一个连接到的客户端才能正确通信,其他的客户端是没有反应的。要想某个客户端能正常通信,只有当其他客户端都下线(结束程序),这个客户端才能接收到响应数据。

可以看到,此处的这个客户端并没有正确通信,当另一个客户端下线之后,该客户端此前发送的数据又正常请求并响应了。

分析过程:

  • 第一个客户端连上服务器之后,服务器就会从accept这里返回(解除阻塞),然后进入到processConnection方法中.
  • 接下来服务器就会在processConnection循环处理客户端的请求,只有当客户端退出之后,连接结束,才会退出循环.
  • 服务器在循环处理客户端请求的时候,第二个客户端发起连接请求,而服务器这里并不能执行到accept。因此并不能成功连接,只有当客户端退出,才会执行回到accept进行连接.

第二个客户端之前发的请求为什么能被立即处理?

  • 当前TCP在内核中,每个 socket 都是由缓冲区的。客户端发送的数据通过客户端代码,已经写入到该客户端的输出流(缓冲区)了,这里数据确实发送了,只不过数据在服务器的接收缓冲区中。
  • 一旦第一个客户端退出,回到第一层循环,执行accept连接操作,后续processConnection方法里的 next 就能把之前缓冲区的内容给读出来。

解决上述问题的核心思路就是使用多线程:

  • 单个线程,无法既能给客户端提供服务,又能去快速执行到第二次 accept 方法进行连接。
  • 通过引入多线程,让主线程只负责执行 accept。每次有一个客户端连接上来,就分配一个新的线程,由新的线程负责给客户端提供服务。

由于这里不涉及多个线程修改共享变量,因此没有线程安全问题,我们只需要改动 start 方法即可。

4. 服务器引入多线程

java">    //多线程public void start() throws IOException {System.out.println("服务器启动!");while (true) {//监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息Socket clientSocket = serverSocket.accept();Thread t = new Thread(() -> {try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});t.start();}}

通过引入多线程,这里的服务器就能支持多个客户端同时与其通信了。

上述问题,不是TCP引起的,而是代码两次循环嵌套引起的,UDP服务器,就是只有一层循环,因此不会有这个问题。

而这个多线程版本同样还有一些问题: 

  • 每有一个客户端连接,就会创建一个新的线程,每当这个客户端结束,就要销毁这个线程。
  • 如果客户端比较多,并且频繁连接、关闭,就会使服务器频繁创建和销毁线程。

前面讲过,线程池解决的就是线程频繁创建和销毁的问题,因此,这里的优化方案就是使用线程池。

5. 服务器引入线程池

java">    public void start() throws IOException {System.out.println("服务器启动!");ExecutorService threadPool = Executors.newCachedThreadPool();while (true) {//监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息Socket clientSocket = serverSocket.accept();threadPool.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}}});}}

线程开销问题解决了。但是,如果当前的场景使线程频繁创建,但是不销毁呢?

  • 这种情况下,如果继续使用多线程/线程池,就会导致当前服务器积累大量的线程,此时,对于服务器的负担是非常重的。

要解决这个新问题,还可以引入其他的方案:

  1. 协程:轻量级线程,本质上还是一个线程,用户态可以通过手动调度的方式让这一个线程“并发”执行多个任务。
  2. IO 多路复用:系统内核级别的机制,本质上是让一个线程同时去负责处理多个socket。

6. TCP字典客户端服务器程序 

同UDP字典客户端服务器程序:

java">import java.io.IOException;
import java.util.HashMap;
import java.util.Map;//TCP字典服务器
public class TcpDictServer extends TcpEchoServer {Map<String, String> map;public TcpDictServer(int port) throws IOException {super(port);map = new HashMap<>();map.put("cat", "小猫");map.put("dog", "小狗");map.put("animal", "动物");}@Overridepublic String process(String request) {return map.getOrDefault(request, "未找到该单词");}public static void main(String[] args) throws IOException {TcpDictServer tcpDictServer = new TcpDictServer(9090);tcpDictServer.start();}
}


http://www.ppmy.cn/embedded/11847.html

相关文章

基于二维CS-SCHT变换和LABS方法的水印嵌入和提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ....................................................................... %对水印进行置乱…

4-19 算法思路总结

leetcode 98 验证二叉搜索树 利用递归算法&#xff0c;从上而下递归&#xff0c;遇到false直接返回无需递归到叶子节点&#xff0c;从上往下递归时&#xff0c;传递并改变两个参数&#xff0c;对于当前节点的最大值以及最小值&#xff0c;对于根节点来说&#xff0c;初始化的最…

信息打点--公众号服务

微信公众号 获取微信公众号的途径https://weixin.sogou.com/ 微信公众号没有第三方服务 Github监控 人员&域名&邮箱 eg&#xff1a;xxx.cn password in:file https://gitee.com/ https://github.com/ https://www.huzhan.com/ 资源搜索 in:name test 仓库标题搜索含有…

(5)步态识别论文研读——GaitDAN:基于对抗域适应的跨视角步态识别

GaitDAN: Cross-view Gait Recognition via Adversarial Domain Adaptation | IEEE Journals & Magazine | IEEE Xplore GaitDAN: Cross-view Gait Recognition via Adversarial Domain Adaptation 基于对抗与适应 摘要&#xff1a;视角变化导致步态外观存在显着差异。因…

不容忽视的听力“杀手”

音乐与短视频已成为现代生活的重要组成部分&#xff0c;许多人在繁忙的日常中依赖耳机来获得片刻的宁静或激情。然而&#xff0c;我们也需要警惕&#xff0c;不当的耳机使用可能对我们的听力造成风险。长时间以高音量使用耳机&#xff0c;无疑会对听力构成挑战。 *图片源于网络…

java反射

java反射机制是指在程序运行状态中&#xff0c;可以构造任意一个类的对象&#xff0c;可以了解任意一个对象所属的类&#xff0c;可以了解任意一个类的成员变量和方法&#xff0c;可以调用任意一个对象的属性和方法。 写了一个小demo&#xff1a; 这是fangdong类 package com…

C语言 实现带透明度的鼠标光标 .cur 编辑器

操作参考&#xff1a; .cur 鼠标光标编辑器-CSDN博客 增加了滑块修改透明度 有一个bug&#xff0c;就是取色在绘图板上取色时&#xff0c;取到的颜色的透明度是0&#xff0c;也就是说&#xff0c;同样的颜色&#xff0c;有的是透明出来的&#xff0c;有的就是本身的颜色。但…

接收区块链的CCF会议--ICSOC 2024 截止7.24

ICSOC是CCF B类会议&#xff08;软件工程/系统软件/程序设计语言&#xff09; 2023年长文短文录用率22% Focus Area 4: Emerging Technologies Quantum Service Computing Digital Twins 3D Printing/additive Manufacturing Techniques Blockchain Robotic Process Autom…

nextjs渲染篇

1 服务器组件 默认情况下&#xff0c;Next.js 使用服务器组件。 1.1 服务器组件是如何呈现的&#xff1f; 在服务器上&#xff0c;Next.js 使用 React 的 API 来编排渲染。渲染工作被拆分为多个块&#xff1a;按单个路段和Suspense 每个区块分两个步骤呈现&#xff1a; Re…

C++:vector的介绍

C的vector是一种可变长度的动态数组&#xff0c;被广泛用于C编程中。它是标准模板库&#xff08;STL&#xff09;中的容器之一&#xff0c;提供了比原始数组更灵活和方便的操作。 vector可以存储任意类型的元素&#xff0c;包括基本类型&#xff08;如整数和浮点数&#xff09…

计算机java项目|springboot校园一卡通

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

离线缓存问题的最优解决方案——将来最远策略

离线缓存问题的最优解决方案——将来最远策略 a. 将来最远策略的缓存管理器伪代码及运行时间分析b. 离线缓存问题的最优子结构性质证明c. 将来最远策略的最优性证明及示例 在现代计算机系统中&#xff0c;缓存技术是提高数据处理速度的关键。通过将主存中的少量数据保存在容量小…

SQL表连接详解:JOIN与逗号(,)的使用及其性能影响

省流版 在这个详细的解释中&#xff0c;我们将深入探讨SQL中表连接的概念&#xff0c;特别是JOIN和逗号&#xff08;,&#xff09;在连接表时的不同用法及其对查询性能的影响。通过实际示例和背后的逻辑分析&#xff0c;我们将揭示在不同场景下选择哪种连接方式更为合适。 1.…

Android—— log的记忆

一、关键log 1.Java的 backtrace(堆栈log) 上述是一个空指针异常&#xff0c;问题出现在sgtc.settings&#xff0c;所以属于客户UI问题。 2.WindowManager(管理屏幕上的窗口和视图层次结构) 3.ActivityManager(管理应用程序生命周期和任务栈) 4.wifi操作 (1) 连接wifi&#…

90天玩转Python—18—Python面向对象编程:核心概念详解

90天玩转Python系列文章目录 90天玩转Python—01—基础知识篇:C站最全Python标准库总结 90天玩转Python--02--基础知识篇:初识Python与PyCharm 90天玩转Python—03—基础知识篇:Python和PyCharm(语言特点、学习方法、工具安装) 90天玩转Python—04—基础知识篇:Pytho…

golang实现获取切片的交集和差集

一.交集 1.两个切片的交集 采用map实现取两个切片的交集,其中如果map[key]访问成功&#xff0c;即有key这个键&#xff0c;ok为true&#xff0c;否则ok为false。,以string类型为例&#xff1a; //a 切片, b 切片 func intersect(a []string, b []string) []string {//初始化一…

WebStorm 2024.1.1 JavaScript集成开发环境 mac/win

JetBrains WebStorm是Jetrains拥有的一款JavaScript前端开发工具。被中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaSscript IDE”。与Int elliJ IDEA同源&#xff0c;继承了Int elliJ IDEA强大的JS功能。 主要功能包括智能代码补全、代码格式…

Spring是如何解决循环依赖的

简介 所谓循环依赖指的是&#xff1a;BeanA对象的创建依赖于BeanB&#xff0c;BeanB对象的创建也依赖于BeanA&#xff0c;这就造成了死循环&#xff0c;如果不做处理的话势必会造成栈溢出。Spring通过提前曝光机制&#xff0c;利用三级缓存解决循环依赖问题。 变量描述single…

【AI开发:音频】一、GPT-SoVITS整合工具包的部署问题解决(GPU版)

前言 目前GPT-SoVITS的合成效果比较不错&#xff0c;相比较其他厂商的产品要规整的多。众多厂家中也是国内使用最多的一款了&#xff0c;并且这个整合包里携带了&#xff0c;除背景音、切割、训练、微调、合成、低成本合成等一些列完整的工具&#xff0c;也可以作为API进行使用…

在IDEA运行spark程序(搭建Spark开发环境)

建议大家写在Linux上搭建好Hadoop的完全分布式集群环境和Spark集群环境&#xff0c;以下在IDEA中搭建的环境仅仅是在window系统上进行spark程序的开发学习&#xff0c;在window系统上可以不用安装hadoop和spark&#xff0c;spark程序可以通过pom.xml的文件配置&#xff0c;添加…