python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端

2023-10-26

python3+tkinter实践历程(四)——基于socket通信与tkinter的TCP串口客户端(仿CRT)


系列文章目录

python3+tkinter实践历程(一)——基于requests与tkinter的API工具
python3+tkinter实践历程(二)——基于tkinter的日志检索工具
python3+tkinter实践历程(三)——基于requests与tkinter的模拟web登录工具
python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端

分享背景

①分享意图在于帮助新入门的朋友,提供思路,里面详细的注释多多少少能解决一些问题。欢迎大佬指点跟交流。
②2021年8月,开始陆续有需求制作一些工具,因为python语言跟tkinter工具相对简单,所以就基于这些做了好几个不同用处的工具。
③分享从完成的第一个工具开始分享,分享到最新完成的工具,对于tkinter的理解也从一开始的摸索入门,到后来逐渐熟练,完成速度也越来越快,所用到的tk的功能点也越来越多。
④这是发布前做的最后一个工具,也是最难、最复杂、要求最多、耗时最长的一个。

制作背景

① 研发部自己做一个串口服务器,硬件部解决板子问题,开发将软件写入板子。最终服务器可以用特制的线连通24个设备的串口,开启了TCP服务端口,服务器驱动将24个设备的日志实时的通过socket套接字将发送给已连接的TCP客户端。
② 本人所做的就是这个接收、处理串口服务器传过来的日志的TCP客户端。
③ 功能需求如最终功能所示,完全实现了所有预期需求。

最终功能

① 提供界面输入-TCP服务器的IP、端口
② 提供界面选项-保存日志的目录,成功连接TCP服务器后,自动将所有接收到的日志保存,按日期划分文件夹,按串口划分log文件。
③ 提供界面打印界面,自主选择串口打印,且与保存日志功能完全分离,互不干扰。
④ 打印日志的界面支持增、删、重命名。
⑤ 工具支持读取界面配置、记录历史配置
⑥ 支持给不同串口发送命令,发送命令框支持回车发送+按钮发送
⑦ 快速发送按钮支持读取配置
⑧ 提供选项-滚动条是否自动跟随新打印的日志,支持实时改变

工具截图展示

在这里插入图片描述
在这里插入图片描述

代码详解

import tkinter as tk
from tkinter import ttk
import tkinter.font as tf
import threading
import os
import datetime
import socket
import re
import time
import json
from tkinter.filedialog import askdirectory


class SocketServer(object):
    def __init__(self, ip, port):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        print('开始连接串口服务器%s:%d' % (ip, port))
        self.sock.connect((ip, port))
        print('连接完成')

    def get_serial_data(self):
        recv_info = self.sock.recv(7168).decode("utf-8", "replace")       # 服务器传过来的是bytes类型,收到数据后编码变成str
        # print(recv_info)
        return recv_info

    def send_data(self, data):
        self.sock.send(data)


class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.grid()
        self.save_log_dir = ''
        self.is_running = ''
        self.LOG_LINE_NUM = 0
        self.see_log = True
        self.button_column = 0      # 用于记录button的最大值
        self.top = ''               # 重命名窗口
        self.tab = {}       # TAB集合
        self.active_tab = ''    # 当前置顶
        self.create_widgets(master)

    def create_widgets(self, master):
        """创建图形化界面"""
        # 菜单栏
        menus = tk.Menu(self)
        num1 = tk.Menu(menus, tearoff=False)
        menus.add_cascade(label='菜单栏', menu=num1)
        num1.add_command(label='邮件')
        master['menu'] = menus
        # 第一行
        row1 = tk.Frame(self)
        row1.grid(row=0, column=0, sticky='w', padx=(15, 0), pady=10)

        # IP端口输入框
        server_url = ttk.Label(row1, text="串口服务器地址(IP:端口)")
        server_url.grid(row=0, column=1)
        server_url = tk.StringVar()
        server_url.set("10.100.10.180:9036")         # set的作用的预置的文字(在标签内可变的文本)
        self.server_url = tk.Entry(row1, textvariable=server_url, width=20)
        self.server_url.grid(row=0, column=2, padx=2, pady=2)
        # 日志存放目录
        ttk.Label(row1, text="", width=1).grid(row=0, column=3)
        ttk.Label(row1, text="日志存放地址").grid(row=0, column=4)
        self.dir = tk.StringVar()
        self.dir.set("")
        self.log_dir = ttk.Entry(row1, textvariable=self.dir, width=35)
        self.log_dir.grid(row=0, column=5)
        ttk.Button(row1, text="选择日志文件夹", command=self.selectPath).grid(row=0, column=6)
        # 开始连接服务器并接收日志按钮
        ttk.Label(row1, text="", width=2).grid(row=0, column=7)
        start_btn = ttk.Button(row1, text="连接串口服务器", command=self.start_socket_conn).grid(row=0, column=8)
        # 停止按钮
        stop_btn = ttk.Button(row1, text="停止连接", command=self.start_stop_task).grid(row=0, column=9, padx=(10, 0))

        self.see_log_value = tk.IntVar()
        self.see_log_value.set(1)
        ttk.Checkbutton(row1, text="滚动条跟随日志", variable=self.see_log_value, command=self.change_see_log).grid(row=0, column=14, padx=20)

        ttk.Button(row1, text='+', command=self.add_log_text, width=2).grid(row=0, column=1000, padx=2)
        ttk.Button(row1, text='-', command=self.del_log_text, width=2).grid(row=0, column=1001, padx=2)
        ttk.Button(row1, text='重命名', command=self.start_tab_rename, width=5).grid(row=0, column=1002, padx=2)

        # 第二行 tab页  非固定行
        self.row2 = ttk.Notebook(self)
        self.row2.grid(row=1, column=0, sticky='w', padx=(15, 0))

        # 读取第二第三行的内容
        interface_data = self.work_for_data('tab', 'read')
        if not interface_data:
            interface_data = {'log1': {'name_str': 'log1', 'serial_value': ''}}
        for name in interface_data:
            column = self.button_column + 1
            self.tab[name] = dict()
            self.tab[name]['Frame'] = tk.Frame(self, relief='ridge')
            self.tab[name]['Frame'].grid(row=0, column=column)
            self.tab[name]['name_str'] = interface_data[name]['name_str']
            self.row2.add(self.tab[name]['Frame'], text=interface_data[name]['name_str'])

            ft = tf.Font(size=10)
            self.tab[name]['text'] = tk.Text(self.tab[name]['Frame'], width=180, height=50, font=ft, padx=5, pady=5,
                                             relief='sunken')
            log_slide_bar = tk.Scrollbar(self.tab[name]['Frame'], command=self.tab[name]['text'].yview,
                                         orient="vertical")
            log_slide_bar.grid(row=0, column=1, sticky='ns')
            self.tab[name]['text'].grid(row=0, column=0)
            self.tab[name]['text'].config(font=ft, yscrollcommand=log_slide_bar.set)
            ttk.Label(self.tab[name]['Frame'], text="当前串口").grid(row=0, column=2, sticky='n', padx=(0, 5))
            self.tab[name]['serial'] = ttk.Combobox(self.tab[name]['Frame'], state='readonly', width=10)
            self.tab[name]['serial'].grid(row=0, column=3, sticky='n')
            self.tab[name]['serial_value'] = interface_data[name]['serial_value']
            self.tab[name]['serial']['value'] = (
                                            'serial01', 'serial02', 'serial03', 'serial04', 'serial05', 'serial06',
                                            'serial07', 'serial08', 'serial09', 'serial10', 'serial11', 'serial12',
                                            'serial13', 'serial14', 'serial15', 'serial16', 'serial17', 'serial18',
                                            'serial19', 'serial20', 'serial21', 'serial22', 'serial23', 'serial24')
            self.tab[name]['serial'].bind('<<ComboboxSelected>>', self.start_change_serial)
            if interface_data[name]['serial_value']:
                self.tab[name]['serial'].set(interface_data[name]['serial_value'])

        row4 = tk.Frame(self)
        row4.grid(row=3, column=0, sticky='w', padx=(15, 0), pady=(15, 0))

        # 数据输入框
        ft = tf.Font(size=10)
        self.log_input = tk.Text(row4, width=100, height=4, font=ft)
        self.log_input.grid(row=0, column=1)
        self.log_input.bind('<Return>', self.start_send_data)       # text框绑定回车键
        ttk.Button(row4, text="发送", command=self.start_send_data).grid(row=0, column=2, padx=10)

        # 第四行
        row5 = tk.Frame(self)
        row5.grid(row=4, column=0, sticky='w', padx=(15, 0), pady=(15, 0))

        # 快捷输入按钮
        quick_send_data = self.work_for_data('quick_send')
        if quick_send_data:
            print(quick_send_data)
            column = 0
            for data in quick_send_data:
                ttk.Button(row5, text=data['name'], command=lambda data2=data: self.start_quick_print(data2)).grid(row=0, column=column, padx=(0, 10))
                column = column + 1

    def add_log_text(self):
        """增加多个tab页"""
        now_log_text_num = 0
        for ii in range(1, 1000):
            if 'log' + str(ii) not in self.tab:
                now_log_text_num = ii
                break
            else:
                now_log_text_num = 1000
        column = self.button_column + 1
        name = 'log' + str(now_log_text_num)
        self.tab[name] = dict()
        self.tab[name]['Frame'] = tk.Frame(self, relief='ridge')
        self.tab[name]['Frame'].grid(row=0, column=column)
        self.tab[name]['name_str'] = name
        self.row2.add(self.tab[name]['Frame'], text=name)

        ft = tf.Font(size=10)
        self.tab[name]['text'] = tk.Text(self.tab[name]['Frame'], width=180, height=50, font=ft, padx=5, pady=5,
                                         relief='sunken')
        log_slide_bar = tk.Scrollbar(self.tab[name]['Frame'], command=self.tab[name]['text'].yview,
                                     orient="vertical")
        log_slide_bar.grid(row=0, column=1, sticky='ns')
        self.tab[name]['text'].grid(row=0, column=0)
        self.tab[name]['text'].config(font=ft, yscrollcommand=log_slide_bar.set)
        ttk.Label(self.tab[name]['Frame'], text="当前串口").grid(row=0, column=2, sticky='n', padx=(0, 5))
        self.tab[name]['serial'] = ttk.Combobox(self.tab[name]['Frame'], state='readonly', width=10)
        self.tab[name]['serial'].grid(row=0, column=3, sticky='n')
        self.tab[name]['serial_value'] = ''
        self.tab[name]['serial']['value'] = (
            'serial01', 'serial02', 'serial03', 'serial04', 'serial05', 'serial06',
            'serial07', 'serial08', 'serial09', 'serial10', 'serial11', 'serial12',
            'serial13', 'serial14', 'serial15', 'serial16', 'serial17', 'serial18',
            'serial19', 'serial20', 'serial21', 'serial22', 'serial23', 'serial24')
        self.tab[name]['serial'].bind('<<ComboboxSelected>>', self.start_change_serial)
        self.active_tab = name
        self.row2.select(self.tab[name]['Frame'])
        # 存放tab信息
        result = self.work_for_data('tab', 'write')
        if result:
            print('存放tab信息与interface_data.txt完成')

    def del_log_text(self):
        """删除当前置顶的tab页"""
        if len(self.tab) == 1:
            print('只有一个tab页,不可销毁')
            return
        self.get_now_active_tab()
        self.row2.forget(self.row2.select())
        del self.tab[self.active_tab]
        # 存放tab信息
        result = self.work_for_data('tab', 'write')
        if result:
            print('存放tab信息与interface_data.txt完成')

    def tab_rename(self):
        """给tab页面重命名"""
        try:
            if self.top:
                self.top.destroy()
                self.top = ''
            self.top = tk.Toplevel()
            self.top.title('重命名')
            self.top.transient(self)
            self.top.resizable(0, 0)
            # 居中
            width = self.winfo_screenwidth()
            height = self.winfo_screenheight()
            ww = 220
            wh = 80
            x = (width-ww)/2
            y = (height-wh)/2
            self.top.geometry("%dx%d+%d+%d" % (ww, wh, x, y))  # 自适应居中
            self.top.grid()
            self.get_now_active_tab()
            # 重命名输入框
            name = tk.StringVar()
            name.set(self.tab[self.active_tab]['name_str'])
            self.rename_entry = tk.Entry(self.top, text=name, width=27)
            self.rename_entry.grid(row=0, column=0, columnspan=2, ipady=5, pady=(10, 5))
            # 确定按钮
            self.rename_sure_btn = ttk.Button(self.top, text='确定', command=lambda : self.control_rename_btn('确定'))
            self.rename_sure_btn.grid(row=1, column=0, padx=10, pady=5)
            # 取消按钮
            self.rename_cancel_btn = ttk.Button(self.top, text='取消', command=lambda : self.control_rename_btn('取消'))
            self.rename_cancel_btn.grid(row=1, column=1, padx=10, pady=5)
        except Exception as e:
            print('初始化重命名界面异常:%s' % e)

    def control_rename_btn(self, operate):
        """控制重命名的确定取消按钮"""
        if operate == '确定':
            new_name = self.rename_entry.get()
            if new_name:
                for i in self.tab:
                    if self.tab[i]['name_str'] == new_name:
                        print('有重名')
                        self.top.destroy()
                        return
                self.tab[self.active_tab]['name_str'] = new_name
                self.row2.add(self.tab[self.active_tab]['Frame'], text=new_name)
                result = self.work_for_data('tab', 'write')
                if result:
                    print('存放tab信息与interface_data.txt完成')
            self.top.destroy()
        if operate == '取消':
            self.top.destroy()

    def socket_conn(self):
        """与串口服务器建立TCP连接,无限循环获取数据"""
        ip_port = self.server_url.get().split(':')
        ip = ip_port[0]
        port = int(ip_port[1])
        global sock_conn
        sock_conn = SocketServer(ip, port)
        self.is_running = True
        while True:
            if self.is_running:
                try:
                    recv_info = sock_conn.get_serial_data()
                except Exception as e:
                    print('获取数据异常:%s' % e)
                    continue
                try:
                    parse_rule = re.compile(r'\*<\[(\d{2})]>\*')
                    parse_result = parse_rule.search(recv_info)
                    if parse_result:
                        log_info = recv_info.replace(parse_result.group(0), '')
                        serial = 'serial' + parse_result.group(1)
                    else:
                        print('串口标识识别失败:%s' % recv_info)
                        continue
                except Exception as e:
                    print('解析数据异常:%s,异常数据:%s' % (e, recv_info))
                    continue
                try:
                    self.save_log(log_info, serial)
                except Exception as e:
                    print('保存日志异常:%s' % e)
                    continue
                try:
                    self.output_log(log_info, serial)
                except Exception as e:
                    print('输出日志异常:%s' % e)
                    continue
            else:
                print('stopped')
                break

    def selectPath(self):
        """把获取到的串口目录传入Entry"""
        dir_ = askdirectory()
        self.dir.set(str(dir_))
        self.save_log_dir = self.log_dir.get()

    def save_log(self, log_info, serial_port):
        """把收到的日志分别传入不同的log文本中"""
        if not self.save_log_dir:
            # print('无传入日志目录')
            return
        # print('获取到的日志目录为:%s' % self.save_log_dir)
        now_day = datetime.datetime.now().strftime('%Y-%m-%d')
        # print('当前日期为:%s' % now_day)
        log_folder = os.path.join(self.save_log_dir, now_day)
        # print('存放今天日志目录为:%s' % log_folder)
        if not os.path.exists(log_folder):
            # print('不存在%s文件夹,进行新建' % log_folder)
            os.mkdir(log_folder)
        log_file = os.path.join(log_folder, '%s_%s.log' % (serial_port, now_day))
        log_info = log_info.rstrip('\n')
        with open(log_file, 'a+', errors='ignore', newline='') as f:
            f.write(log_info)

    def change_serial(self):
        """更改输出日志的串口"""
        self.get_now_active_tab()
        print('当前tab为%s' % self.active_tab)
        if self.tab[self.active_tab]['serial_value'] != self.tab[self.active_tab]['serial'].get():
            self.tab[self.active_tab]['serial_value'] = self.tab[self.active_tab]['serial'].get()
            self.tab[self.active_tab]['text'].delete(0.0, 'end')
            print('更改%s的输出串口为:%s' % (self.active_tab, self.tab[self.active_tab]['serial_value']))
            # 存放tab信息
            result = self.work_for_data('tab', 'write')
            if result:
                print('存放tab信息与interface_data.txt完成')

    def get_now_active_tab(self):
        active_name = self.row2.tab(self.row2.select(), "text")
        for i in self.tab:
            if self.tab[i]['name_str'] == active_name:
                self.active_tab = i
                break

    def work_for_data(self, key, work=''):
        """存/取tab字典,取快捷输入数据"""
        try:
            if key == 'tab':
                if work == 'write':
                    self.save_tab = {}
                    for i in self.tab:
                        self.save_tab[i] = {}
                        self.save_tab[i]['name_str'] = self.tab[i]['name_str']
                        self.save_tab[i]['serial_value'] = self.tab[i]['serial_value']
                    with open('interface_data.txt', 'w') as f:
                        data = json.dumps(self.save_tab)
                        f.write(data)
                        return True
                if work == 'read':
                    with open('interface_data.txt', 'r') as f:
                        data = f.read()
                        data = json.loads(data)
                        print('读取到的tab:%s' % data)
                        return data
            if key == 'quick_send':
                data_list = []
                with open('quick_send_data.txt', 'r') as f:
                    for i in f:
                        if '*start-' in i:
                            info = {}
                            l = i.replace('*start-', '')
                            name = l[0:l.rfind('=')]
                            l = l[l.rfind('=') + 1:]
                            if '\enter' in l:
                                enter = True
                                l = l.replace('\enter', '')
                            else:
                                enter = False
                            if '\n' in l:
                                l = l.replace('\n', '')
                            l = l.split(';')
                            info['data'] = l
                            info['name'] = name
                            info['enter'] = enter
                            data_list.append(info)
                return data_list
        except Exception as e:
            print('对%s进行%s操作异常:%s' % (key, work, e))

    def output_log(self, log_info, serial_port):
        """实时输出日志"""
        for i in self.tab:
            if self.tab[i]['serial_value'] == serial_port:
                self.write_log_to_Text(i, log_info)
                break

    def stop_recv_data(self):
        self.is_running = False
        print('stopping')

    def send_data_to_server(self, data=''):
        """发送数据给服务器"""
        try:
            self.get_now_active_tab()
            if not self.tab[self.active_tab]['serial_value']:
                print('无指定串口')
                return
            if data:
                print('有指定数据:%s' % data)
            else:
                data = self.log_input.get(0.0, 'end')
                self.log_input.delete(0.0, 'end')
                if not data:
                    print('无数据')
                    return
            if data[-1] not in ['\n', '\r']:
                print('给数据加回车')
                data = data + '\n'
            print('给%s发送%s指令' % (self.tab[self.active_tab]['serial_value'], data))
            data = '*<[%s]>*' % self.tab[self.active_tab]['serial_value'][-2:] + data
            sock_conn.send_data(bytes(data, encoding='utf8'))
        except Exception as e:
            print('发送数据异常:%s' % e)

    def quick_print(self, data):
        if data:
            if len(data['data']) == 1:      # 只有一条数据
                if data['enter']:   # 自动发送
                    self.start_send_data(data=data['data'][0])
                else:               # 打印出来,不自动发送
                    self.log_input.insert('end', data['data'][0])
            else:               # 大于1条数据,需要发送多次
                for i in data['data']:
                    if i == 'p':
                        print('sleep(1)')
                        time.sleep(1)
                    else:
                        if data['enter']:
                            self.start_send_data(data=i)
                        else:
                            self.log_input.insert('end', i)

    def change_see_log(self):
        if self.see_log_value.get() in ['1', 1]:
            self.see_log = True
            print('切换成跟随日志')
        else:
            self.see_log = False
            print('切换成不跟随日志')

    def write_log_to_Text(self, interface, log_msg):
        """日志动态打印"""
        self.LOG_LINE_NUM = int(self.tab[interface]['text'].index('end').split('.')[0]) - 1
        if self.LOG_LINE_NUM > 3001:
            print('log超过3000行,%s' % self.LOG_LINE_NUM)
            del_target = float(self.LOG_LINE_NUM - 3000 - 1)
            self.tab[interface]['text'].delete(1.0, del_target)
        logmsg_in = "%s\n" % log_msg
        self.tab[interface]['text'].insert('end', logmsg_in)
        if self.active_tab == interface and self.see_log:
            self.tab[interface]['text'].see('end')

    def thread_it(self, func, *args):
        """将函数打包进线程"""
        # 创建
        t = threading.Thread(target=func, args=args)
        # 守护 !!!
        t.setDaemon(True)
        # 启动
        t.start()

    def start_socket_conn(self, *args):
        self.thread_it(self.socket_conn)

    def start_save_log(self, *args):
        self.thread_it(self.save_log)

    def start_change_serial(self, *args):
        self.thread_it(self.change_serial)

    def start_output_log(self, *args):
        self.thread_it(self.output_log)

    def start_send_data(self, ev=None, data=''):
        self.thread_it(self.send_data_to_server, data)
        return 'break'

    def start_stop_task(self, *args):
        self.thread_it(self.stop_recv_data)

    def start_quick_print(self, data=''):
        self.thread_it(self.quick_print, data)

    def start_write_log(self, log_msg):
        self.thread_it(self.write_log_to_Text, log_msg)

    def start_tab_rename(self, *args):
        self.thread_it(self.tab_rename)


root = tk.Tk()
app = Application(master=root)
root.withdraw()
root.update()
root.title("串口收发客户端")
root.resizable(False, False)

sw = root.winfo_screenwidth()   # 得到屏幕宽度
sh = root.winfo_screenheight()  # 得到屏幕高度
# 窗口宽高为100
ww = 1500
wh = 900
x = (sw-ww) / 2
y = (sh-wh) / 2
root.geometry("%dx%d+%d+%d" % (ww, wh, x, y))       # 自适应居中


root.deiconify()        # 显示窗口
app.mainloop()          # 进入消息循环

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端 的相关文章

随机推荐

  • zookeeper 搭建集群

    待完善
  • 《计算机文化基础》22-23第一学期后十周教学计划(中国铁道出版社第三版)

    课程 任课教师 授课班级 编制时间 计算机文化基础 2022 10 28 授课日期 2022年 10月31日至 2022年 12月 16日 本课程总课时 28课时 已授课时 0 课时 尚余课时 28课时 本学期授课周 7周 本学期周课时 4
  • 超详细讲解!Android面试题集2021版,面试心得体会

    前言 Android常用知识体系是什么鬼 所谓常用知识体系 就是指对项目中重复使用率较高的功能点进行梳理 注意哦 不是Android知识体系 古语道 学而不思则罔 思而不学则殆 如果将做项目类比为 学 那么整理就可以类比为 思 在做项目过程
  • 文件包含漏洞

    一 文件包含函数 将外部文件的内容引入当前环境 include
  • 玩转Kali之初始化系统

    文章目录 下载镜像 安装系统 修改root密码 配置APT国内源 更新软件包 下载镜像 1 打开kali官网 https www kali org 安装系统 1 打开VirtualBox 2 选择新建虚拟机 1 输入虚拟机名称 2 选择安装
  • TopK问题的三种解法

    TopK问题是指从n个数据中取前K个数据 在生活中的应用也有很多 如游戏中xxx的排行榜前10名等 在这篇博客中我将主要利用堆去解决TopK问题 堆排序 首先我们需要建一个堆 然后我们再进行堆排序 排好序后直接取前K个就可以了 需要注意的是
  • Debian10iptables放行语法

    文章目录 1 基本语法 2 修改默认规则 3 实例 4 易错总结 1 基本语法 iptables A 链 匹配条件 j 动作 D 删除 p 协议 ACCEPT 放行 s 源ip地址 DROP 丢弃 d 目的ip地址 REJECT 拒绝 sp
  • java.util.EnumSet complementOf (EnumSet<E> s)方法具有什么功能呢?

    转自 java util EnumSet complementOf EnumSet lt E gt s 方法具有什么功能呢 下文笔者讲述EnumSet complementOf方法的功能简介说明 如下所示 EnumSet complemen
  • To Java程序员:切勿用普通for循环遍历LinkedList

    ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说 可能平时使用得最多的List就是ArrayList 对于ArrayList的遍历 一般用如下写法 public static void mai
  • 余弦定理实现新闻自动分类算法

    前言 余弦定理 这个在初中课本中就出现过的公式 恐怕没有人不知道的吧 但是另外一个概念 可能不是很多的人会听说过 他叫空间向量 一般用e表示 高中课本中有专门讲过这个东西 有了余弦定理和向量空间 我们就可以做许多有意思的事情了 利用余弦定理
  • Spring框架(三)Spring注解和获取Bean对象详解

    目录 一 什么是基于Java的Spring注解配置 具体注解的例子 二 更好的将Bean存储在Spring中 1 前置工作 在配置文件中设置Bean根路径 2 添加注解存储Bean对象 2 1 Controller 控制器存储 2 2 Se
  • Vue中 实现上一篇下一篇的功能

    效果 看下html页面 div class NewsDetails cont footer div img src assets img newsDetail 公共 更多2 1 png alt span 上一篇 lastTitle span
  • 软件开发 文档 质量

    1 在撰写API文档时 如果某个API的性能 时间性能 内存性能 特别低 应该在文档里详细列出 如此做的好处是 1 1 有助于客户在设计阶段 采用正确 高效的方案 1 2 对于开发这个API的team 可以减轻维护压力 明确责任 因为在文档
  • 中文同义句在线转换器 - 中文同义句转换器软件

    在线同义句转换器 中文同义句在线转换器 中文同义句转换器软件 made in Japan 祝你学习进步 更上一层楼 请记得采纳 谢谢 同义句转换器 1 I d like to go to the beach on vacation beca
  • 【1803. 统计异或值在范围内的数对有多少】

    来源 力扣 LeetCode 描述 给你一个整数数组 nums 下标 从 0 开始 计数 以及两个整数 low 和 high 请返回 漂亮数对 的数目 漂亮数对 是一个形如 i j 的数对 其中 0 lt i lt j lt nums le
  • 2019年黑马新版Java学习路线图(内含大纲+视频+工具+书籍+面试)面试必看!

    非常好的java学习路线 伴有配套资源 面试必看 黑马程序员 http bbs itheima com thread 386464 1 1 html
  • LEVELDB介绍

    基本信息 特性 keys 和 values 是任意的字节数组 数据按 key 值排序存储 调用者可以重载函数来重写排序顺序 提供基本的 Put key value Get key Delete key Batch 操作 多个更改可以在一个原
  • JWT

    1 常见的认证机制 1 1 HTTP Basic Auth HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password 简言之 Basic Auth是配合RESTful API 使用的最简单的
  • SpringBoot整合Shiro

    一 pom xml引入依赖 1 shiro依赖
  • python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端

    python3 tkinter实践历程 四 基于socket通信与tkinter的TCP串口客户端 仿CRT 文章目录 系列文章目录 分享背景 制作背景 最终功能 工具截图展示 代码详解 系列文章目录 python3 tkinter实践历程