一、长短链接
1、短连接
对于短链接,在写完这个项目的理解是这样的,对于发给后端的每一个请求都有一个新建socket,当完成前端和后端的通信或者断开时即可给这个socket关闭。还有写的过程中查询资料室知的一些知识点。
(1)短链接的优点
a.对于服务器的资源占用较少
b.相对长连接实现起来相对简单
c.服务器不需要记录用户端的状态
(2)短链接的缺点
a.会频繁的和后端建立链接,断开连接
b.延迟高,不利于实时通信
c.不适用于高并发,很消耗后端的CPU和内存
(3)短链接的应用场景
a.低频请求
b.无状态服务(包括静态资源请求下载,登录注册忘记密码)
c.高安全性要求(银行等交易场所,避免信息被盗窃)
以下就是短链接的基本实现代码
用户端
import java.io.*;
import java.net.Socket;
public class ShortConnectionClient {
public static void main(String[] args) {
String serverAddress = "localhost";
int port = 8080;
try {
// 每次请求都创建新的Socket连接
Socket socket = new Socket(serverAddress, port);
// 获取输出流,向服务器发送数据
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello Server!");
// 获取输入流,读取服务器响应
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String response = in.readLine();
System.out.println("Server response: " + response);
// 关闭连接
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
后端接收
import java.io.*; import java.net.ServerSocket; import java.net.Socket;
public class ShortConnectionServer { public static void main(String[] args) { int port = 8080; try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println("Server started on port " + port); while (true) { // 接受客户端连接(每次都是新的连接) Socket clientSocket = serverSocket.accept(); System.out.println("Client connected: " + clientSocket.getInetAddress()); // 处理客户端请求 handleClient(clientSocket); } } catch (IOException e) { e.printStackTrace(); } } private static void handleClient(Socket clientSocket) { try ( BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true) ) { // 读取客户端消息 String message = in.readLine(); System.out.println("Received from client: " + message); // 发送响应 out.println("Hello Client! Your message: " + message); // 关闭连接(短链接特点) clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
观了别人写的代码后我自己又突然有了想法,是不是后端可以有多个端口,负责不同的业务,于是我进行了查询,以下便是我的理解。
后端多端口
对于后端多个端口,每个端口都有自己负责的业务,可以有短链接请求端口,长连接建立端口,以及资源等其他业务请求端口。
(1)优点
a.业务需求多样化
b.性能优化
c.安全性 && 隔离
2.长连接
对于长连接,我的理解是当用户登录之后进入了主界面时,长连接也要建立了,而建立长连接,则是通过前端和后端while循环来建立,源源不断的看是否有请求的传递。
(1)长连接的优点
a.减少连接建立和断开的开销
b.适合频繁通信的场景
c.保持会话状态
d.更快的响应速度
(2)长连接的缺点
a.占用服务器资源
b.需要心跳机制维护
c.网络环境适应性较
d.复杂性较高
(3)长连接的应用场景
a.实时通信(qq,微信这种聊天软件等)
b.高频短数据交互
c.数据库/缓存连接池
d.API网关/微服务通信
长连接基本代码实现
用户端
import java.io.*; import java.net.*; import java.util.concurrent.*;
public class LongConnectionClient { private static final String HOST = "localhost"; private static final int PORT = 8888; private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public static void main(String[] args) { try { Socket socket = new Socket(HOST, PORT); socket.setSoTimeout(5000); // 设置读取超时 PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream())); // 发送业务消息 for (int i = 0; i < 5; i++) { String message = "业务消息_" + i; out.println(message); String response = in.readLine(); System.out.println("收到响应:" + response); Thread.sleep(2000); } // 关闭连接 scheduler.shutdown(); socket.close(); } catch (Exception e) { e.printStackTrace(); } }
private static void startHeartbeat(PrintWriter out) { // 每10秒发送一次心跳 scheduler.scheduleAtFixedRate(() -> { try { out.println("HEARTBEAT"); System.out.println("发送心跳"); } catch (Exception e) { e.printStackTrace(); } }, 0, 10, TimeUnit.SECONDS); } }
服务器
import java.io.*; import java.net.*; import java.util.concurrent.*;
public class LongConnectionServer { private static final int PORT = 8888; private static final int MAX_THREADS = 100; private static final ExecutorService threadPool = Executors.newFixedThreadPool(MAX_THREADS);
public static void main(String[] args) { try (ServerSocket serverSocket = new ServerSocket(PORT)) { System.out.println("长连接服务端启动,监听端口:" + PORT); while (true) { Socket clientSocket = serverSocket.accept(); // 设置读写超时时间(毫秒) clientSocket.setSoTimeout(30000); // 长连接线程 threadPool.execute(new ClientHandler(clientSocket)); } } catch (IOException e) { e.printStackTrace(); } }
static class ClientHandler implements Runnable { private final Socket socket; private long lastHeartbeatTime;
public ClientHandler(Socket socket) { this.socket = socket; this.lastHeartbeatTime = System.currentTimeMillis(); }
@Override public void run() { try (BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter( socket.getOutputStream(), true)) { System.out.println("客户端连接:" + socket.getRemoteSocketAddress());
String inputLine; while ((inputLine = in.readLine()) != null) { // 心跳检测 if ("HEARTBEAT".equals(inputLine)) { lastHeartbeatTime = System.currentTimeMillis(); out.println("HEARTBEAT_ACK"); continue; } // 处理业务逻辑 System.out.println("收到消息:" + inputLine); String response = "处理结果:" + inputLine; out.println(response); // 检查连接是否超时(30秒无心跳) if (System.currentTimeMillis() – lastHeartbeatTime > 30000) { System.out.println("连接超时,关闭连接"); break; } } } catch (SocketTimeoutException e) { System.out.println("读取超时,关闭连接"); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); System.out.println("连接关闭:" + socket.getRemoteSocketAddress()); } catch (IOException e) { e.printStackTrace(); } } } } }
二、序列化
1.序列化是将对象转换为字节流的过程,以便存储或传输;
2.反序列化则是将字节流恢复为对象。它在分布式系统、数据持久化和网络通信中起着关键作用。
3. 序列化的核心作用
(1)对象持久化(存储到文件/数据库)
-
将内存中的对象保存到磁盘,程序重启后可恢复。
-
示例:游戏存档、用户会话保存。
(2)网络传输(跨进程/跨机器通信)
-
对象 → 字节流 → 网络传输 → 字节流 → 对象。
-
示例:RPC调用、微服务通信。
(3)深拷贝(Deep Copy)
-
通过序列化/反序列化实现对象的完全复制。
-
示例:避免Java的浅拷贝问题。
(4)跨语言数据交换
-
通用序列化格式(如JSON、Protocol Buffers)支持不同语言解析。
-
示例:Java服务与Python客户端通信。
4. 序列化的核心意义
(1)解决对象传输问题
-
网络只能传输字节流,序列化是对象传输的基础。
(2)实现分布式系统通信
-
微服务、RPC、消息队列(如Kafka)依赖序列化传递对象。
(3)保证数据一致性
-
序列化协议定义了数据的编码规则,避免解析错误。
(4)提高系统扩展性
-
通过版本兼容的序列化协议(如Protobuf),支持字段动态增减。
四、IO流
在项目中,IO流主要用于文件读写和网络数据传输,核心使用了以下类:
1. 字节流(Byte Streams)
FileInputStream`/`FileOutputStream 用于读写二进制文件(如图片、视频)。
// 读取文件 try (FileInputStream fis = new FileInputStream("file.bin")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { // 处理数据 } }
// 写入文件 try (FileOutputStream fos = new FileOutputStream("output.bin")) { fos.write(dataBytes); }
2. 字符流(Character Streams)
BufferedReader`/`BufferedWriter
// 读取文本文件 try (BufferedReader br = new BufferedReader(new FileReader("config.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } }
// 写入文本文件 try (BufferedWriter bw = new BufferedWriter(new FileWriter("log.txt"))) { bw.write("Log message"); bw.newLine(); }
3. 对象流(Object Streams)
ObjectInputStream`/`ObjectOutputStream 用于序列化和反序列化Java对象(如用户数据持久化)。
// 序列化对象到文件 try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("user.dat"))) { oos.writeObject(user); }
// 从文件反序列化对象 try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream("user.dat"))) { User user = (User) ois.readObject(); }
4. 网络IO(Socket流)
Socket.getInputStream()`/`Socket.getOutputStream() 用于网络通信,结合`BufferedReader`和`PrintWriter`处理文本协议。
// 服务端读取客户端数据 try (BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream()))) { String message = in.readLine(); }
// 客户端发送数据到服务端 try (PrintWriter out = new PrintWriter( socket.getOutputStream(), true)) { out.println("Hello Server!"); }
五、多线程在项目中的应用
多线程主要用于并发处理客户端请求和异步任务。
1. 线程池管理
ExecutorService 避免频繁创建/销毁线程,提升性能。
ExecutorService threadPool = Executors.newFixedThreadPool(10); threadPool.execute(() -> { // 处理客户端请求 });
2. 线程同步
synchronized`关键字 保护共享资源(如数据库连接池)。
public synchronized void updateData() { // 线程安全操作 }
六、文件分片上传/下载
通过IO流实现大文件的分块处理。
1. 文件分片上传
// 客户端分片发送 try (FileInputStream fis = new FileInputStream("large_file.zip"); BufferedInputStream bis = new BufferedInputStream(fis)) { byte[] buffer = new byte[1024 * 1024]; // 1MB分片 int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { socket.getOutputStream().write(buffer, 0, bytesRead); } }
2. 服务端分片接收
try (FileOutputStream fos = new FileOutputStream("received_file.zip"); BufferedOutputStream bos = new BufferedOutputStream(fos)) { byte[] buffer = new byte[1024 * 1024]; int bytesRead; while ((bytesRead = socket.getInputStream().read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); } }
七、MySQL数据库操作
使用JDBC进行CRUD操作。
1. 数据库连接
String url = "jdbc:mysql://localhost:3306/mydb"; String user = "root"; String password = "123456"; try (Connection conn = DriverManager.getConnection(url, user, password)) { // 执行SQL }
2. 查询数据
String sql = "SELECT * FROM users WHERE id = ?"; try (PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setInt(1, 1001); ResultSet rs = stmt.executeQuery(); while (rs.next()) { String name = rs.getString("name"); } }
3. 插入/更新数据
String sql = "INSERT INTO users (name, age) VALUES (?, ?)"; try (PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, "Alice"); stmt.setInt(2, 25); stmt.executeUpdate(); }
总结
| 技术 | 在项目中的应用 | |————-|——————————————————————| | IO流 | 文件读写、网络数据传输、对象序列 | | 多线程 | 并发处理客户端请求任务执行 | | 文件分片 | 大文件的上传与下载 | | MySQL | 用户数据存储、查询和更新 | | JavaFX | 构建图形界面,与后端Socket/数据库交互 |
通过结合这些技术,项目实现了:
1.客户端与服务端的稳定通信(长短链接结合) 2.高效文件传输(分片处理) 3.数据持久化(MySQL + 序列化) 4.用户友好的图形界面(JavaFX)
一、算法题
1.
解题思路:正常的并查集,加一个map来实现string->int即可
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#include <climits>
#include <algorithm>
#include <map>
#define ll long long
using namespace std;
int n, m, p;
int find(int x, vector<int>& parent) {
if (parent[x] != x)
parent[x] = find(parent[x], parent);
return parent[x];
}
void setUnion(int a, int b, vector<int>& parent, vector<int>& rank) {
a = find(a,parent);
b = find(b,parent);
if (a == b)
return;
int x1 = min(a, b);
int x2 = max(a, b);
if (rank[x1] > rank[x2])
parent[x2] = x1;
else if (rank[x1] < rank[x2])
parent[x2] = x1;
else {
parent[x1] = x2;
rank[x2]++;
}
}
bool isConnect(int a, int b, vector<int>& parent) {
a = find(a, parent);
b = find(b, parent);
return a == b;
}
int main() {
cin >> n >> m;
vector<int>parent(n + 1, 0); // 节点的上一级
map<string,int> tt;
vector<int>rank(n + 1, 0); // 建立最小的树
for (int i = 1; i <= n; i++) { // 先指向自身
parent[i] = i;
string s; cin >> s;
tt[s] = i;
}
for (int i = 1; i <= m; i++) { // 建立亲戚关系
string a, b; cin >> a >> b;
int x1 = tt[a], x2 = tt[b];
setUnion(x1, x2, parent, rank);
}
cin >> p;
for (int i = 1; i <= p; i++) {
string a, b; cin >> a >> b;
int x1 = tt[a], x2 = tt[b];
if (isConnect(x1, x2, parent))
cout << "Yes." << endl;
else
cout << "No." << endl;
}
return 0;
}
2.
解题思路: 并查集,限定了合并条件,根据题目的合并条件合成即可
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#include <climits>
#include <algorithm>
#include <map>
#define ll long long
using namespace std;
map<string, string> parent;
// 查找并返回name的最早祖先,并进行路径压缩
string findAncestor(string name) {
if (name != parent[name]) {
parent[name] = findAncestor(parent[name]);
}
return parent[name];
}
int main() {
char prefix;
string name;
string current_parent;
cin >> prefix;
while (prefix != '$') {
cin >> name;
if (prefix == '#') {
current_parent = name;
if (parent.find(name) == parent.end()) {
parent[name] = name; // 初始化父亲为自身
}
} else if (prefix == '+') {
parent[name] = current_parent;
} else if (prefix == '?') {
cout << name << ' ' << findAncestor(name) << endl;
}
cin >> prefix;
}
return 0;
}
3.
解题思路:把题目看成图,每个点都是顶点,距离都是权重,就可以用kruskal了。
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <iomanip>
using namespace std;
struct Edge {
int u, v;
double w;
bool operator<(const Edge& other) const {
return w < other.w;
}
};
vector<Edge> edges;
vector<int> parent;
int S, P;
vector<pair<int, int>> d;
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unionset(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootY] = rootX;
}
}
double kruskal() {
parent.resize(P + 1);
for (int i = 1; i <= P; i++) {
parent[i] = i;
}
sort(edges.begin(), edges.end());
double ans = 0.0;
int k = P – S;
for (const auto& edge : edges) {
if (find(edge.u) != find(edge.v)) {
unionset(edge.u, edge.v);
ans = edge.w;
k–;
if (k == 0) {
break;
}
}
}
return ans;
}
int main() {
cin >> S >> P;
d.resize(P + 1);
for (int i = 1; i <= P; i++) {
cin >> d[i].first >> d[i].second;
}
for (int i = 1; i <= P; i++) {
for (int j = i + 1; j <= P; j++) {
double dx = d[i].first – d[j].first;
double dy = d[i].second – d[j].second;
double dis = sqrt(dx * dx + dy * dy);
edges.push_back({ i, j, dis });
}
}
double ans = kruskal();
cout << fixed << setprecision(2) << ans << endl;
return 0;
}
4.
解题思路:开始想了好久,发现N<=6,这个数量好像可以直接遍历所有的情况来讨论,但是对于面积的计算还是有点迷,就看了下别人的思路。让放下的油滴尽可能的扩大,相加所有的面积,每次枚举都取面积的最大值
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <iomanip>
using namespace std;
double area(int N, const vector<pair<int, int>>& points, int left, int right, int bottom, int top) {
double max_total_area = 0.0;
vector<int> indices(N);
for (int i = 0; i < N; ++i) {
indices[i] = i;
}
do {
vector<double> radii(N, 0.0);
double total_area = 0.0;
for (int i = 0; i < N; ++i) {
int x = points[indices[i]].first;
int y = points[indices[i]].second;
double min_r = min({x – left, right – x, y – bottom, top – y});
for (int j = 0; j < i; ++j) {
int xj = points[indices[j]].first;
int yj = points[indices[j]].second;
double dx = x – xj;
double dy = y – yj;
double distance = sqrt(dx * dx + dy * dy);
min_r = min(min_r, distance – radii[j]);
if (min_r <= 0) break;
}
if (min_r > 0) {
radii[i] = min_r;
total_area += M_PI * min_r * min_r;
}
}
if (total_area > max_total_area) {
max_total_area = total_area;
}
} while (next_permutation(indices.begin(), indices.end()));
double area_rect = (right – left) * (top – bottom);
return area_rect – max_total_area;
}
int main() {
int N;
cin >> N;
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
int left = min(x1, x2);
int right = max(x1, x2);
int bottom = min(y1, y2);
int top = max(y1, y2);
vector<pair<int, int>> points(N);
for (int i = 0; i < N; ++i) {
cin >> points[i].first >> points[i].second;
}
sort(points.begin(), points.end());
double remaining_area = area(N, points, left, right, bottom, top);
cout << static_cast<int>(round(remaining_area)) << endl;
return 0;
}
5.
解题思路:第一次写这种题,我最开始就是直接用dfs来搜索,但是时间超限,然后我看了一下别人的思路,可以用个二维数组存储已经知道的每个的位置的最大值,当dfs到这个位置,直接从二维数组里取出来就行了,这个方法也是记忆化搜索
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#include <climits>
#include <algorithm>
#include <map>
#define ll long long
using namespace std;
// 定义四个方向
int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
// 全局变量存储矩阵和dp数组
vector<vector<int>> matrix;
vector<vector<int>> dp;
int R, C;
// DFS函数,返回从(x, y)出发的最长滑坡长度
int dfs(int x, int y) {
// 如果已经计算过,直接返回结果
if (dp[x][y] != -1) {
return dp[x][y];
}
// 初始化为1,因为至少可以滑到自己
dp[x][y] = 1;
// 遍历四个方向
for (int i = 0; i < 4; ++i) {
int newX = x + dirs[i][0];
int newY = y + dirs[i][1];
// 检查边界条件和高度是否下降
if (newX >= 0 && newX < R && newY >= 0 && newY < C && matrix[newX][newY] < matrix[x][y]) {
// 递归计算相邻点的最长滑坡长度,并更新当前点的dp值
dp[x][y] = max(dp[x][y], dfs(newX, newY) + 1);
}
}
return dp[x][y];
}
int main() {
// 读取行数和列数
cin >> R >> C;
matrix.assign(R, vector<int>(C));
for (int i = 0; i < R; ++i) {
for (int j = 0; j < C; ++j) {
cin >> matrix[i][j];
}
}
// 初始化dp数组为-1,表示未计算
dp.assign(R, vector<int>(C, -1));
// 遍历所有点,计算最长滑坡长度
int maxLength = 0;
for (int i = 0; i < R; ++i) {
for (int j = 0; j < C; ++j) {
if (dp[i][j] == -1) { // 如果尚未计算
dfs(i, j);
}
maxLength = max(maxLength, dp[i][j]);
}
}
// 输出结果
cout << maxLength << endl;
return 0;
}
评论前必须登录!
注册