网络通讯聊天工具的实现

学习网络与通信,实现聊天界面能够通过服务器进行私聊和群聊的功能。

1.服务器:ServeSocket

客户端先发送消息给服务器,服务器接受消息后再发送给客户端。

利用服务器随时监听。等待客户端的请求,一旦有请求便生产一个socket套接字用于双方之间的数据传输。在此需要指定服务器的端口号。

端口号:区分一个IP地址中套接字。

package ChatV2.Server;
import java.io.IOException;
import java.net.ServerSocket;public class Server {private int port = 50001;ServerSocket serverSocket;{try {serverSocket = new ServerSocket(port);System.out.println("创建服务端成功");ListenerRunnable lr = new ListenerRunnable(serverSocket);new Thread(lr).start();} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) {new Server();}
}

同时在服务器中我还创建了两个线程,一个用于不断接受新的客户端的连接,一个用于与各客户端的发送接受消息。

1.1接受客户端连接的线程

这个线程需要接受用户端的连接,那么就需要创建的serverSocket对象,利用ServerSocket.accept();方法接受对象,后接受其创建后返回的socket对象。此方法为阻塞方法,一直等待客户端的接入。

并且将此对象放入创建的Socket套接字的队列中,以便使用。

package ChatV2.Server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ArrayBlockingQueue;public class ListenerRunnable implements Runnable{private ServerSocket serverSocket;private Socket socket;private OutputStream os;//存放接入的socketprivate static ArrayBlockingQueue<Socket> sockets = new ArrayBlockingQueue<>(10);public ListenerRunnable(ServerSocket serverSocket) {this.serverSocket = serverSocket;}public ListenerRunnable() {}@Overridepublic void run() {while (true) {try {socket = serverSocket.accept();//接受,阻塞方法,//存入到sockets中sockets.offer(socket);System.out.println(socket.getPort() + "连接成功");//savePort();InputStream is = socket.getInputStream();//创建接受信息的线程InformationRunnable ir = new InformationRunnable(is,socket,sockets);//发送给其他所有用户 当前用户的端口号String socketMsg = "3#";for (int i = 0; i < sockets.size(); i++) {Socket temSocket = sockets.poll();System.out.println("发送端口号");socketMsg += temSocket.getPort() + "#";sockets.offer(temSocket);}groupOutput(socketMsg);new Thread(ir).start();} catch (IOException e) {throw new RuntimeException(e);}}}//群发所有端口public void groupOutput(String msg) {try {int size = sockets.size();for (int i = 0; i < size; i++) {System.out.println("多少个用户端:" + sockets.size());Socket temSocket = sockets.poll();os = temSocket.getOutputStream();output(msg);sockets.offer(temSocket);}} catch (IOException e) {throw new RuntimeException(e);}}public void output(String msg) {try {byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8);//转化为字节型int ml = msgBytes.length;os.write(ml);//大小for (int i = 0; i < msgBytes.length; i++) {os.write(msgBytes[i]);}} catch (IOException e) {throw new RuntimeException(e);}}}

同时在此接受到新客户端的连接后,将此时所有的端口号再转发给所有的客户端用于私聊功能的实现。

输出流的写入实现方法:

利用套接字中OutputStream 与 InputStream 来读取与写入字符

每次写入与读取只能写入/读取一个字节,那么就会存在多个字节如何组成我们想要的字符,以及接受端究竟该接受多少个字符才能写入段的一句完成的对话的问题。

第一个问题的解决:

        只需使用相同的字符编码方式,将字符串类型转化为字节类型中选择UTF-8的字符编码方式,接受端也是用此编码方式 即可保证为相同的字符。

第二个问题的解决:

        我们将字符串转化为字节类型,用数组存储。先将此数组的大小传过去,之后接收端创建相同大小的数组用于接受,接受完后转为字符串即可。

            int ml = is.read();//用于接受字节大小byte[] msgBytes = new byte[ml];//发过来的一串信息for (int i = 0; i < msgBytes.length; i++) {int readByte = is.read();msgBytes[i] = (byte) readByte;}String msg = new String(msgBytes, StandardCharsets.UTF_8);

1.2接受消息与发送消息的线程

这个线程需要判断发过来的信息是群发还是私聊。我的发送过来的消息的格式为“ 字符1# 字符2”如果字符21为0则为群聊,字符二为发送的消息。“字符一#字符二#字符三”如果字符1 为1则为私聊,此时字符二为私聊的端口号,字符三为消息。用‘#’进行分割。

package ChatV2.Server;import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ArrayBlockingQueue;public class InformationRunnable implements Runnable {private InputStream is;private OutputStream os;private Socket socket;private ArrayBlockingQueue<Socket> sockets;public InformationRunnable(InputStream is, Socket socket, ArrayBlockingQueue<Socket> sockets) {this.is = is;this.socket = socket;this.sockets = sockets;}@Overridepublic void run() {while (true) {getIMG();}}//接受消息public void getIMG() {try {System.out.println("客户端说:");int ml = is.read();//用于接受字节大小byte[] msgBytes = new byte[ml];//发过来的一串信息for (int i = 0; i < msgBytes.length; i++) {int readByte = is.read();msgBytes[i] = (byte) readByte;}String msg = new String(msgBytes, StandardCharsets.UTF_8);String[] msg1 = msg.split("#");//分开if (msg1[0].equals("0")) {//群聊System.out.println("群聊消息:");groupOutput(msg1[1] + "#");} else {//私聊 确认发送对象System.out.println("私聊消息:");String msg0 = "";//发送给发送信息的人int size = sockets.size();for (int i = 0; i < size; i++) {System.out.println("查找私聊对象");Socket temSocket = sockets.poll();if (msg1[1].equals(String.valueOf(temSocket.getPort()))) {//给私聊对象发送os = temSocket.getOutputStream();msg1[2] = "私聊:" + msg1[2] +"#";msg0 = "私聊给:" + msg1[1] + msg1[2] +"#";output(msg1[2]);}sockets.offer(temSocket);}//发送给消息对象os = socket.getOutputStream();output(msg0);}} catch (IOException e) {throw new RuntimeException(e);}}//群发方式public void groupOutput(String msg) {try {int size = sockets.size();for (int i = 0; i < size; i++) {System.out.println("多少个用户端:" + sockets.size());Socket temSocket = sockets.poll();os = temSocket.getOutputStream();output(msg);sockets.offer(temSocket);}} catch (IOException e) {throw new RuntimeException(e);}}//发送消息public void output(String msg) {try {byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8);//转化为字节型int ml = msgBytes.length;os.write(ml);//大小for (int i = 0; i < msgBytes.length; i++) {os.write(msgBytes[i]);}} catch (IOException e) {throw new RuntimeException(e);}}//以端点命名的文件,将端点及聊天内容全部写入文件过来。public synchronized void saveInformation(String information) throws IOException {String path = "C:\\Users\\15697\\IdeaProjects\\Pro24\\src\\Chat\\UsersInformation\\document";FileWriter fw = new FileWriter(path, true);BufferedWriter bw = new BufferedWriter(fw);bw.write(information);bw.write("#");//分割消息System.out.println("写入消息:" + information);bw.close();}
}

将字节数组转化为字符串后,里面split("#");方法将字符串分割为多个字符串数组msg1。判断msg1[0]是否为0来区分群聊或者私聊。

1.21群聊的实现

判断后调用groupOutput()群聊方法。

在这方法会将我们先前存入的socket队列一 一取出并 使用它的outputStream发送消息

    public void groupOutput(String msg) {try {int size = sockets.size();for (int i = 0; i < size; i++) {System.out.println("多少个用户端:" + sockets.size());Socket temSocket = sockets.poll();os = temSocket.getOutputStream();output(msg);sockets.offer(temSocket);}} catch (IOException e) {throw new RuntimeException(e);}}//发送消息public void output(String msg) {try {byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8);//转化为字节型int ml = msgBytes.length;os.write(ml);//大小for (int i = 0; i < msgBytes.length; i++) {os.write(msgBytes[i]);}} catch (IOException e) {throw new RuntimeException(e);}}

1.22私聊的实现

         与群聊不同的是他需要判断私聊的对象。将各个套接字取出后对比msg1[1]与各个套接字的端口号。找到了则取出它的输出流,设置发送的字符,调用output()方法即可。同时需要发送给消息的发送者,让其显示在界面上。

                System.out.println("私聊消息:");String msg0 = "";//发送给发送信息的人int size = sockets.size();for (int i = 0; i < size; i++) {System.out.println("查找私聊对象");Socket temSocket = sockets.poll();if (msg1[1].equals(String.valueOf(temSocket.getPort()))) {//给私聊对象发送os = temSocket.getOutputStream();msg1[2] = "私聊:" + msg1[2] +"#";msg0 = "私聊给:" + msg1[1] + msg1[2] +"#";output(msg1[2]);}sockets.offer(temSocket);}//发送给消息对象os = socket.getOutputStream();output(msg0);

到此服务器的接受与发送功能以实现。

2.客户端:

需要知道服务器的ip以及端口号,设置即可

package ChatV2.Client;import ChatV2.DATA;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;public class Clint implements DATA {private Socket socket;//IP 端点public int getPort() {return socket.getLocalPort();}private OutputStream os;private InputStream is;public InputStream getInputStream() {return is;}{try {socket = new Socket("127.0.0.1", 50001);os = socket.getOutputStream();is = socket.getInputStream();} catch (IOException e) {/* throw new RuntimeException(e);*/System.out.println("无法连接服务器");}}public void output(String msg, int index) {try {//使用#隔开 第一个为类型,第二个为端口号,第三部分为内容 0为私聊String s = "";if (IMG[1] == 0) {s += IMG[1] + "#" + msg;} else {s += IMG[1] + "#" + PORT.get(index) + "#" + msg;}System.out.println("待发送消息:" + s);byte[] msgBytes = s.getBytes(StandardCharsets.UTF_8);int ml = msgBytes.length;os.write(ml);for (int i = 0; i < ml; i++) {os.write(msgBytes[i]);}} catch (IOException e) {throw new RuntimeException(e);}}
}

同时我在里面写了一个发送给服务器信息的方法output(String msg,int index)msg为发送的信息,格式与服务器接受的格式一致,index则为接受服务端发送端口号后,存入的信息数组的下标,以便确认私聊对象。

其他的交给界面UI来实现。

3.聊天界面:

结构:以及在此调用用户端。

DATA接口中创建了存放界面需要多次使用的数据。用户的端口号,以及生成的单选按钮,选择聊天对象,以及数据。


import javax.swing.*;
import java.util.ArrayList;public interface DATA {//存放用户ArrayList<String> PORT = new ArrayList<>();//用来存放按钮,表示各用户JRadioButton[] JRB = new JRadioButton[10];//下标0为记录连接人数以及群聊。1为聊天方式为群聊或私聊int[] IMG = new int[2];
}

界面的设置不多说:

package ChatV2;
import ChatV2.Client.Clint;import javax.swing.*;
import java.awt.*;public class ChatUI extends JFrame implements DATA{Clint clint;public ChatUI() {setTitle("MyChat");setSize(900, 700);setLayout(null);setDefaultCloseOperation(EXIT_ON_CLOSE);//按钮组ButtonGroup bg = new ButtonGroup();//setLayout(new FlowLayout());JLabel jl = new JLabel("群聊");//输入框JTextArea jta = new JTextArea();jta.setBounds(10, 500, 700, 200);//JScrollPane scrollPane = new JScrollPane(jta);//翻页的作用//scrollPane.setJButton jb = new JButton("发送");//需添加监听jb.setBounds(710, 550, 75, 50);ButtonListener bl = new ButtonListener(jta);jb.addActionListener(bl);//显示框:JTextArea chatArea = new JTextArea( );//几行几列// chatArea.setPreferredSize(new Dimension(500,480));chatArea.setBounds(10,10,700,480);//设置不可编辑chatArea.setEditable(false);//添加滚动功能JScrollPane scrollPane = new JScrollPane(chatArea);add(scrollPane,BorderLayout.CENTER);//放置界面的中心add(chatArea);//刷新列表按钮JButton jb1 = new JButton("刷新列表");jb1.addActionListener(bl);jb1.setBounds(750,30,100,30);JRadioButton jrb1 = new JRadioButton("群聊");bg.add(jrb1);IMG[0] = 1;JRB[IMG[0]-1] = jrb1;add(jrb1);add(jb1);add(jb);add(jta);add(jl);setVisible(true);clint = new Clint();bl.clint = clint;//用户端传过去bl.ui = this;bl.chatArea = chatArea;Thread thread = new Thread(new ChatRunnable(chatArea,clint.getInputStream(),this,bg));thread.start();}@Overridepublic void paint(Graphics g) {super.paint(g);System.out.println("加载列表");System.out.println("人数:"+IMG[0]);if(IMG[0] != 0){for (int i = 0; i <= IMG[0]; i++) {JRB[i].setBounds(750,80+i*40,100,30);}}}public static void main(String[] args) {new ChatUI();}
}

注:重写paint()方法,对单选按钮进行位置及大小的设置。

同时需要将单选按钮加入一个组 ButtonGroup创建的组中才能实现不能多选的功能。

同时需要按钮的监听,以及一个随时接受消息,并显示在聊天区域的线程。线程在打开UI时就启动。

3.1按钮监听的实现内容:

目前有两个按钮,一个为“发送”,一个为“刷新列表”。

“发送”:发送信息给服务器。获取文本后调用clint中output()的方法。实现发送信息

“刷新列表”:调用repaint()进行重绘。当有新的客户端接入的时候则点击“刷新列表”,即可选择新的客户进行私聊。

package ChatV2;import ChatV2.Client.Clint;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;public class ButtonListener implements ActionListener, DATA {JTextArea jta;Clint clint;JFrame ui;JTextArea chatArea;public ButtonListener(JTextArea jta) {this.jta = jta;}@Overridepublic void actionPerformed(ActionEvent e) {String ae = e.getActionCommand();if (ae.equals("发送")) {//按一次发送一次文件System.out.println("获取文本");//获取文本,启动用户端String msg = clint.getPort()+":"+jta.getText();//获取后清空jta.setText(null);//发送信息int index = groupOrPrivate();clint.output(msg,index);System.out.println("下标"+ index);} else if (ae.equals("刷新列表")) {System.out.println("刷新列表");ui.repaint();}}//监控按钮此时为群聊或者私聊public int groupOrPrivate(){Boolean[] b = new Boolean[IMG[0]+1];for (int i = 0; i < b.length; i++) {b[i] = JRB[i].isSelected();}System.out.println(Arrays.toString(b));for (int i = 0; i < b.length; i++) {System.out.println("判断私聊or群聊");if(b[0]){IMG[1] = 0;//群聊System.out.println("群聊");return -1;} else if (b[i]) {System.out.println("私聊");IMG[1] = 1;//私聊//此时的i,端口。return i-1;}}System.out.println("判断失败,自动为群聊");return -1;}
}

实现了一个判断此时选择的是群聊还是私聊,以及选择私聊的对象的下标。

当为群聊返回-1,私聊则返回其存在DATA接口中端口号在PORT数组中的下标。如果没选则默认为群聊,返回-1;

3.2接受消息的线程:

package ChatV2;import javax.swing.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;public class ChatRunnable implements Runnable, DATA {InputStream is;JTextArea chatArea;JFrame ui;ButtonGroup bg;public ChatRunnable(JTextArea chatArea, InputStream is, JFrame ui, ButtonGroup bg) {this.is = is;this.chatArea = chatArea;this.ui = ui;this.bg = bg;}@Overridepublic void run() {while (true) {//接受消息try {int ml ;//用于接受字节大小ml = is.read();byte[] msgBytes = new byte[ml];//发过来的一串信息for (int i = 0; i < msgBytes.length; i++) {int readByte = is.read();msgBytes[i] = (byte) readByte;}String msg = new String(msgBytes, StandardCharsets.UTF_8);String[] msg1 = msg.split("#");if (msg1[0].equals("3")) {//为端口记录号System.out.println("接受消息为端口号");int size = msg1.length;int count = 0;PORT.clear();for (int i = 1; i < size; i++) {PORT.add(msg1[i]);count++;}IMG[0] = count;addUser();} else {sendMsg(msg1[0]);System.out.println("接受到消息:" + msg);}} catch (IOException e) {throw new RuntimeException(e);}}}public void sendMsg(String msg) {chatArea.append(msg + "\n");//发送后换行chatArea.setCaretPosition(chatArea.getDocument().getLength());//跳转到最新消息}//创建新的用户,并存入按钮中public void addUser() {for (int i = 1; i <= IMG[0]; i++) {JRadioButton jrb = new JRadioButton(PORT.get(i-1));JRB[i] = jrb;bg.add(jrb);ui.add(jrb);}System.out.println("已有新用户加入,共" + IMG[0] + "个用户连接,请刷新列表");}
}

方法:

1.sendMsg(String msg);将字符串msg添加到界面的聊天显示界面中,同时调转到发送消息的最下面。

2.addUser();将读取的端口号重新设置在新的单选按钮中,并添加好组,以及添加到界面。

线程功能:接受发送过来的字节,并转为字符串。接着将其用“#”进行分割为字符串数组。

msg1[0]为"3"则为发送的端口号,将后面的进行存储。其他则为私聊或者群聊的消息,调用sendMsg();显示在聊天区域即可。

聊天工具的最终实现效果:

一个用户连接:

第二个用户连接后:

第一个界面刷新前:刷新后:

第三个用户的加入:

聊天功能:

群聊:

私聊:未选择客户端接受不到。

不足:

功能不够齐全。加上文件的发送等功能。

代码的优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/333227.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js深入理解对象的 属性(properties)的特殊 特性(attributes)

对象 js对象 // 构造一个对象 let obj {}; let obj new Object(); 我们知道js中一切皆对象&#xff0c;对象是一个键值对集合&#xff08;key: value)&#xff0c;一个键(key)对应一个值(value)&#xff0c;而每个键都是这个对象的属性&#xff0c;我们可以通过对象的属性来…

Java绩效考核系统源码 springboot员工绩效考核系统源码

Java绩效考核系统源码 springboot员工绩效考核系统源码-009 源码下载地址&#xff1a;https://download.csdn.net/download/xiaohua1992/89352195 项目介绍 本系统的功能分为管理员和员工两个角色 管理员的功能有&#xff1a; &#xff08;1&#xff09;个人中心管理功能&a…

一点点 cv 经验 1:cv方向、模型评估、输入尺寸、目标检测器设计

一点点 cv 经验 1&#xff1a;cv方向、模型评估、输入尺寸、目标检测器设计 cv 方向Pytorch数据集划分 模型评估误差偏差方差噪声 输入尺寸方法一&#xff1a;让数据适应模型方法二&#xff1a;修改模型适应数据方法三&#xff1a;划分Patch&#xff0c;分别处理 目标检测器结构…

【Redis】 关于列表类型

文章目录 &#x1f343;前言&#x1f340;常见操作命令介绍&#x1f6a9;lpush&#x1f6a9;lpushx&#x1f6a9;rpush&#x1f6a9;rpushx&#x1f6a9;lrange&#x1f6a9;lpop&#x1f6a9;rpop&#x1f6a9;lindex&#x1f6a9;linsert&#x1f6a9;llen&#x1f6a9;lrem&…

Python3 笔记:Python之禅

打开Python Shell&#xff0c;输入import this&#xff0c;按回车键运行程序。 Beautiful is better than ugly. 优雅胜于丑陋。 Explicit is better than implicit. 明确胜于含糊。 Simple is better than complex. 简单胜于复杂。

Ansible02-Ansible Modules模块详解

目录 写在前面4. Ansible Modules 模块4.1 Ansible常用模块4.1.1 Command模块4.1.2 shell模块4.1.3 scrpit模块4.1.4 file模块4.1.5 copy模块4.1.6 lineinfile模块4.1.7 systemd模块4.1.8 yum模块4.1.9 get_url模块4.1.10 yum_repository模块4.1.11 user模块4.1.12 group模块4.…

在C++中自定义命名空间,在命名空间中定义string变量,同时定义一个函数实现单词逆置

代码 #include <iostream> #include <cstring> using namespace std; namespace my_space {string s;void reverse(string s);//定义逆置函数 } using namespace my_space; void my_space::reverse(string s){int lens.size();int i0;int jlen-1;while(i<j){//…

设计模式17——模板方法模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 模板方法模式&#xff08;Temp…

买车是小米su7还是model3?这个AI在我这里“干掉了”百万车评人

作者 | 曾响铃 文 | 响铃说 43天交付1万辆新车&#xff01;雷军的微博一发&#xff0c;又把小米汽车推上了热搜。 自小米su7问世以来&#xff0c;天天刷屏。说不心动&#xff0c;那是假的&#xff0c;身边好几个朋友都按捺不住要下订一台了。 但真要买&#xff0c;还是忍不住…

容器组件:页面和自定义组件生命周期(HarmonyOS学习第五课)

页面和自定义组件生命周期 先明确自定义组件和页面的关系&#xff1a; 自定义组件:Component装饰的UI单元&#xff0c;可以组合多个系统组件实现U的复用。 页面:即应用的UI页面。可以由一个或者多个自定义组件组成&#xff0c;Entry装饰的自定义组件为贞面的入口组件&#xf…

5月26(信息差)

&#x1f30d; 珠峰登顶“堵车”后冰架断裂 5人坠崖 2人没爬上来&#xff01; 珠峰登顶“堵车”后冰架断裂 5人坠崖 2人没爬上来&#xff01; &#x1f384; Windows 11 Beta 22635.3646 预览版发布&#xff1a;中国大陆地区新增“微软电脑管家”应用 ✨ 成都限购解除即将满…

DOS学习-目录与文件应用操作经典案例-ren

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.案例 案例 1&#xff1a;重命名当前目录下的文件 案例 2&#xff1a…

Postman快捷功能-快速填写请求头

大家好&#xff0c;之前给大家分享关于 Postman 工具的基础使用&#xff0c;今天给大家介绍一个快捷功能&#xff0c;可以一定程度提高我们使用 Postman 工具的效率&#xff0c;在我们进行接口测试时&#xff0c;几乎每个接口都需要填写 Headers&#xff0c;且 Headers 中的参数…

【leetcode面试经典150题】-80. 删除有序数组中的重复项 II

【leetcode面试经典150题】-80. 删除有序数组中的重复项 II 1 题目介绍2 个人解题思路2.1 代码2.2 思路 3 官方题解 1 题目介绍 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组…

一键接入大模型:One-Api本地安装配置实操,POSTMAN、APIFOX调用CURL

前言 最近准备学习一下 Semantic Kernel, OpenAI 的 Api 申请麻烦,所以想通过 One-api 对接一下国内的在线大模型,先熟悉一下 Semantic Kernel 的基本用法,本篇文章重点记录一下OneApi安装配置的过程。 讯飞星火有 3.5 模型的 200w 个人免费 token,可以拿来学习。 讯飞星…

推导2维镜像变换(Reflection Transform)的公式

我们知道2维的旋转变换公式为 Q ( cos ⁡ ( θ ) sin ⁡ ( θ ) − sin ⁡ ( θ ) cos ⁡ ( θ ) ) Q\left( \begin{matrix} \cos \left( \theta \right)& \sin \left( \theta \right)\\ -\sin \left( \theta \right)& \cos \left( \theta \right)\\ \end{matrix} \r…

UTF-8格式大统一:转码高效指南,彻底解决文件编码乱码问题!

文章目录 1 背景说明2 统一的好处3 对增量代码怎么进行统一4 对存量代码怎么进行统一4.1 指定单一文件夹&#xff0c;对里面的 .h .cpp 文件全转换4.2 指定单一文件夹&#xff0c;对里面的.h .cpp文件按需转换4.3 指定多文件夹&#xff0c;对里面的.h .cpp文件全部转换4.4 指定…

用数据,简单点!奇点云2024 StartDT Day数智科技大会,直播见

在充满挑战的2024&#xff0c;企业如何以最小化的资源投入和试错成本&#xff0c;挖掘新的增长机会&#xff0c;实现确定性发展&#xff1f; “简单点”是当前商业环境的应对策略&#xff0c;也是奇点云2024 StartDT Day的核心理念。 5月28日&#xff0c;由奇点云主办的2024 S…

RPA机器人流程自动化如何优化人力资源工作流程

人力资源部门在支持员工和改善整体工作环节方面扮演着至关重要的角色&#xff0c;但是在人资管理的日常工作中&#xff0c;充斥着大量基于规则的重复性任务&#xff0c;例如简历筛选、面试安排、员工数据管理、培训管理、绩效管理等&#xff0c;这些任务通常需要工作人员花费大…

Hive课程文档

基本语法 库操作 Hive和MySQL类似&#xff0c;提供了针对database的操作。 1)创建库&#xff1a; create database demo; 注意&#xff0c;每一个database在HDFS上都会对应一个目录&#xff0c;如果不指定&#xff0c;那么默认是放在/user/hive/warehouse/下。在Hive中&am…