Java实例 基于UDP及自建知识库的聊天机器人
01 涉及技术栈
- GUI界面:
Swing
组件库+GUI Form
布局设计 - C\S通信:
UDP
+线程管理 - 知识库:
IO
文件读写流+本地.txt
文件 - 日期处理:
Data
类+SimpleDateFormat
类(格式转换) - 其他知识: Java基础知识+面向对象编程+
String
字符串处理+异常处理等
02 运行效果展示
代码已上传仓库,切换分支后拉取。https://gitee.com/strivezhangp/java-demo.git 分支:Chatting
03 项目目录说明
04 程序工作流程说明
主要包含以下步骤:
-
Main()
进入程序 - 进行登录,输入用户名判断
- 判断成功,进入聊天室,同时创建客户端服务端UDP通信进程
- 客户端输入文字提问
- 服务端收到信息,创建IO流,进行本地知识库的读取,并作出相应的回复
- Data()获取当前时间,设置
Server
和Client
的聊天记录的展示
05 关键知识及代码
此处只展示关键代码,其余代码见Gitee仓库。
(1)文件读写流
包括文件的读写和字符串的处理,涉及到了String
类的一些方法:
-
startsWith()
:判断字符串是否一XX开始 -
substring()
:字符串的截取(获取子串)
文件读写流相关知识:
-
BufferedReader
:带缓冲的字符流的创建使用 -
FileReader
:文件读取流的创建使用
集合相关知识:
-
ArrayList
:集合的创建以及内容的读取,添加等
package fileIO;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
/**
* @description: 文件的读写流使用字符缓冲流进行
* @author:StrivePeng
*/
public class FileIo {
public static final String FILEPATH = "src/fileIO/知识库.txt";
private static BufferedReader reader; // 字符缓冲流
private ArrayList<String> answerList = new ArrayList<String>(); // 存放答案
private ArrayList<String> questionList = new ArrayList<String>(); // 存放问题
/**
* @throws IOException io异常
* @throws IllegalArgumentException 文件不符合预期格式
* 构造函数,读取文件数据存放到静态变量
*/
public FileIo() throws IOException, IllegalArgumentException {
try {
reader = new BufferedReader(new FileReader(FILEPATH)); // 初始化字符缓冲器
findKnowledge(); // 处理文件 存放变量集合中
} finally {
if (reader != null) {
// 关闭资源
reader.close();
}
}
}
/**
* @throws IOException io异常
* @description: 将知识库中的问题和答案分类存放到响应的数组中
* @author:StrivePeng
*/
private void findKnowledge() throws IOException {
// 按照行读取
String line;
while ((line = reader.readLine()) != null) {
// 判断是问题or答案
if (line.startsWith("答案")) {
String answer = line.substring("答案".length()); // 截取后面得字符串
answerList.add(answer);
}
if (line.startsWith("问题")) {
String question = line.substring("问题".length());
questionList.add(question);
}
}
}
public ArrayList<String> getAnswerList() {
return answerList;
}
public ArrayList<String> getQuestionList() {
return questionList;
}
}
(2)UDP通信相关
涉及到的一些知识点:
-
静态常量的创建和使用
-
DatagramSocket
数据流的创建和使用 -
DatagramPacket
数据包的创建和使用 -
客户端和服务端的意义所在:
UDP的特性使得它适用于广播通信。如果服务端监听的是广播地址,那么来自同一广播地址的多个客户端都能够发送消息给服务端。但在一般的应用场景中,客户端通常需要明确指定服务端的IP地址和端口号,以确保消息准确到达指定的服务端。
服务端并没有指定固定的地址,而是通过
DatagramSocket
的构造方法创建了一个未绑定地址的DatagramSocket
。这样做的原因是,服务端通常是被动地等待客户端的连接请求,因此不需要预先指定一个地址。服务端使用
server.receive(receivePacket);
接收客户端发送的数据包时,DatagramSocket
会自动绑定到一个系统分配的可用端口,并通过这个端口接收数据。服务端的地址信息会在receivePacket
中自动填充。客户端在发送数据包时,为了指定服务端的地址,需要使用
InetAddress.getByName("127.0.0.1")
来获取服务端的IP地址,同时指定服务端的端口号(在这个例子中是PORT
)。这样客户端的数据包就能够到达服务端指定的地址和端口。
// 客户端
package udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Client {
private static final int PORT = 8888; // 固定的端口号
private DatagramSocket clientSocket; // 未初始化的客户端
// 构造函数 同时初始化客户端
public Client() throws Exception {
clientSocket = new DatagramSocket();
}
/**
* @param message 消息内容
* @description: 向服务器发送消息
* @author: strivePeng
*/
public void clientSendMsg(String message) {
try {
InetAddress serverAddress = InetAddress.getByName("127.0.0.1"); // 固定一个客户端IP为 本地
byte[] sendBuffer = message.getBytes();
// 创建并发送数据包
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, serverAddress, PORT);
clientSocket.send(sendPacket);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 服务端
package udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
private static final int PORT = 8888; // 固定的端口号
private DatagramSocket server; // 一个未初始化的服务端
// 构造函数
public Server() throws Exception {
server = new DatagramSocket(PORT); // 初始化服务端
}
/**
* @return 返回一个收到的字符串
* @description: 服务器接收客户端发来的消息
* @author: strivePeng
*/
public String serverReceiveMsg() {
try {
byte[] receiveBuffer = new byte[1024];
// 创建并接收数据包 同时转为字符串返回
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
server.receive(receivePacket);
return new String(receivePacket.getData(), 0, receivePacket.getLength());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
(3)Swing组件的使用
窗口结合IDEA的GUI Form可视化拖动设计。基本的创建步骤如下图所示:
Swing组件
-
常用容器组件:
-
JFrame
:顶级窗口容器,用于创建窗口。 -
JPanel
:用于创建面板,可将组件添加到面板中。 -
JDialog
:对话框容器,用于创建弹出式对话框。
-
-
基本组件:
-
JButton
:按钮。 -
JLabel
:标签。 -
JTextField
:文本框。 -
JTextArea
:多行文本框。 -
JCheckBox
:复选框。 -
JRadioButton
:单选按钮。
-
当学习 Java Swing 时,深入了解各个组件的特性、属性以及适用场景是非常重要的。以下是对几个常用 Swing 组件的详细介绍:
JFrame:顶级窗口容器
-
特性:
- 用于创建窗口应用程序的主窗口。
- 可以包含其他 Swing 组件。
- 提供了标题栏、菜单栏等标准窗口元素。
-
属性:
-
setTitle(String title)
:设置窗口标题。 -
setSize(int width, int height)
:设置窗口的大小。 -
setDefaultCloseOperation(int operation)
:设置关闭窗口的操作。 -
setLocationRelativeTo(null);
:将窗口定位到屏幕的中央。这是为了确保窗口在屏幕上居中显示。
-
-
适用场景:
- 创建独立的窗口应用程序。
- 包含其他 Swing 组件,构建完整的应用。
JPanel:面板容器
-
特性:
- 用于组织和布局其他组件。
- 可以嵌套在其他容器中,如 JFrame。
- 提供了轻量级容器,不具备窗口功能。
-
属性:
-
setLayout(LayoutManager manager)
:设置布局管理器。 -
add(Component comp)
:添加组件到面板。
-
-
适用场景:
- 作为容器,组织其他组件。
- 嵌套在 JFrame 中,实现复杂的界面布局。
JButton:按钮
-
特性:
- 用于触发用户操作的按钮。
- 可以包含文本、图标等。
- 通过监听器响应点击事件。
-
属性:
-
setText(String text)
:设置按钮上的文本。 -
setIcon(Icon icon)
:设置按钮上的图标。 -
addActionListener(ActionListener listener)
:添加点击事件监听器。
-
-
适用场景:
- 作为用户交互的触发器。
- 触发表单提交、对话框弹出等操作。
JLabel:标签
-
特性:
- 用于显示文本或图像。
- 可以用于展示静态信息。
- 不响应用户输入。
-
属性:
-
setText(String text)
:设置标签显示的文本。 -
setIcon(Icon icon)
:设置标签显示的图标。
-
-
适用场景:
- 展示静态文本或图像。
- 用于显示提示信息。
JTextField:文本框
-
特性:
- 用于接收用户输入的单行文本。
- 可以设置初始文本。
- 通过监听器响应文本变化事件。
-
属性:
-
setText(String text)
:设置文本框的初始文本。 -
getText()
:获取文本框的当前文本。
-
-
适用场景:
- 接收用户输入的文本信息。
- 用于登录、搜索等场景。
JTextArea:多行文本框
-
特性:
- 用于接收用户输入的多行文本。
- 可以设置初始文本和行数。
- 通过监听器响应文本变化事件。
-
属性:
-
setText(String text)
:设置多行文本框的初始文本。 -
getText()
:获取多行文本框的当前文本。
-
-
适用场景:
-
接收用户输入的多行文本信息。
-
用于评论、描述等场景。
-
布局管理器(Layout Managers)
-
特性:
- 用于确定组件在容器中的位置和大小。
- 常见的有
FlowLayout
、BorderLayout
、GridLayout
、BoxLayout
等。 - 可以嵌套使用,实现复杂的布局。
-
适用场景:
- 定义和控制组件的相对位置。
- 适配不同屏幕尺寸和分辨率。
-
特性:
-
(4)类对象的引用
除此之外,在登录成功后涉及到了弹窗的关闭,此时使用了类对象的引用的传入,方便销毁登录界面。具体实现如下。
// 构造函数 同时传入登录界面的引用方便在登录成功后关闭
public LoginSuccessfulDialog(LoginJF loginJF) {
this.loginJF = loginJF; // 传入一个窗口对象的引用
this.setTitle("提示");
this.setContentPane(contentPane);
this.setModal(true);
this.getRootPane().setDefaultButton(buttonOK);
// 判断用户是否存在
if(loginJF.userIsExit){
// 用户存在
this.successfulDialog.setText("登录成功!");
// 设置按钮功能为点击后跳转到聊天界面
this.buttonOK.addActionListener(actionEvent -> onOK());
}else{
// 用户不存在
this.successfulDialog.setText("登录失败,请重新登录!");
this.buttonOK.addActionListener(actionEvent -> onCancel());
}
......
}
(5)线程调用GUI窗口
public static void main(String[] args) {
/*
使用了 SwingUtilities.invokeLater() 方法来确保 Swing 组件的初始化和显示操作在事件分派线程(Event Dispatch Thread,EDT)上执行。
在 Swing 中,GUI 的相关操作应该在 EDT 上执行,以确保线程安全性。
*/
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
LoginJF loginJF = new LoginJF();
loginJF.setVisible(true);
}
});
}
(6)Lambda表达式的使用
Lambda 表达式是 Java 8 中引入的一个强大的特性,它提供了一种更简洁、更方便的方式来实现函数式编程。Lambda 表达式通常在简单的、只有一个抽象方法的接口中发挥作用,使代码更为紧凑。Lambda 表达式的主要用途是简化匿名内部类的语法,使代码更加紧凑和易读。下面详细讲解 Lambda 表达式的用法:
基本语法
Lambda 表达式的基本语法如下:
(parameters) -> expression
或者
(parameters) -> { statements; }
-
(parameters)
:指定参数列表。如果没有参数,可以留空或使用()
。 -
->
:称为箭头操作符,将参数列表与 Lambda 表达式的主体分隔开。 -
expression
:Lambda 表达式的主体,可以是单个表达式。 -
{ statements; }
:Lambda 表达式的主体,可以是一系列语句。
程序中的使用
// 按钮监听事件绑定
this.resetButton.addActionListener(actionEvent -> reset());
// 判断用户是否存在
if(loginJF.userIsExit){
// 用户存在
this.successfulDialog.setText("登录成功!");
// 设置按钮功能为点击后跳转到聊天界面
this.buttonOK.addActionListener(actionEvent -> onOK());
}else{
// 用户不存在
this.successfulDialog.setText("登录失败,请重新登录!");
this.buttonOK.addActionListener(actionEvent -> onCancel());
}
// 开启聊天界面
SwingUtilities.invokeLater(()->{
ChattingJF chattingJF = new ChattingJF();
chattingJF.setVisible(true);
});
(7)日期获取与处理
/**
* @return 当前日期的字符串
* @description 获取当前时间并格式化为字符串
* @author strivePeng
*/
private String getCurrentTime() {
// 格式化日期 SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
// 获取当前日期
Date data = new Date(System.currentTimeMillis());
// 格式化日期时间转为字符串
String str = sdf.format(data);
return str;
}
(8)知识库中的答案的搜索与展示
涉及到了更多的String字符串的处理以及List集合的操作,详见代码注释:
/**
* @param question 提问的问题
* @return 服务器返回的答案
* @throws IOException 文件IO异常
* @description 从知识库中返回答案
* @author strivePeng
*/
private String findAnswer(String question){
try{
FileIo fileIo = new FileIo();
for (String q : fileIo.getQuestionList()) {
if (question.trim().equals(q.trim())) {
// 根据问题和答案的索引一一对应的关系返回答案
return fileIo.getAnswerList().get(fileIo.getQuestionList().indexOf(q));
}
}
}catch (Exception e){
e.printStackTrace();
}
return "抱歉!我还没有学习到这方面的知识哦!!!";
}
06 结语
聊天机器人相关知识详情都在源码注释中详细备注,本次练习旨在为自己一阶段的学习进行检验,在本次实例中使用到了java基础的语法,以及异常处理,一小部分的线程处理,以及Swing组件的使用和文件的读写流等,适合新手上手以及对网路UDP通信的理解。文章来源:https://www.toymoban.com/news/detail-772249.html
前期的学习参考了以下博客:CSDN博客文章来源地址https://www.toymoban.com/news/detail-772249.html
到了这里,关于Java实例 基于UDP及自建知识库的聊天机器人的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!