数据库crud的简单测试
为管理岗位业务培训信息,有如下3个表:S(S#,SN,SD,SA),其中S#,SN,SD,SA分别代表学号、学员姓名、所属单位、学员年龄。 C (C#,CN ),其中C#,CN分别代表课程编号、课程名称SC(S#,C#,G),其中S#,C#,G分别代表学号、所选修的课程编号、学习成绩请使用2种标准SQL语句査洵选修课程名称为“税收基础”的学员学号和姓名,并说明其优缺点 。SQL92标准:SELECT SN,SD FROM SWHERE [S#] IN(SELECT [S#] FROM C,SCWHERE C.[C#]=SC.[C#]AND CN=N'税收基础')SQL99标准:elect s.s#,s.sn from sjoin sc on s.s#=sc.s#join c on sc.c#=c.c#where c.cn='税收基础'优点:SQL99将连接条件和过滤条件分开,显得代码清晰。SQL92书写简单易于理解。缺点:SQL92连接条件和过滤条件都写在一起,不利于查看。SQL99书写相对麻烦不易于理解。用Java怎么实现有每天有1亿条记录的DB储存?MySQL上亿记录数据量的数据库如何设计?1.这么大数据量首先建议 使用大数据的DB,可以用spring batch 来做类似这样的处理。定量向DB存储数据。如果需要定时,可以考虑 quartz。Mysql数据库设计:1.读写分离;2.纵向横向拆分库、表。MySQL的基本功能中包括replication(复制)功能。所谓replication,就是确定master以及与之同步的slave服务器,再加上slave将master中写入的内容polling过来更新自身内容的功能。这样slave就是master的replica(复制品)。这样就可以准备多台内容相同的服务器。通过master和salve的replication,准备好多台服务器之后,让应用程序服务器通过负载均衡器去处理查询slave。这样就能将查询分散到多台服务器上。应用程序实现上应该只把select等读取之类的查询发送给负载均衡器,而更新应当直接发送给master。要是在slave上执行更新操作,slave和master的内容就无法同步。MySQL会检测到master和slave之间内容差异,并停止replication,这回导致系统故障。Slave可以采用LVS(linux系统自带的负载均衡器)实现查询的负载均衡。使用MySQL的replication是利用的冗余化,实现冗余化需要实现的最小服务器数量是4台,三台slave和一台master,slave为什么是需要三台呢,比如一台slave死机了,现在需要修复再次上线,那么意味着你必须停止一台slave来复制MySQL的数据,如果只有两台slave,一台坏了,你就必须停止服务,如果有三台,坏了一台,你复制数据时停止一台,还有一台可以运维。对于数据的处理是能放入到内存中就尽量放入到内存中如果不能放入到内存中,可以利用MySQL的Partitioning。Partitioning就是表分割也就是讲A表和B表放在不同的服务器上。简单来说,Partitioning就是充分利用局部性进行分割,提高缓存利用效率,从而实现Partitioning的效果。其中最重要的一点就是以Partitioning为前提设计的系统将表分割开,用RDBMS的方式的话,对于一对多的关系经常使用JOIN查询将两张表连接起来。但是如果将表分割开了之后,也就是两张表不在同一个数据库,不在同一个服务器上怎样使用JOIN操作,这里需要注意的是如果是用where in操作不是省了一些麻烦了嘛。Mysql的引擎有哪些?支持事物么?DB储存引擎有哪些?MySQL有多种存储引擎,每种存储引擎有各自的优缺点,可以择优选择使用:MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE。MySQL支持数个存储引擎作为对不同表的类型的处理器。MySQL存储引擎包括处理事务安全表的引擎和处理非事务安全表的引擎。· MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。MyISAM在所有MySQL配置里被支持,它是默认的存储引擎,除非你配置MySQL默认使用另外一个引擎。· MEMORY存储引擎提供“内存中”表。MERGE存储引擎允许集合将被处理同样的MyISAM表作为一个单独的表。就像MyISAM一样,MEMORY和MERGE存储引擎处理非事务表,这两个引擎也都被默认包含在MySQL中。注释:MEMORY存储引擎正式地被确定为HEAP引擎。· InnoDB和BDB存储引擎提供事务安全表。BDB被包含在为支持它的操作系统发布的MySQL-Max二进制分发版里。InnoDB也默认被包括在所 有MySQL 5.1二进制分发版里,你可以按照喜好通过配置MySQL来允许或禁止任一引擎。· EXAMPLE存储引擎是一个“存根”引擎,它不做什么。你可以用这个引擎创建表,但没有数据被存储于其中或从其中检索。这个引擎的目的是服务,在 MySQL源代码中的一个例子,它演示说明如何开始编写新存储引擎。同样,它的主要兴趣是对开发者。· NDB Cluster是被MySQL Cluster用来实现分割到多台计算机上的表的存储引擎。它在MySQL-Max 5.1二进制分发版里提供。这个存储引擎当前只被Linux, Solaris, 和Mac OS X 支持。在未来的MySQL分发版中,我们想要添加其它平台对这个引擎的支持,包括Windows。· ARCHIVE存储引擎被用来无索引地,非常小地覆盖存储的大量数据。· CSV存储引擎把数据以逗号分隔的格式存储在文本文件中。· BLACKHOLE存储引擎接受但不存储数据,并且检索总是返回一个空集。· FEDERATED存储引擎把数据存在远程数据库中。在MySQL 5.1中,它只和MySQL一起工作,使用MySQL C Client API。在未来的分发版中,我们想要让它使用其它驱动器或客户端连接方法连接到另外的数据源
取高八位与低八位,高八位与低八位合并
这些操作都是针对于二进制位而言的算法, >>8 表示的是右移8位(就相当于去掉了低八位) &表示的是按位与 ( 将数值转化为二进制进行与操作 ) 0xff是两位的十六进制相当于全是 1 的 8 位二进制数, 运算结果就是低八位c语言#include <stdio.h>
#include <stdlib.h>
char right,left,temp;//right表示第八位,left表示高八位
int size_right,size_left,size_temp;
short int number,size_number;
short int number_two,number_three;
//int 是四个字节 short int 是两个字节
int main()
{
number =32343; //65536个数范围是 -32768-32767
right = number&0XFF;//低八位
left = number>>8;//高八位
// 前取高八位,在取低八位
left=(number>>8)&0XFF; //先取高八位
right=number&0XFF; //再取第八位
// 现在要把高八位和第八位合并
number_three=left;
number_three<<=8;
number_three|=right;
printf("number_three=%d",number_three);
return 0;
}c#语言int a = 1023;
int right = a & 0x00ff; // 低八位
int left = a >> 8; // 高八位
int number_three = left;
number_three <<= 8;
number_three |= right;
MessageBox.Show(number_three.ToString());
WPF 之 数据与命令绑定 (MVVM方式)
目录格式实现的功能XAML 文件<Grid>
<StackPanel>
<!-- 数据绑定 -->
<TextBox Width="300" x:Name="TextBox1" Height="30" Margin="15" Text="{Binding SearchText}"/>
<!-- 命令绑定 -->
<Button Content="展示" Width="50" Height="30" Margin="15" Command="{Binding SignInCommand}" />
</StackPanel>
</Grid>baseCommon NotifyPropertyChanged.csusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace dataEventBinding.baseCommon
{
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
// 通知属性更改 <param name="propertyName" />
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}baseCommon RelayCommand.csusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace dataEventBinding.baseCommon
{
class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged = (sender, e) => { };
private Action mAction;
public RelayCommand(Action action) {
this.mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction.Invoke();
}
}
}ViewModels MainViewModel.csusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace dataEventBinding.ViewModels
{
public class MainViewModel : baseCommon.NotifyPropertyChanged
{
public MainViewModel()
{
SignInCommand = new baseCommon.RelayCommand(() => {
MessageBox.Show(searchText);
});
}
private string searchText;
// 发布方法 供外面使用
public ICommand SignInCommand { get; private set; }
public string SearchText
{
get { return searchText; }
set { searchText = value; RaisePropertyChanged(nameof(searchText)); }
}
}
}MainWindow.xaml.csusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace dataEventBinding.Views
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
// 创建对象并赋值给 window.DataContext
public ViewModels.MainViewModel m = new ViewModels.MainViewModel();
public MainWindow()
{
InitializeComponent();
m.SearchText = "water";
this.DataContext = m;
}
}
}
WPF 之 串口使用
布局代码:<Grid>
<TextBox HorizontalAlignment="Left" Height="23" Margin="112,59,0,0" TextWrapping="Wrap" Name="txtSend" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="112,113,0,0" TextWrapping="Wrap" Name="txtReceive" VerticalAlignment="Top" Width="120"/>
<Button Content="发送" Name="btnSend" HorizontalAlignment="Left" Margin="83,271,0,0" VerticalAlignment="Top" Width="35" Click="btnSend_Click"/>
<Button Content="发送" Name="btnSend1" HorizontalAlignment="Left" Margin="143,271,0,0" VerticalAlignment="Top" Width="35" Click="btnSend1_Click"/>
<Button Content="发送" Name="btnSend2" HorizontalAlignment="Left" Margin="203,271,0,0" VerticalAlignment="Top" Width="35" Click="btnSend2_Click"/>
<Button Content="发送" Name="btnSend3" HorizontalAlignment="Left" Margin="263,271,0,0" VerticalAlignment="Top" Width="35" Click="btnSend3_Click"/>
<Label Name="one" Background="Red" HorizontalAlignment="Left" Margin="84,190,0,0" VerticalAlignment="Top" Height="24" Width="28"/>
<Label Name="two" Background="Red" HorizontalAlignment="Left" Margin="144,190,0,0" VerticalAlignment="Top" Height="24" Width="28"/>
<Label Name="three" Background="Red" HorizontalAlignment="Left" Margin="204,190,0,0" VerticalAlignment="Top" Height="24" Width="28"/>
<Label Name="four" Background="Red" HorizontalAlignment="Left" Margin="264,190,0,0" VerticalAlignment="Top" Height="24" Width="28"/>
</Grid>后台代码:private SerialPort Sp = new SerialPort();
public delegate void HandleInterfaceUpdataDelegate(string text);
private HandleInterfaceUpdataDelegate interfaceUpdataHandle;
String[] arr = { "0", "0", "0", "0" };//用于存储硬件上面灯状态Loaded事件添加用于更改串口参数: //更改参数
Sp.PortName = "COM3";
Sp.BaudRate = 115200;
Sp.Parity = Parity.None;
Sp.StopBits = StopBits.One;编写监听和发送数据事件:编写监听和发送数据事件:
private void Serial()
{
Sp.DataReceived += new SerialDataReceivedEventHandler(Sp_DataReceived);
if (!Sp.IsOpen)
{
Sp.Open();
}
// 用字节的形式发送数据
SendBytesData(Sp);
}
//发送二进制数据
private void SendBytesData(SerialPort Sp)
{
byte[] bytesSend = System.Text.Encoding.Default.GetBytes(txtSend.Text);
Sp.Write(bytesSend, 0, bytesSend.Length);
}
public void Sp_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
SerialPort serialPort = (SerialPort)(sender);
System.Threading.Thread.Sleep(100);//延缓一会,用于防止硬件发送速率跟不上缓存数据导致的缓存数据杂乱
int n = serialPort.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据
//received_count += n;//增加接收计数
serialPort.Read(buf, 0, n);//读取缓冲数据
//因为要访问ui资源,所以需要使用invoke方式同步ui
interfaceUpdataHandle = new HandleInterfaceUpdataDelegate(UpdateTextBox);//实例化委托对象
// Dispatcher.Invoke(interfaceUpdateHandle, new string[] { Encoding.ASCII.GetString(buf) });
Dispatcher.Invoke(interfaceUpdataHandle, new string[] { Encoding.ASCII.GetString(buf) });
}
private void UpdateTextBox(string text)
{
txtReceive.Text = text;
String Receive = Convert.ToString(text);
if (Receive != "")
{
// MessageBox.Show("receive", Receive);
String Receive1 = Receive.Substring(0, 1);
String Receive2 = Receive.Substring(1, 1);
String Receive3 = Receive.Substring(2, 1);
String Receive4 = Receive.Substring(3, 1);
if (Receive1 == 1.ToString())
{
one.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[0] = 1.ToString();
}
else
{
one.Background = new SolidColorBrush(Colors.Red);
arr[0] = 0.ToString();
}
if (Receive2 == 1.ToString())
{
two.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[1] = 1.ToString();
}
else
{
two.Background = new SolidColorBrush(Colors.Red);
arr[1] = 0.ToString();
}
if (Receive3 == 1.ToString())
{
three.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[2] = 1.ToString();
}
else
{
three.Background = new SolidColorBrush(Colors.Red);
arr[2] = 0.ToString();
}
if (Receive4 == 1.ToString())
{
four.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[3] = 1.ToString();
}
else
{
four.Background = new SolidColorBrush(Colors.Red);
arr[3] = 0.ToString();
}
}
}button点击事件添加: private void btnSend_Click(object sender, RoutedEventArgs e)
{
if (arr[0] == 0.ToString())
{
txtSend.Text = "0";
Serial();
}
else
{
txtSend.Text = "1";
Serial();
}
}
private void btnSend1_Click(object sender, RoutedEventArgs e)
{
if (arr[1] == 0.ToString())
{
txtSend.Text = "2";
Serial();
}
else
{
txtSend.Text = "3";
Serial();
}
}
private void btnSend2_Click(object sender, RoutedEventArgs e)
{
if (arr[2] == 0.ToString())
{
txtSend.Text = "4";
Serial();
}
else
{
txtSend.Text = "5";
Serial();
}
}
private void btnSend3_Click(object sender, RoutedEventArgs e)
{
if (arr[3] == 0.ToString())
{
txtSend.Text = "6";
Serial();
}
else
{
txtSend.Text = "7";
Serial();
}
}完整后台代码: public partial class MainWindow : Window
{
private SerialPort Sp = new SerialPort();
public delegate void HandleInterfaceUpdataDelegate(string text);
private HandleInterfaceUpdataDelegate interfaceUpdataHandle;
String[] arr = { "0", "0", "0", "0" };//用于存储硬件上面灯状态
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//更改参数
Sp.PortName = "COM3";
Sp.BaudRate = 115200;
Sp.Parity = Parity.None;
Sp.StopBits = StopBits.One;
Serial();
}
private void btnSend_Click(object sender, RoutedEventArgs e)
{
if (arr[0] == 0.ToString())
{
txtSend.Text = "0";
Serial();
}
else
{
txtSend.Text = "1";
Serial();
}
}
private void btnSend1_Click(object sender, RoutedEventArgs e)
{
if (arr[1] == 0.ToString())
{
txtSend.Text = "2";
Serial();
}
else
{
txtSend.Text = "3";
Serial();
}
}
private void btnSend2_Click(object sender, RoutedEventArgs e)
{
if (arr[2] == 0.ToString())
{
txtSend.Text = "4";
Serial();
}
else
{
txtSend.Text = "5";
Serial();
}
}
private void btnSend3_Click(object sender, RoutedEventArgs e)
{
if (arr[3] == 0.ToString())
{
txtSend.Text = "6";
Serial();
}
else
{
txtSend.Text = "7";
Serial();
}
}
private void Serial()
{
Sp.DataReceived += new SerialDataReceivedEventHandler(Sp_DataReceived);
if (!Sp.IsOpen)
{
Sp.Open();
}
//用字节的形式发送数据
SendBytesData(Sp);
}
//发送二进制数据
private void SendBytesData(SerialPort Sp)
{
byte[] bytesSend = System.Text.Encoding.Default.GetBytes(txtSend.Text);
Sp.Write(bytesSend, 0, bytesSend.Length);
}
public void Sp_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
/* byte[] readBuffer = new byte[Sp.ReadBufferSize];
Sp.Read(readBuffer, 0, readBuffer.Length);
//Dispatcher.Invoke(interfaceUpdataHandle, new string[]{ Encoding.UTF8.GetString(readBuffer)});
Dispatcher.Invoke(interfaceUpdataHandle, new string[] { Encoding.ASCII.GetString(readBuffer) });
//Dispatcher.Invoke(interfaceUpdateHandle, new string[] { Encoding.ASCII.GetString(buf) });*/
SerialPort serialPort = (SerialPort)(sender);
System.Threading.Thread.Sleep(100);//延缓一会,用于防止硬件发送速率跟不上缓存数据导致的缓存数据杂乱
int n = serialPort.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据
//received_count += n;//增加接收计数
serialPort.Read(buf, 0, n);//读取缓冲数据
//因为要访问ui资源,所以需要使用invoke方式同步ui
interfaceUpdataHandle = new HandleInterfaceUpdataDelegate(UpdateTextBox);//实例化委托对象
// Dispatcher.Invoke(interfaceUpdateHandle, new string[] { Encoding.ASCII.GetString(buf) });
Dispatcher.Invoke(interfaceUpdataHandle, new string[] { Encoding.ASCII.GetString(buf) });
//serialPort.Close();
}
private void UpdateTextBox(string text)
{
txtReceive.Text = text;
String Receive = Convert.ToString(text);
if (Receive != "")
{
// MessageBox.Show("receive", Receive);
String Receive1 = Receive.Substring(0, 1);
String Receive2 = Receive.Substring(1, 1);
String Receive3 = Receive.Substring(2, 1);
String Receive4 = Receive.Substring(3, 1);
if (Receive1 == 1.ToString())
{
one.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[0] = 1.ToString();
}
else
{
one.Background = new SolidColorBrush(Colors.Red);
arr[0] = 0.ToString();
}
if (Receive2 == 1.ToString())
{
two.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[1] = 1.ToString();
}
else
{
two.Background = new SolidColorBrush(Colors.Red);
arr[1] = 0.ToString();
}
if (Receive3 == 1.ToString())
{
three.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[2] = 1.ToString();
}
else
{
three.Background = new SolidColorBrush(Colors.Red);
arr[2] = 0.ToString();
}
if (Receive4 == 1.ToString())
{
four.Background = new SolidColorBrush(Colors.MediumAquamarine);
arr[3] = 1.ToString();
}
else
{
four.Background = new SolidColorBrush(Colors.Red);
arr[3] = 0.ToString();
}
//String abc = Convert.ToString(arr);
//MessageBox.Show("abc", abc);
// MessageBox.Show("arr", arr);
}
}
}
WPF使用AvalonEdit实现代码高亮显示、搜索、替换功能
WPF使用AvalonEdit实现代码高亮显示、搜索、替换功能很多工程软件拥有自己定义的脚本语言,作为程序员用惯了具有高亮显示和智能提示功能的编辑器,所以针对特定的脚本自己开发一个编辑器。主要采用WPF、C#语言以及AvalonEdit控件。WPF使用AvalonEdit实现代码高亮显示、搜索、替换功能AvlonEdit控件实现自定义高亮显示实现文本搜索实现文本替换自定义搜索栏用户控件实现自定义搜索实现自定义替换AvlonEdit控件AvalonEdit是基于WPF的代码显示控件,可以支持代码高亮显示、智能提示、代码折叠等功能。AvalonEdit项目官网在WPF中使用AvalonEdit非常简单,直接Nuget安装,然后引入命名空间xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit",最后直接使用即可<avalonEdit:TextEditor/>。因为本文后面要实现自定义替换,需要对源码进行修改及重新编译,所以最好直接下载源码。实现自定义高亮显示AvalonEdit已经内置了C#、C++、Java等常见语言的高亮显示,如果要为自定义的语言进行语法高亮需要写一个*.xshd文件,该文件的基本使用如下:<?xml version="1.0"?><SyntaxDefinition name="Custom Highlighting" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"> <!--设置颜色与文本风格,如粗体,背景色等--> <Color name="Comment" foreground="#C6B1B1" exampleText="* comment"/> <Color name="Card" fontWeight="bold" foreground="#960092" exampleText="=CSTR"/> <Color name="Field" fontWeight="bold" foreground="#3A76D7" exampleText="CA"/> <!-- 主要的规则集 --> <RuleSet> <!--以//开头或者包裹在/*..*/中的文本使用Comment颜色--> <Span color="Comment" begin="//" /> <Span color="Comment" multiline="true" begin="/\*" end="\*/" /> <Span color="String"> <Begin>"</Begin> <End>"</End> <!--可以定义规则子集--> <RuleSet> <Span begin="\\" end="." /> </RuleSet> </Span> <!--定义关键词--> <Keywords fontWeight="bold" foreground="Blue"> <Word>if</Word> <Word>else</Word> <!-- ... --> </Keywords> <!-- 可以使用正则进行定义 --> <Rule foreground="DarkBlue"> \b0[xX][0-9a-fA-F]+ # hex number | \b ( \d+(\.[0-9]+)? #number with optional floating point | \.[0-9]+ #or just starting with floating point ) ([eE][+-]?[0-9]+)? # optional exponent </Rule> </RuleSet></SyntaxDefinition>自定义完*.xshd文件后,一定要设置文件的属性设置完成后,需要在程序中设置加载 //注册自定义高亮 IHighlightingDefinition customHighlighting; using (Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("NotConvertPeps.PEPSHighlighting.xshd")) { using (XmlReader reader = new XmlTextReader(s)) { customHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance); } }//要设置一个后缀名字,在这里我设置了fre HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".fre" }, customHighlighting); InitializeComponent();//在InitializeComponent()之后使用,为txtEdit设置高亮语法 txtEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".fre");设置完成后,看下效果实现文本搜索AvalonEdit已经具有了搜索功能,新版本只需使用ICSharpCode.AvalonEdit.Search.SearchPanel.Install(txtEditor);便可以使用Ctrl+F调出搜索栏,该搜索栏具有是否忽略大小写、是否全字匹配、是否使用正则三个设置项,而且还有背景显示、下拉框自动下拉等功能,基本满足要求。实现文本替换很遗憾AvalonEdit没有提供像搜索栏一样的功能,必须自己来实现。自定义搜索栏用户控件仿照VS的替换栏进行页面设计其中注册replaceContent、findContent、CareCase、MatchAll等依赖属性进行绑定,值得注意的是,用户控件在绑定的时候,source要用RelativeSource,否则不能实现数据更新,正确绑定方式如下:<CheckBox x:Name="chxCareCase" IsChecked="{Binding CareCase,RelativeSource={RelativeSource AncestorType=local:ReplaceControl}}" Template="{StaticResource ToggleButtonControlTemplate2}" ToolTip="区分大小写" />实现自定义搜索因为替换操作仍然需要先查询再替换,所以需要自定义实现搜索。实现思路可以是得到textEditor中的text,然后使用string.index等方法进行,但是这样太麻烦,而且还需要自定义搜索结果的背景高亮以及文本选择。所以要换个思路,因为AvalonEdit已经提供了搜索功能,所以一定会有相关的接口,与查询有关的代码都存在于ICSharpCode.AvalonEdit.Search命名空间下,主要的查询方法存在于ICSharpCode.AvalonEdit.Search.SearchPanel中,其中最关键的查询方法是SearchStrategyFactory.Create(SearchPattern, !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal);除了该方法外,我们还需要进行上一个和下一个搜索以及搜索结果的背景高亮显示,这都和SearchResultBackgroundRenderer类相关,但是AvalonEdit官方源码中,该类的访问权限是Private,所以需要将访问权限改为public,然后重新编译。实现自定义搜索功能C#代码SearchResultBackgroundRenderer renderer = new SearchResultBackgroundRenderer();void DoFind(){ renderer.CurrentResults.Clear();//清空已经搜索出的结果 if (!string.IsNullOrEmpty(replaceUserControl.findContent)) { //文字的背景高亮 textArea.TextView.BackgroundRenderers.Clear(); textArea.TextView.BackgroundRenderers.Add(renderer); //搜索的管件方法 ISearchStrategy strategy = SearchStrategyFactory.Create(replaceUserControl.findContent, !replaceUserControl.CareCase, replaceUserControl.MatchAll, replaceUserControl.regex ? SearchMode.RegEx : SearchMode.Normal); var results = strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength); //将搜索的结果全部加到renderer.CurrentResults中,方便后续的进行上一个、下一个搜索的展示 foreach (SearchResult result in results) { renderer.CurrentResults.Add(result); } }}实现下一个private void FindNext(object sender, RoutedEventArgs e){ DoFind(); SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); if (result == null) result = renderer.CurrentResults.FirstSegment; if (result != null) { SelectResult(result); }}实现上一个private void FindPre(object sender, RoutedEventArgs e){ DoFind(); SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); if (result != null) result = renderer.CurrentResults.GetPreviousSegment(result); if (result == null) result = renderer.CurrentResults.LastSegment; if (result != null) { SelectResult(result); }}实现效果实现自定义替换AvalonEdit提供了Document.Replace方法,可以直接使用private void ReplaceNext(object sender, RoutedEventArgs e){ string replace = replaceUserControl.replaceContent; DoFind(); SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); if (result == null) result = renderer.CurrentResults.FirstSegment; if (result != null) { SelectResult(result); this.txtEditor.Document.Replace(result.StartOffset, result.Length, replace); DoFind();//必须调用一次,不然查询出的字段背景色会乱 }}WPF使用AvalonEdit实现代码高亮显示、搜索、替换功能AvlonEdit控件实现自定义高亮显示实现文本搜索实现文本替换自定义搜索栏用户控件实现自定义搜索实现自定义替换
如何以超快的方式调试?
起因最近在研究ServiceScope的内一些内在运行逻辑,发现相关资料非常少,只有讲IOC相关的文章有说Core时代的官方依赖注入怎么使用。。遂决定还是要去看源代码。这部分源代码在Microsoft.Extensions.DependencyInjection库中,源代码位置在src/libraries下。阅读了一点,发现内部解析服务的时候会来回倒腾,那看代码的方式去梳理就非常难受了。。有没有一种可能, C#也能像JAVA那样非常顺畅的调试源代码呢?效果还真有! 话不说多,看图:速度非常快,像调试本地代码一样.. 比反编译出来的流畅度不知道高到那里去了!不知道官方的项目用了什么黑魔法,这里能直接拉到源代码(图里的外部源),而自己开发的项目做不到这一点。具体步骤这块其实官方有说明,但漏了几个关键点导致我卡了非常久,下面会进行详细说明:PS1:以Windows VS为主,其它平台应该类似PS2: 我主要是查看DI的构建逻辑,这块在不同版本差异不大, 所以我直接获取了6.0起因最近在研究ServiceScope的内一些内在运行逻辑,发现相关资料非常少,只有讲IOC相关的文章有说Core时代的官方依赖注入怎么使用。。遂决定还是要去看源代码。这部分源代码在Microsoft.Extensions.DependencyInjection库中,源代码位置在src/libraries[1]下。阅读了一点,发现内部解析服务的时候会来回倒腾,那看代码的方式去梳理就非常难受了。。有没有一种可能, C#也能像JAVA那样非常顺畅的调试源代码呢?效果还真有! 话不说多,看图:速度非常快,像调试本地代码一样.. 比反编译出来的流畅度不知道高到那里去了!不知道官方的项目用了什么黑魔法,这里能直接拉到源代码(图里的外部源),而自己开发的项目做不到这一点。具体步骤这块其实官方有说明,但漏了几个关键点导致我卡了非常久,下面会进行详细说明:PS1:以Windows VS为主,其它平台应该类似PS2: 我主要是查看DI的构建逻辑,这块在不同版本差异不大, 所以我直接获取了6.01.打开官方仓库官方仓库[2]然后你拉取你想看的分支代码到本地,我主要是看2.找到他们的构建说明3.安装对应平台的基础环境Windows VS平台是这样安装的:然后点击查看详细信息,弹出的提示(无法安装XXXXX)可以忽略, 然后点修改即可。这一步,官方的说法是你只需要安装更高版本的SDK即可,不用一一匹配。通常情况下开发的电脑上都会安装.NET Framework和.NET的几个SDK,一般都有。我自己是安装 .NET Framework 4.0目标包+4.7.2、目标包+NET 6.0的SDK。[重点]4. 还原对应库资源浏览器定位到runtime的根目录,记住这个build.cmd右键打开命令行或pwd,像这样执行:脚本会下载一个ps1文件然后自动执行,我们等待即可,他会自动还原我们需要的库,并且把依赖的基础包也一并还原好。官方的代码结构中已经做好了nuget配置和输出目录, 我们已经不需要额外配置了,下一步进行编译。5.生成对应库的dll文件打开对应库的代码文件:右键打开命令行或pwd,像这样执行:等待编译结束去这个目录下找东西每个库都会生成到artifacts下面,然后不同架构对应一个文件夹, 此时你就可以在你的测试项目中直接引用这个dll了, 愉快的调试吧。[可选]6.生成依赖库文件我这里是想调试Microsoft.Extensions.DependencyInjection,在Nuget上就能看到他还依赖一个抽象定义包Microsoft.Extensions.DependencyInjection.Abstractions,为了不在调试中卡壳,我把这个包一并如法炮制。其它在查阅资料时发现其实也可以用VS直接编译,但需要配置些东西,我没看明白就用这个办法了,我也不需要编译所有的。VS Code也可以,但我主要用VS就略过这部分了build.cmd脚本不加参数似乎是编译所有包, 我不需要就跳过这个了1.打开官方仓库官方仓库然后你拉取你想看的分支代码到本地,我主要是看2.找到他们的构建说明3.安装对应平台的基础环境Windows VS平台是这样安装的:然后点击查看详细信息,弹出的提示(无法安装XXXXX)可以忽略, 然后点修改即可。这一步,官方的说法是你只需要安装更高版本的SDK即可,不用一一匹配。通常情况下开发的电脑上都会安装.NET Framework和.NET的几个SDK,一般都有。我自己是安装 .NET Framework 4.0目标包+4.7.2、目标包+NET 6.0的SDK。[重点]4. 还原对应库资源浏览器定位到runtime的根目录,记住这个build.cmd右键打开命令行或pwd,像这样执行:脚本会下载一个ps1文件然后自动执行,我们等待即可,他会自动还原我们需要的库,并且把依赖的基础包也一并还原好。官方的代码结构中已经做好了nuget配置和输出目录, 我们已经不需要额外配置了,下一步进行编译。5.生成对应库的dll文件打开对应库的代码文件:右键打开命令行或pwd,像这样执行:等待编译结束去这个目录下找东西每个库都会生成到artifacts下面,然后不同架构对应一个文件夹, 此时你就可以在你的测试项目中直接引用这个dll了, 愉快的调试吧。[可选]6.生成依赖库文件我这里是想调试Microsoft.Extensions.DependencyInjection,在Nuget上就能看到他还依赖一个抽象定义包Microsoft.Extensions.DependencyInjection.Abstractions,为了不在调试中卡壳,我把这个包一并如法炮制。其它在查阅资料时发现其实也可以用VS直接编译,但需要配置些东西,我没看明白就用这个办法了,我也不需要编译所有的。VS Code也可以,但我主要用VS就略过这部分了build.cmd脚本不加参数似乎是编译所有包, 我不需要就跳过这个了
阳老师太受欢迎了|OceanBase观点文章精选Top 10
2022 OceanBase 年度发布会上,OceanBase 创始人兼首席科学家阳振坤在会上分享了《OceanBase 4.0 核心技术解读》的主题演讲,阳老师从 OceanBase 的 0.1 版本聊到今天的 4.0 发布,向大家分享了 4.0 的核心技术突破——业内首个单机分布式一体化数据库,RTO 从 30 秒迈入 8 秒,进入真正的秒级容灾时代。并表达了 12 年技术演进背后的思考——从 2010 年 OceanBase 的第一个版本开始到今天,已经过去 12 年多的时间,我们开发了好多的版本,可以说每一个版本都是整个 OceanBase 梦想的一次次迭代、一次次升级。2022 OceanBase 年度发布会上,OceanBase CEO 杨冰在会上分享了《小就是大,构建企业核心竞争力》的主题演讲。向大家汇报了OceanBase在金融、政企、公有云、开源方面的成绩,他表示:对于 OceanBase 而言我要做好四件事:产品、服务、生态、开发者四维一体,这将是 OceanBase 不断前进的“金钥匙”。在业务布局方面,核心系统升级、全面上云、业务出海将是支持OceanBase未来增长的三级火箭,针对不同业务规模的客户、不同区域的客户都会提供优质的数据库的产品服务,在为客户创造价值的同时实现自身的发展和共赢。在2022 HI COOL 全球创业者峰会上,OceanBase 创始人兼首席科学家阳振坤发表了《阳振坤:分布式技术引领关系数据库发展》的主题演讲,分享了全球主流关系型数据库发展的几个重要阶段以及对分布式数据库的畅想。阳老师认为:关系数据库的研制十分困难,分布式关系数据库的研制更加困难,分布式关系数据库是否可行?举个例子:“一个是 Google 的 Spanner,可能很多人了解它是从分布式存储直接跨到分布式数据库的;另一个是中国的 OceanBase 数据库,是从半分布式数据库到高可用数据库再到分布式关系数据库,虽然两者的技术路径不一样,但是结果是殊途同归。”2022 年,OceanBase 举办了 DC 2021 分布式数据库开发者大会。本次大会由中国电子技术标准化研究院研究室主任杨丽蕴女士开场致辞,并特别邀请了MySQL 之父、MariaDB 创始人 Michael“Monty”Widenius 与 PostgreSQL 全球开发组联合创始人 Bruce Momjian 带来深度的行业解析。同时 OceanBase 创始人阳振坤、CEO 杨冰、CTO 杨传辉、巨杉首席架构师 & 研发副总裁陈元熹、PingCAP 公司副总裁刘松,以及腾讯分布式数据库 TDSQL 首席架构师李海翔、华为云数据库首席架构师冯柯等多位重磅嘉宾也都莅临直播间,为开发者们贡献了一场分布式数据库领域的技术“盛宴”。本文从“分布式”、“云、开放性”、“开源生态”、“一致性”、“HTAP混合负载”、“一体化架构”、“核心首选”、“客户价值”八个关键词对本次大会进行了要点提炼。2022 OceanBase 年度发布会上,OceanBase 资深开源生态技术总监封仲淹在会上分享了《OceanBase 社区版 4.0 未来畅想》的主题演讲,向大家分享了 OceanBase 社区版 4.0 的全新规划与畅想。分享分为四部分,第一,关于生态;第二,关于OceanBase想建设一个什么样的社区;第三,关于 3.1.4 的特性;第四,未来 4.1 会是什么样。过去的2021年, 用一句话进行总结 “开源开放, 生态共赢”。电动汽车与分布式数据库,看似两个风马不相及的事物,其发展路径却极为雷同:两者都在较早期被发明,但都没有成为主流。21 世纪初,电动汽车再次进入人们的视野,与此同时,由于互联网业务的高并发和海量数据等处理需求,分布式数据库再次被提及,并掀起应用热潮。有需求便会有供给,市面上随之出现两种新的产品:混合动力汽车与分库分表数据库。本文对电动汽车与分布式数据库的发展进行了分析对照。
Java对象的引用和复制
在Java中,我们无时无刻不在接触和使用变量。从最简单的int,到复杂的自定义对象等等。然而,像Java,以及C#、JavaScript这类面向对象的语言,也总是会出现对象的复制和引用的问题。前段时间写了JavaScript中对象引用和复制,今天就来总结一下Java中的。众所周知,Java的变量无外乎就分为两大类:基本数据类型(也可以叫做内置数据类型):byte:字节型short:短整型int:整型long:长整型float:浮点型double:双精度浮点型boolean:布尔型char:字符型引用数据类型:其余无论是数组、还是自定义的类的对象,亦或是系统自带的类的对象,都是引用数据类型,例如我们常常用的字符串String类,实际上是引用数据类型。所有的引用数据类型的类都是Object类的子类。大家也知道,我们的变量都是放在内存里面的,那么基础数据类型和引用数据类型,其储存的形式可能有所不同,我们一一来看。1,基本数据类型和引用数据类型在内存中的形式简述(1) 基本数据类型基本数据类型,顾名思义就是最基本的数据,基本上基本数据类型只会储存它们的值。int a = 1;
int b = 2;可见,基本数据类型只会储存它自己的值,储存形式较为简单。(2) 引用数据类型我们自己创建的类的对象,或者是一些类创建的对象,都是引用数据类型。我们都知道,一个类说白了就是很多属性(成员变量)和方法的集合,那么同样地,在内存中,我们每new一个对象,就会开辟一小块内存空间,在里面存放这个类的属性及其值,也可以说引用类型在内存中就是一个引用指向一个键值对的集合。我们先新建一个类,下面将以这个自定义类为例:package com.example.singleinstance.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 猫类
*/
@Getter
@Setter
@NoArgsConstructor
public class Cat {
/**
* 名字
*/
private String name;
/**
* 类型
*/
private String type;
}实例化一个Cat对象,并给其属性赋值:Cat cat = new Cat();
cat.setName("大橘");
cat.setType("中华田园猫");可见我们创建了一个名为cat的猫的对象。在这里这个cat叫做引用,这个引用指向了对应的内存,也就是指向了我们新开辟的内存空间表示这个猫对象。可以说,这一句实例化猫对象的代码可以理解为:前半段Cat cat只是创建一个名为cat的引用,还没有创建出实际的对象(准备生一只猫并给它起了名字cat,但是还没生出来)后半段new Cat()才是真正的创建出了对象并会执行构造函数中的内容,开辟了内存空间(这时猫生出来了)整行代码,把cat这个引用指向创建的对象一步一步地来看,是不是就很好理解了呢?其实我们平时实例化对象的过程也就是如此。这个引用其实就很类似我们C语言或者C++的指针,指向一块内存空间,也就是说,引用事实上就是存放其指向的对象的内存地址的一个变量。2,对象引用和复制问题(1) 基本类型基本类型因为其只储存一个值也就是本身的值,因此赋值的时候就直接复制了,我们来看:int a = 1;
int b = a;
b = 2;
System.out.println(a);
System.out.println(b);可见把a赋值给b,然后改变b并没有改变a,这说明int b = a的时候,是把a的值复制给b了。(2) 引用类型我们来实例化上述猫类对象试试:Cat bigOrange = new Cat();
bigOrange.setName("大橘");
bigOrange.setType("中华田园猫");
Cat threeFlower = bigOrange;
threeFlower.setName("三花");
System.out.println("第一只猫:" + bigOrange.getName());
System.out.println("第二只猫:" + threeFlower.getName());好像出现了问题。为什么我们写法和上面一样,结果不一样了呢?因为Cat类的对象是引用类型,直接赋值的时候不会像基本类型一样把里面的值都给赋值给另一个对象,而只是建立引用。所以说当执行Cat threeFlower = bigOrange;的时候,只是新建了个名为threeFlower的引用,并指向了bigOrange所指向的内存空间。所以说事实上,上述两个变量bigOrange和threeFlower指向的是同一个内存空间。对比一下上面基本数据类型,大家就能够理解两者的差别。你只生了一只猫,但是起了两个名字(3) 再看==运算符通常我们使用==来判断两者是否相等,但事实上,==在比较基本数据类型和引用数据类型的时候作用是不一样的:基本数据类型中使用==是判断两者值是否相等引用数据类型中使用==是判断两者是否指向同一个内存空间我们来试一下:int a = 1;
int b = 2;
int c = 1;
System.out.println(a == b);
System.out.println(a == c);
Cat bigOrange = new Cat();
bigOrange.setName("大橘");
bigOrange.setType("中华田园猫");
Cat threeFlower = bigOrange;
threeFlower.setName("三花");
System.out.println(bigOrange == threeFlower);大家一定要知道这个==运算符的实际意义。所以说我们在判断字符串是否相等的时候,就不能使用==了而是equals方法,因为字符串是引用数据类型,即使两个String的值是一样的,但是可能不会指向同一个内存空间。3,值传递和引用传递值传递和引用传递,事实上并非是Java语言特有的概念,而是所有的编程语言中都存在的概念。我们先来看一下它们是什么:值传递:在调用函数时,将实际参数复制一个并传递到函数中,这样在函数中对参数进行修改,不会对原来的实际参数造成影响引用传递:在调用函数时,将实际参数的地址直接传递到函数中,这样在函数中对参数进行的修改,会对原来的实际参数造成影响同样地,我们先来看一下例子:public class Main {
public static void change(int a) {
a = 10;
}
public static void main(String[] args) {
int a = 1;
change(a);
System.out.println(a);
}
}结果:这就是值传递的一个很简单的例子,我们写了个change函数“试图”修改传入的值,但事实上并没有成功。因为传入的是值类型的变量,传入后,变量的值被复制给了参数,然后在函数内修改了参数的值,并不影响原来被传入变量的值。再来看一个例子:public class Main {
public static void change(Cat cat) {
cat.setName("乳白");
cat.setType("英短");
}
public static void main(String[] args) {
Cat cat = new Cat();
cat.setName("大橘");
cat.setType("中华田园猫");
change(cat);
System.out.println("name: " + cat.getName() + " type: " + cat.getType());
}
}结果:可见和上面的值传递结果不同,这里传入了猫类的引用,由于cat是自定义的类型,属于引用数据类型变量,因此传入时,仅仅是对象的地址被赋值给了参数,在函数内对这个对象的属性进行修改,也会影响到原来被传入的对象。但是,这个示例就是引用传递吗?我们来继续看一个例子:public class Main {
public static void change(Cat cat) {
Cat cat1 = new Cat();
cat1.setName("麻花");
cat1.setType("美短");
cat = cat1;
}
public static void main(String[] args) {
Cat cat = new Cat();
cat.setName("大橘");
cat.setType("中华田园猫");
change(cat);
System.out.println("name: " + cat.getName() + " type: " + cat.getType());
}
}结果和上面一样吗?然而并不是:这个示例也说明了,在Java中所有的参数传递都是值传递,我们知道了Java中一个引用数据类型变量实质上是存放对象地址的变量,那么上述在将引用类型作为参数传递的时候,事实上是将对象的地址复制给了函数参数,在函数内将一个新的对象赋值给这个参数,也只是使这个参数指向了新的对象,并不会使原来被传入参数改变指向。也就是说,传递引用数据类型的时候,实质上也是值传递,即把引用数据类型变量的地址复制给了参数。在C++语言中,就存在着值传递和引用传递,直接将一个变量或者指针传入函数的时候就是值传递,而在形参名前面加上&,就会使传入的参数变成引用传递。4,引用类型的深复制那假设上述我就要克隆一个猫猫出来改一改(复制一个猫对象而不是建立引用),变为另一只猫怎么办呢?很简单,我们把猫类使用Cloneable接口并重写clone方法即可,clone方法需要具体自己实现。package com.example.singleinstance.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 猫类
*/
@Getter
@Setter
@NoArgsConstructor
public class Cat implements Cloneable {
/**
* 名字
*/
private String name;
/**
* 类型
*/
private String type;
@Override
public Cat clone() {
// 自己实现克隆逻辑
}
}那clone方法要怎么写呢?这个我这里有两种方法。其实,你也可以自己另写一个方法实现对象深复制,但是规范起见,建议还是在对应的类中使用Cloneable接口并实现clone方法。(1) 反射递归复制法我们可以用反射方法先获取要复制的类的所有字段,然后依次判断字段是基本类型还是引用类型,如果是引用类型则递归进行该字段复制,否则直接给其字段赋值即可。我们把Cat类改装如下:package com.example.singleinstance.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.lang.reflect.Field;
/**
* 猫类
*/
@Getter
@Setter
@NoArgsConstructor
public class Cat implements Cloneable {
/**
* 名字
*/
private String name;
/**
* 类型
*/
private String type;
/**
* 克隆对象自身-递归部分
*
* @param origin 要被克隆的对象
* @return 克隆之后的对象
*/
private Object clone(Object origin) throws Exception {
// 首先是判断传入的是否是基本数据类型,如果是则直接返回
// 传入null也直接返回
// String虽然是引用类型,但是比较特殊,因为String对象基本上也只是储存字符串值,遍历其属性是没有意义的
if (origin == null || origin instanceof Number || origin instanceof Character || origin instanceof Boolean || origin instanceof String) {
return origin;
}
// 否则,获取被克隆对象的所有的字段
Field[] fields = origin.getClass().getDeclaredFields();
// 新建一个对象,把原对象的属性值复制给新对象的属性值(调用自身类的无参构造器创建新实例)
Object result = origin.getClass().getDeclaredConstructor().newInstance();
// 遍历字段,判断字段类型,如果是基本类型则直接赋值,否则进行递归
for (Field field : fields) {
// 使该字段可以访问
field.setAccessible(true);
// 获取对象的对应字段值,并把对应值赋值给新对象对应属性
field.set(result, clone(field.get(origin)));
}
return result;
}
/**
* 克隆对象自身-启动部分
*
* @return 克隆后的对象
*/
@Override
public Cat clone() {
Object result = null;
try {
result = clone(this);
} catch (Exception e) {
e.printStackTrace();
}
return (Cat) result;
}
}可见增加了两个方法用于克隆(深复制)对象自身。我们来试一下子:Cat bigOrange = new Cat();
bigOrange.setName("大橘");
bigOrange.setType("中华田园猫");
// 克隆一个大橘赋值给三花
Cat threeFlower = bigOrange.clone();
threeFlower.setName("三花");
System.out.println(bigOrange.getName());
System.out.println(threeFlower.getName());
System.out.println(bigOrange == threeFlower);(2)【推荐】序列化再反序列化法上述方法大家也发现了:不仅比较复杂,而且只是适用于大多数情况,不适用于所有情况,例如存在有的类没有无参构造器的时候就出问题了。还有一个方法就是使该类可以序列化,然后先将自己序列化为对象流,再把流反序列化为对象即可实现深复制。首先,被复制的类也就是上述Cat类需要同时实现Cloneable和Serializable接口,然后重写clone方法如下:/**
* 克隆对象自身
*
* @return 克隆后的对象
*/
@Override
public Cat clone() {
Cat result = null;
try {
// 实例化字节序列输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 实例化对象写入流
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 读取对象自身并写入字节序列输出流,这样就完成了对象自身的序列化
oos.writeObject(this);
// 然后再实例化字节序列输入流,将上面序列化的结果再读取,这样就把刚刚序列化的内容又反序列化为对象了,反序列化得到的对象值和原来完全相同但是却不再是同一个对象了
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
// 读取流为对象
result = (Cat) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}我们来试一下子:Cat bigOrange = new Cat();
bigOrange.setName("大橘");
bigOrange.setType("中华田园猫");
// 克隆一个大橘赋值给三花
Cat threeFlower = bigOrange.clone();
threeFlower.setName("三花");
System.out.println(bigOrange.getName());
System.out.println(threeFlower.getName());
System.out.println(bigOrange == threeFlower);效果相同。5,总结理解Java中对象的引用和复制,我们首先要搞清楚Java中有基本数据类型和引用数据类型这两大数据类型,以及它们在内存中的形式,赋值的机制的不同等等,理解了这些,一些问题也会迎刃而解。
2023 年8个ChatGPT 的替代品
这种先进的 AI 技术不仅可以根据命令生成、重写和汇总文本,还可以与用户进行交互。它会记住以前的对话,甚至可以根据用户输入提出问题,甚至可以编写代码并向用户解释。其实还有许多其他的机器人在本文中,我将整理8 个 ChatGPT 替代方案。Neuroflash那么 Neuroflash 就主要服务于德语内容生成器。Neuroflash 基于 GPT-3.5 构建的聊天助手是 ChatGPT 的绝佳替代品。与 ChatGPT 和其他类似工具不同,Neuroflash 不需要单独的用户界面——它可以在常规文本编辑器中轻松访问,与 ChatGPT 一样,Neuroflash 也可以编写代码。遗憾的是没有像ChatGPT那样的语法高亮Jasper ChatJasper 是目前市场上最流行的文本生成器之一。与 Writesonic 类似,Jasper 对 ChatGPT 的发布反应非常迅速,并在 ChatGPT 发布大约三周后的 2022 年 12 月 20 日发布了 Jasper Chat 功能:但是Jasper Chat 还不能从互联网上提取数据,这就是聊天机器人有时会重现过时信息并且无法提供来源的原因。Chatsonic (Writesonic)Chatsonic 是 AI 文本生成器 Writesonic 的一项新功能,它可以说是英语文本 ChatGPT 的最佳替代品。该工具目前仍处于测试阶段,比如说如果工具无法处理输入,不会生成任何输出。Chatsonic 也不会编写代码,但是与 ChatGPT 相比,它具有显着优势:它提供了访问当前谷歌数据的选项,而 ChatGPT 的答案有时是 1、2 或 3 年前的。比如说当你询问时事时,比如 2022 年世界杯,你会从 Chatsonic 得到正确答案YouChatYou.com 是第一个将聊天助手集成到其搜索结果中的已知搜索引擎(并且是公开的)。它是 ChatGPT 的一个很好的替代品:它在答案中包含自己的搜索索引,因此也可以回答有关时事的问题将源网页包含在答案中,并且有脚注编号Perplexity AIPerplexity 是一个基于 OpenAI API 的搜索引擎,但与 ChatGPT 不同的是它的答案中不仅包括训练数据,还包括来自互联网的内容。在答案中以脚注数字的形式引用了来源。所有引用的网页作为搜索结果显示如下:但是他的问题与 You.com 类似,就是答案的质量仍然参差不齐。但是我认为搜索结果和聊天响应的混合显示是引领潮流的。可以想象未来的 Google 或 Bing 可能看起来像这样,或者至少是类似的东西。Perplexity 不是聊天机器人,而是搜索引擎(或者更准确地说,是答案引擎),其输出中不包含过去的问题或搜索词。Github Copilot如果只想生成代码而不是文本,GitHub Copilot 是 ChatGPT 的最佳替代方案。与 ChatGPT 一样,该工具也基于 OpenAI API,但遵循更适合编程的规则:它不提供自己的用户界面或应用程序,而是作为扩展安装,包括 Neovim、JetBrains IDE、Visual Studio 和 Visual Studio Code。它可以处理许多不同的编程语言,包括 Python、JavaScript、TypeScript、Ruby、Go、C# 和 C++。GitHub Copilot 的价格为每月 10 美元起,目前提供 60 天的试用期。虽然花钱,但是这个还是挺值的。Google LaMDALaMDA(“对话应用程序的语言模型”的缩写)是一个聊天助手,或者更准确地说是一个开发聊天助手的系统,由谷歌于 2021 年年中推出。与 GPT-3、BERT 和 ChatGPT 类似,它基于 Transformer 架构。与 ChatGPT 不同,LaMDa 更积极地参与对话、提出问题、讲述自己,并且不仅根据事实而且还“情感地”回应自己的输入。在 2021 年谷歌“负责任的人工智能”部门工作的软件开发人员布莱克勒莫因公开认为 LaMDA 具有意识和个性,并因此被解雇,使得它声名狼藉。2022年5月,谷歌在谷歌I/O开发者大会上发布了LaMDA 2,带来了多项新功能。其中包括“想象它”模式,其中 LaMDA 对给定情况产生共鸣,或“列出它”模式,它允许 LaMDA 用于学习某些东西。但是与 YouChat不同,谷歌决定限制 LaMDA 的发布。这是因为该技术可以传递用于训练语言模型的文本中的种族主义、性别歧视、反犹太主义和其他形式的偏见或错误信息,并且(很像 ChatGPT)并不总是坚持事实。因此,该技术根据“质量、安全和落地”的严格标准进一步评估和开发。不过可以想象,自从ChatGPT 发布后,谷歌肯定会加速LaMDA 的开发。我们可以使用 AI Test Kitchen 应用程序免费测试 LaMDA(某些功能)。目前只有有来自美国才能使用。Sparrow2022 年 9 月,谷歌的子公司 Deepmind 推出了一款名为 Sparrow 的人工智能聊天机器人。根据 Deepmind 的说法,Sparrow 是一个实验模型和概念证明,将有助于使聊天机器人更有用、更准确、更安全。与 ChatGPT 类似,它使用强化学习 (RL) 进行训练,这意味着真实的人会提供对 Sparrow 输出的反馈:Sparrow 使用 Google 搜索来寻找合适的来源。人工智能究竟是如何做到这一点的,以及它如何为答案选择合适的搜索结果,可以在相关的研究论文中阅读。根据 Deepmind 首席执行官 Demis Hassabis 的说法,Sparrow 的私人测试版将于今年晚些时候发布。替代模型ChatGPT使用GPT-3.5,由三个语言模型code-davinci-002、text-davinci-002和text-davinci-003组成。但是,可以考虑以下的语言模型 (LLM) 用于 AI 聊天机器人开发:希望本文对你有所帮助https://avoid.overfit.cn/post/c5b24fe729424dc3a10fed92d0f5fce5作者:Daniel Peric
一文搞懂MQTT,如何在SpringBoot中使用MQTT实现消息的订阅和发布
之前介绍了RabbitMQ以及如何在SpringBoot项目中整合使用RabbitMQ,看过的朋友都说写的比较详细,希望再总结一下目前比较流行的MQTT。所以接下来,就来介绍什么MQTT?它在IoT中有着怎样的作用?如何在项目中使用MQTT?一、MQTT介绍1.1 什么是MQTT?MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。MQTT具有协议简洁、轻巧、可扩展性强、低开销、低带宽占用等优点,已经有PHP,JAVA,Python,C,C#,Go等多个语言版本,基本可以使用在任何平台上。在物联网、小型设备、移动应用等方面有较广泛的应用,特别适合用来当做物联网的通信协议。1.2 MQTT特点MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。MQTT协议是为硬件性能有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:1.使用发布/订阅消息模式,提供多对多的消息发布,解除应用程序耦合;2.对负载内容屏蔽的消息传输;3.使用TCP/IP 提供网络连接;4.支持三种消息发布服务质量(QoS):QoS 0(最多一次):消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这个级别可用于如下情况,环境传感器数据,丢失一次数据无所谓,因为不久后还会有第二次发送。QoS 1(至少一次):确保消息到达,但消息重复可能会发生。QoS 2(只有一次):确保消息到达一次。这个级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。5.传输数据小,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;(用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。)1.3 MQTT应用场景MQTT作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有着广泛的应用。MQTT服务只负责消息的接收和传递,应用系统连接到MQTT服务器后,可以实现采集数据接收、解析、业务处理、存储入库、数据展示等功能。常见的应用场景主要有以下几个方面:(1)消息推送: 如PC端的推送公告,比如安卓的推送服务,还有一些即时通信软件如微信、易信等也是采用的推送技术。(2)智能点餐: 通过MQTT消息队列产品,消费者可在餐桌上扫码点餐,并与商家后端系统连接实现自助下单、支付。(3)信息更新: 实现商场超市等场所的电子标签、公共场所的多媒体屏幕的显示更新管理。(4)扫码出站: 最常见的停车场扫码缴费,自动起竿;地铁闸口扫码进出站。二、MQTT的角色组成2.1 MQTT的客户端和服务端2.1.1 服务端(Broker)EMQX就是一个MQTT的Broker,emqx只是基于erlang语言开发的软件而已,其它的MQ还有ActiveMQ、RabbitMQ、HiveMQ等等。EMQX服务端:https://www.emqx.io/zh/downloads?os=Windows2.1.2 客户端(发布/订阅)EMQX客户端:https://mqttx.app/zh这个是用来测试验证的客户端,实际项目是通过代码来实现我们消息的生产者和消费者。2.2 MQTT中的几个概念相比RabbitMQ等消息队列,MQTT要相对简单一些,只有Broker、Topic、发布者、订阅者等几部分构成。接下来我们先简单整理下MQTT日常使用中最常见的几个概念:1.Topic主题:MQTT消息的主要传播途径, 我们向主题发布消息, 订阅主题, 从主题中读取消息并进行.业务逻辑处理, 主题是消息的通道2.生产者:MQTT消息的发送者, 他们向主题发送消息3.消费者:MQTT消息的接收者, 他们订阅自己需要的主题, 并从中获取消息4.broker服务:消息转发器, 消息是通过它来承载的, EMQX就是我们的broker, 在使用中我们不用关心它的具体实现其实, MQTT的使用流程就是: 生产者给broker的某个topic发消息->broker通过topic进行消息的传递->订阅该主题的消费者拿到消息并进行相应的业务逻辑三、EMQX的安装和使用下面以Windows为例,演示Windows下如何安装和使用EXQX。step 1:下载EMQ安装包,配置EMQ环境EMQX服务端:https://www.emqx.io/zh/downloads?os=Windowsstep 2:下载压缩包解压,cmd进入bin文件夹step 3:启动EMQX服务在命令行输入:emqx start 启动服务,打卡浏览器输入:http://localhost:18083/ 进入登录页面。默认用户名密码 admin/public 。登录成功后,会进入emqx的后台管理页面,如下图所示:四、使用SpringBoot整合MQTT协议前面介绍了MQTT协议以及如何安装和启动MQTT服务。接下来演示如何在SpringBoot项目中整合MQTT实现消息的订阅和发布。4.1 创建工程首先,创建spring-boot-starter-mqtt父工程,在父工程下分别创建消息的提供者spring-boot-starter-mqtt-provider 模块和消息的消费者spring-boot-starter-mqtt-consumer模块。4.2 实现生产者接下来,修改生产者模块spring-boot-starter-mqtt-provider 相关的代码,实现消息发布的功能模块。4.2.1 导入依赖包修改pom.xml 文件,添加MQTT相关依赖,具体示例代码如下所示:<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>5.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
</dependencies>4.2.2 修改配置文件修改application.yml配置文件,增加MQTT相关配置。示例代码如下所示:spring:
application:
name: provider
#MQTT配置信息
mqtt:
#MQTT服务地址,端口号默认11883,如果有多个,用逗号隔开
url: tcp://127.0.0.1:11883
#用户名
username: admin
#密码
password: public
#客户端id(不能重复)
client:
id: provider-id
#MQTT默认的消息推送主题,实际可在调用接口是指定
default:
topic: topic
server:
port: 8080 4.2.3 消息生产者客户端配置创建MqttProviderConfig配置类,读取application.yml中的相关配置,并初始化创建MQTT的连接。示例代码如下所示:import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@Slf4j
public class MqttProviderConfig {
@Value("${spring.mqtt.username}")
private String username;
@Value("${spring.mqtt.password}")
private String password;
@Value("${spring.mqtt.url}")
private String hostUrl;
@Value("${spring.mqtt.client.id}")
private String clientId;
@Value("${spring.mqtt.default.topic}")
private String defaultTopic;
/**
* 客户端对象
*/
private MqttClient client;
/**
* 在bean初始化后连接到服务器
*/
@PostConstruct
public void init(){
connect();
}
/**
* 客户端连接服务端
*/
public void connect(){
try{
//创建MQTT客户端对象
client = new MqttClient(hostUrl,clientId,new MemoryPersistence());
//连接设置
MqttConnectOptions options = new MqttConnectOptions();
//是否清空session,设置false表示服务器会保留客户端的连接记录(订阅主题,qos),客户端重连之后能获取到服务器在客户端断开连接期间推送的消息
//设置为true表示每次连接服务器都是以新的身份
options.setCleanSession(true);
//设置连接用户名
options.setUserName(username);
//设置连接密码
options.setPassword(password.toCharArray());
//设置超时时间,单位为秒
options.setConnectionTimeout(100);
//设置心跳时间 单位为秒,表示服务器每隔 1.5*20秒的时间向客户端发送心跳判断客户端是否在线
options.setKeepAliveInterval(20);
//设置遗嘱消息的话题,若客户端和服务器之间的连接意外断开,服务器将发布客户端的遗嘱信息
options.setWill("willTopic",(clientId + "与服务器断开连接").getBytes(),0,false);
//设置回调
client.setCallback(new MqttProviderCallBack());
client.connect(options);
} catch(MqttException e){
e.printStackTrace();
}
}
public void publish(int qos,boolean retained,String topic,String message){
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setQos(qos);
mqttMessage.setRetained(retained);
mqttMessage.setPayload(message.getBytes());
//主题的目的地,用于发布/订阅信息
MqttTopic mqttTopic = client.getTopic(topic);
//提供一种机制来跟踪消息的传递进度
//用于在以非阻塞方式(在后台运行)执行发布是跟踪消息的传递进度
MqttDeliveryToken token;
try {
//将指定消息发布到主题,但不等待消息传递完成,返回的token可用于跟踪消息的传递状态
//一旦此方法干净地返回,消息就已被客户端接受发布,当连接可用,将在后台完成消息传递。
token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
} catch (MqttException e) {
e.printStackTrace();
}
}
}4.2.4 生产者客户端消息回调创建MqttProviderCallBack类并继承MqttCallback,实现相关消息回调事件,示例代码如下图所示:import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
public class MqttConsumerCallBack implements MqttCallback{
/**
* 客户端断开连接的回调
*/
@Override
public void connectionLost(Throwable throwable) {
System.out.println("与服务器断开连接,可重连");
}
/**
* 消息到达的回调
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
System.out.println(String.format("接收消息主题 : %s",topic));
System.out.println(String.format("接收消息Qos : %d",message.getQos()));
System.out.println(String.format("接收消息内容 : %s",new String(message.getPayload())));
System.out.println(String.format("接收消息retained : %b",message.isRetained()));
}
/**
* 消息发布成功的回调
*/
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
System.out.println(String.format("接收消息成功"));
}
}4.2.5 创建Controller控制器实现消息发布功能创建SendController控制器类,实现消息的发送功能,示例代码如下所示:@Controller
public class SendController {
@Autowired
private MqttProviderConfig providerClient;
@RequestMapping("/sendMessage")
@ResponseBody
public String sendMessage(int qos,boolean retained,String topic,String message){
try {
providerClient.publish(qos, retained, topic, message);
return "发送成功";
} catch (Exception e) {
e.printStackTrace();
return "发送失败";
}
}
}4.3 实现消费者前面完成了生成者消息发布的模块,接下来修改消费者模块spring-boot-starter-mqtt-consumer实现消息订阅、处理的功能。4.3.1 导入依赖包修改pom.xml 文件,添加MQTT相关依赖,具体示例代码如下所示:<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>5.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
</dependencies>4.3.2 修改配置文件修改application.yml配置文件,增加MQTT相关配置。示例代码如下所示:spring:
application:
name: consumer
#MQTT配置信息
mqtt:
#MQTT服务端地址,端口默认为11883,如果有多个,用逗号隔开
url: tcp://127.0.0.1:11883
#用户名
username: admin
#密码
password: public
#客户端id(不能重复)
client:
id: consumer-id
#MQTT默认的消息推送主题,实际可在调用接口时指定
default:
topic: topic
server:
port: 80854.3.3 消费者客户端配置创建消费者客户端配置类MqttConsumerConfig,读取application.yml中的相关配置,并初始化创建MQTT的连接。示例代码如下所示:import javax.annotation.PostConstruct;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MqttConsumerConfig {
@Value("${spring.mqtt.username}")
private String username;
@Value("${spring.mqtt.password}")
private String password;
@Value("${spring.mqtt.url}")
private String hostUrl;
@Value("${spring.mqtt.client.id}")
private String clientId;
@Value("${spring.mqtt.default.topic}")
private String defaultTopic;
/**
* 客户端对象
*/
private MqttClient client;
/**
* 在bean初始化后连接到服务器
*/
@PostConstruct
public void init(){
connect();
}
/**
* 客户端连接服务端
*/
public void connect(){
try {
//创建MQTT客户端对象
client = new MqttClient(hostUrl,clientId,new MemoryPersistence());
//连接设置
MqttConnectOptions options = new MqttConnectOptions();
//是否清空session,设置为false表示服务器会保留客户端的连接记录,客户端重连之后能获取到服务器在客户端断开连接期间推送的消息
//设置为true表示每次连接到服务端都是以新的身份
options.setCleanSession(true);
//设置连接用户名
options.setUserName(username);
//设置连接密码
options.setPassword(password.toCharArray());
//设置超时时间,单位为秒
options.setConnectionTimeout(100);
//设置心跳时间 单位为秒,表示服务器每隔1.5*20秒的时间向客户端发送心跳判断客户端是否在线
options.setKeepAliveInterval(20);
//设置遗嘱消息的话题,若客户端和服务器之间的连接意外断开,服务器将发布客户端的遗嘱信息
options.setWill("willTopic",(clientId + "与服务器断开连接").getBytes(),0,false);
//设置回调
client.setCallback(new MqttConsumerCallBack());
client.connect(options);
//订阅主题
//消息等级,和主题数组一一对应,服务端将按照指定等级给订阅了主题的客户端推送消息
int[] qos = {1,1};
//主题
String[] topics = {"topic1","topic2"};
//订阅主题
client.subscribe(topics,qos);
} catch (MqttException e) {
e.printStackTrace();
}
}
/**
* 断开连接
*/
public void disConnect(){
try {
client.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
}
/**
* 订阅主题
*/
public void subscribe(String topic,int qos){
try {
client.subscribe(topic,qos);
} catch (MqttException e) {
e.printStackTrace();
}
}
}4.3.4 消费者客户端消息回调创建MqttConsumerCallBack类并继承MqttCallback,实现相关消息回调事件,示例代码如下图所示:import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
public class MqttConsumerCallBack implements MqttCallback{
/**
* 客户端断开连接的回调
*/
@Override
public void connectionLost(Throwable throwable) {
System.out.println("与服务器断开连接,可重连");
}
/**
* 消息到达的回调
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
System.out.println(String.format("接收消息主题 : %s",topic));
System.out.println(String.format("接收消息Qos : %d",message.getQos()));
System.out.println(String.format("接收消息内容 : %s",new String(message.getPayload())));
System.out.println(String.format("接收消息retained : %b",message.isRetained()));
}
/**
* 消息发布成功的回调
*/
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
System.out.println(String.format("接收消息成功"));
}
}4.3.5 创建Controller控制器,实现MQTT连接的建立和断开接下来,创建Controller控制器MqttController,并实现MQTT连接的建立和断开等方法。示例代码如下所示:import com.weiz.mqtt.config.MqttConsumerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MqttController {
@Autowired
private MqttConsumerConfig client;
@Value("${spring.mqtt.client.id}")
private String clientId;
@RequestMapping("/connect")
@ResponseBody
public String connect(){
client.connect();
return clientId + "连接到服务器";
}
@RequestMapping("/disConnect")
@ResponseBody
public String disConnect(){
client.disConnect();
return clientId + "与服务器断开连接";
}
}4.4 测试验证首先,分别启动生产者spring-boot-starter-mqtt-provider 和消费者spring-boot-starter-mqtt-consumer两个项目,打开浏览器,输入地址http://localhost:18083/,在EMQX管理界面可以看到连接上来的两个客户端。如下图所示:接下来,调用生产者的消息发布接口验证消息发布是否成功。使用Pomstman调用消息发送接口:http://localhost:8080/sendMessage ,如下图所示:通过上图可以发现,生产者模块已经把消息发送成功。接下来查看消费者模块,验证消息是否处理成功。如下图所示:通过日志输出可以发现,消费者已经成功接收到生产者发送的消息,说明我们成功实现在Spring Boot项目中整合MQTT实现了消息的发布和订阅的功能。最后以上就是如何在Spring Boot中使用MQTT的详细内容,更多关于在Spring Boot中MQTT的使用大家可以去自己研究学习。比如:如何利用qos机制保证数据不会丢失?消息的队列和排序?集群模式下的应用?等等。