• [技术干货] Java+saas模式 智慧校园系统源码Java Android +MySQL+ IDEA 多校运营数字化校园云平台源码
    Java+saas模式 智慧校园系统源码Java Android +MySQL+ IDEA 多校运营数字化校园云平台源码智慧校园即智慧化的校园,也指按智慧化标准进行的校园建设,按标准《智慧校园总体框架》中对智慧校园的标准定义是:物理空间和信息空间的有机衔接,使任何人、任何时间、任何地点都能便捷的获取资源和服务。 智慧校园通常由以传感器网络及智能硬件为核心的校园基础设施和部署在数据中心内云端服务器上的智慧化软件系统构成 ,常见功能可分为智慧教学环境、智慧教学资源、智慧校园管理、智慧校园服务四大板块智慧校园基础数据管理:1、学校信息:支持管理员对学校对基本学校信息进行编辑并浏览,通过编辑提交后全校可查看2、学科设置:支持管理添加并编辑以及删除学科,添加学科时系统自动创建学科任课老师角色,删除学科如学科有数据系统可提醒用户是否删除;添加、删除、编辑等功能可通过业务权限进行自定义控制3、组织架构:支持管理员单个创建以及批量创建部门,部门层级最少不低于四级架构;组织架构支持可视展示4、教师数据:支持管理员教师单个数据添加;支持教师批量数据添加,批量添加支持从excel表格批量复制粘贴至系统表格内,并支持系统自动检索判断部门角色是否正确对应;管理员支持初始化全校教师登陆密码,可以单个重置登陆密码;支持单个删除教师数据以及单个修改教师数据;支持批量导出教师数据至Excel;支持不同的角色(如班主任、管理员)查看的教师数据信息只能查看到自己数据权限范围内的数据5、学生数据:支持管理员学生单个数据添加;支持学生批量数据添加,批量添加支持从excel表格批量复制粘贴至系统表格内,并支持系统自动检索判断班级角色是否正确对应;管理员支持初始化全校学生(家长)登陆密码,可以单个重置登陆密码;支持单个删除学生数据以及单个修改学生数据;支持批量导出学生数据至Excel;支持不同的角色(如班主任、管理员)查看的学生数据信息只能查看到自己数据权限范围内的数据6、教室管理:支持管理员针对教室和班级进行绑定管理,通过教室id绑定班级,教室绑定班牌设备权限管理:1、数据权限:系统支持所有业务模块通过角色判断是否有数据查看范围的权限,权限模块支持数据权限并可自定义编辑权限范围2、功能权限:支持管理员针对整个平台所有的一级、二级菜单以及增删改查按钮进行权限配置勾选;权限模块支持功能权限到增删改查的级别3、角色管理:支持管理员自定义添加角色、修改角色删除角色,并通过角色组进行权限的设定4、应用权限:支持管理员可以通过平台对学校的应用功能进行勾选式的权限控制设备管理:1、 管理员查看全校到班牌设备信息:含有(班级信息、软件版本、设备型号、开关机信息、班牌截屏信息、教室编号、设备ID、设备描述、在线状态、离线状态、班牌最新更新时间)2、 系统支持查看设备信息筛选,可通过班级、在线状态、设备ID进行查询3、 系统支持管理员在小程序或者管理平台设备绑定并通过教室ID绑定设备4、 系统支持管理员在管理平台对绑定对设备进行修改5、 系统支持管理员在小程序或者管理平台进行调节班牌音量大小6、 系统支持管理员重置班牌密码7、 系统支持管理员修改设备类型8、 系统支持管理员远程升级设备软件9、 系统支持管理员设置日常开关机时间并一键清除日常开关机内容,保存后日常开关机可以准确对下发至班牌终端10、 系统支持管理员设置节假日开关机,并可以同时设置多个节假日;支持自定义节假日设置,保存后节假日开关机设置准确的下发至班牌11、支持手机移动端以及web端对班牌设备的管控,支持一键重启、一键关机、节假日自动开关机、日常自动开关机、远程升级、远程截屏、远程控制设备音量、远程查看在离线状态、扫一扫快捷绑定设备12、班牌终端远程升级:管理员可以通过管理后台对所有终端班牌进行单台或者多台批量远程升级,上传安装包后班牌进行下载安装包自动升级
  • [其他] HashMap实现原理, 扩容机制
    1.讲下对HashMap的认识 HashMap 存储的是键值对 key - value,key 具有唯一性,采用了链地址法来处理哈希冲突。当往 HashMap 中添加元素时,会计算 key 的 hash 值取余得出元素在数组中的的存放位置。  HashMap底层的数据结构在 JDK1.8 中有了较大的变化,1.8之前采用数组加链表的数据结构,1.8采用数组加链表加红黑树的数据结构。 HashMap 是线程不安全的,线程安全可以使用 HashTable 和 ConcurrentHashMap 。 在 1.8 版本的中 hash() 和 resize( ) 方法也有了很大的改变,提升了性能。 键和值都可存放null,键只能存放一个null,键为null时存放入table[0]。 2.HashMap的一些参数     //HashMap的默认初始长度16     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;           //HashMap的最大长度2的30次幂     static final int MAXIMUM_CAPACITY = 1 << 30;          //HashMap的默认加载因子0.75     static final float DEFAULT_LOAD_FACTOR = 0.75f;          //HashMap链表升级成红黑树的临界值     static final int TREEIFY_THRESHOLD = 8;          //HashMap红黑树退化成链表的临界值     static final int UNTREEIFY_THRESHOLD = 6;          //HashMap链表升级成红黑树第二个条件:HashMap数组(桶)的长度大于等于64     static final int MIN_TREEIFY_CAPACITY = 64;          //HashMap底层Node桶的数组     transient Node<K,V>[] table;          //扩容阈值,当你的hashmap中的元素个数超过这个阈值,便会发生扩容     //threshold = capacity * loadFactor     int threshold;   3.为什么HashMap的长度必须是2的n次幂? 在计算存入结点下标时,会利用 key 的 hsah 值进行取余操作,而计算机计算时,并没有取余等运算,会将取余转化为其他运算。  当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n,就可以用位运算代替取余运算,计算更加高效。  4.HashMap 为什么在获取 hash 值时要进行位运算 换种问法:能不能直接使用key的hashcode值计算下标存储?  static final int hash(Object key) {     int h;     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 如果使用直接使用hashCode对数组大小取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让 hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动。 (h >>> 16)是无符号右移16位的运算,右边补0,得到 hashCode 的高16位。 (h = key.hashCode()) ^ (h >>> 16) 把 hashCode 和它的高16位进行异或运算,可以使得到的 hash 值更加散列,尽可能减少哈希冲突,提升性能。 而这么来看 hashCode 被散列 (异或) 的是低16位,而 HashMap 数组长度一般不会超过2的16次幂,那么高16位在大多数情况是用不到的,所以只需要拿 key 的 HashCode 和它的低16位做异或即可利用高位的hash值,降低哈希碰撞概率也使数据分布更加均匀。 5.HashMap在JDK1.7和JDK1.8中有哪些不同? HashMap的底层实现 在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。  JDK1.8主要解决或优化了以下问题:  resize 扩容和 计算hash 优化 引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。 6.HashMap的put方法的具体流程? 源码   HashMap是懒加载,只有在第一次put时才会创建数组。 总结 ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容; ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③; ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals; ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值 对,否则转向⑤; ⑤.遍历table[i],并记录遍历长度,如果遍历过程中发现key值相同的,则直接覆盖value,没有相同的key则在链表尾部插入结点,插入后判断该链表长度是否大等于8,大等于则考虑树化,如果数组的元素个数小于64,则只是将数组resize,大等于才树化该链表; ⑥.插入成功后,判断数组中的键值对数量size是否超过了阈值threshold,如果超过,进行扩容。  7.HashMap 的 get 方法的具体流程?   public V get(Object key) {     Node<K,V> e;     return (e = getNode(hash(key), key)) == null ? null : e.value; }  final Node<K,V> getNode(int hash, Object key) {     Node<K,V>[] tab; Node<K,V> first, e; int n; K k;          //Node数组不为空,数组长度大于0,数组对应下标的Node不为空     if ((tab = table) != null && (n = tab.length) > 0 &&         //也是通过 hash & (length - 1) 来替代 hash % length 的         (first = tab[(n - 1) & hash]) != null) {                  //先和第一个结点比,hash值相等且key不为空,key的第一个结点的key的对象地址和值均相等         //则返回第一个结点         if (first.hash == hash && // always check first node             ((k = first.key) == key || (key != null && key.equals(k))))             return first;         //如果key和第一个结点不匹配,则看.next是否为空,不为null则继续,为空则返回null         if ((e = first.next) != null) {             //如果此时是红黑树的结构,则进行处理getTreeNode()方法搜索key             if (first instanceof TreeNode)                 return ((TreeNode<K,V>)first).getTreeNode(hash, key);             //是链表结构的话就一个一个遍历,直到找到key对应的结点,             //或者e的下一个结点为null退出循环             do {                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k))))                     return e;             } while ((e = e.next) != null);         }     }     return null; } 总结  首先根据 hash 方法获取到 key 的 hash 值 然后通过 hash & (length - 1) 的方式获取到 key 所对应的Node数组下标 ( length对应数组长度 ) 首先判断此结点是否为空,是否就是要找的值,是则返回空,否则判断第二个结点是否为空,是则返回空,不是则判断此时数据结构是链表还是红黑树 链表结构进行顺序遍历查找操作,每次用 == 符号 和 equals( ) 方法来判断 key 是否相同,满足条件则直接返回该结点。链表遍历完都没有找到则返回空。 红黑树结构执行相应的 getTreeNode( ) 查找操作。 8.HashMap的扩容操作是怎么实现的? 不管是JDK1.7或者JDK1.8 当put方法执行的时候,如果table为空,则执行resize()方法扩容。默认长度为16。  JDK1.7扩容 条件:发生扩容的条件必须同时满足两点  当前存储的数量大于等于阈值 发生hash碰撞 因为上面这两个条件,所以存在下面这些情况  就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。 当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,所以不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象。 特点:先扩容,再添加(扩容使用的头插法)  缺点:头插法会使链表发生反转,多线程环境下可能会死循环  扩容之后对table的调整:  table容量变为2倍,所有的元素下标需要重新计算,newIndex = hash (扰动后) & (newLength - 1)  JDK1.8扩容 条件:  当前存储的数量大于等于阈值 当某个链表长度>=8,但是数组存储的结点数size() < 64时 特点:先插后判断是否需要扩容(扩容时是尾插法)  缺点:多线程下,1.8会有数据覆盖  举例: 线程A:往index插,index此时为空,可以插入,但是此时线程A被挂起 线程B:此时,对index写入数据,A恢复后,就把B数据覆盖了  扩容之后对table的调整:  table容量变为2倍,但是不需要像之前一样计算下标,只需要将hash值和旧数组长度相与即可确定位置。  如果 Node 桶的数据结构是链表会生成 low 和 high 两条链表,是红黑树则生成 low 和 high 两颗红黑树 依靠 (hash & oldCap) == 0 判断 Node 中的每个结点归属于 low 还是 high。 把 low 插入到 新数组中 当前数组下标的位置,把 high 链表插入到 新数组中 [当前数组下标 + 旧数组长度] 的位置 如果生成的 low,high 树中元素个数小于等于6退化成链表再插入到新数组的相应下标的位置 9.HashMap 在扩容时为什么通过位运算 (e.hash & oldCap) 得到下标? 从下图中我们可以看出,计算下标通过(n - 1) & hash,旧table的长度为16,hash值只与低四位有关,扩容后,table长度为32(两倍),此时只与低五位有关。  所以此时后几位的结果相同,前后两者之间的差别就差在了第五位上。  同时,扩容的时候会有 low 和 high 两条链表或红黑树来记录原来下标的数据和原来下标 + 旧table下标的数据。  如果第五位 b 是 0,那么只要看低四位 (也就是原来的下标);如果第五位是 1,只要把低四位的二进制数 + 1 0 0 0 0 ,就可以得到新数组下标。前面的部分刚好是原来的下标,后一部分就是旧table的长度 。那么我们就得出来了为什么把 low 插入扩容后 新数组[原来坐标] 的位置,把 high 插入扩容后 新数组[当前坐标 + 旧数组长度] 的位置。  那为什么根据 (e.hash & oldCap) == 0 来做判断条件呢?是因为旧数组的长度 length 的二进制数的第五位刚好是 1,hash & length 就可以计算 hash 值的第五位是 0 还是 1,就可以区别是在哪个位置上。  10.链表升级成红黑树的条件 链表长度大于8时才会考虑升级成红黑树,是有一个条件是 HashMap 的 Node 数组长度大于等于64(不满足则会进行一次扩容替代升级)。  11.红黑树退化成链表的条件 扩容 resize( ) 时,红黑树拆分成的 树的结点数小于等于临界值6个,则退化成链表。 删除元素 remove( ) 时,在 removeTreeNode( ) 方法会检查红黑树是否满足退化条件,与结点数无关。如果红黑树根 root 为空,或者 root 的左子树/右子树为空,root.left.left 根的左子树的左子树为空,都会发生红黑树退化成链表。 12.HashMap是怎么解决哈希冲突的? 使用链地址法(使用散列表)来链接拥有相同下标的数据; 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均; 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快; 13.HaspMap的初始化时数组长度和加载因子的约束范围 可以看到如果初始化数组长度 initialCapacity 小于 0 的话会跑出 IllegalArgumentException 的异常,initialCapacity 大于 MAXIMUM_CAPACITY 即 2 的 30 次幂的时候最大长度也只会固定在 MAXIMUM_CAPACITY ,在扩容的时候,如果数组的长度大等于MAXIMUM_CAPACITY,会将阈值设置为Integer.MAX_VALUE。  加载因子小于等于0时,或者加载因子是NaN时 (NaN 实际上就是 Not a Number的简称) 会抛出 IllegalArgumentException 的异常。 ————————————————                         原文链接:https://blog.csdn.net/qq_49217297/article/details/126304736 
  • [其他] Kafka消息延迟和时序性详解
     一、概括 1.1 介绍 Kafka 消息延迟和时序性 Kafka 消息延迟和时序性对于大多数实时数据流应用程序至关重要。本章将深入介绍这两个核心概念,它们是了解 Kafka 数据流处理的关键要素。  1.1.1 什么是 Kafka 消息延迟? Kafka 消息延迟是指消息从生产者发送到消息被消费者接收之间的时间差。这是一个关键的概念,因为它直接影响到数据流应用程序的实时性和性能。在理想情况下,消息应该以最小的延迟被传递,但在实际情况中,延迟可能会受到多种因素的影响。   消息延迟的因素包括:  网络延迟:消息必须通过网络传输到 Kafka 集群,然后再传输到消费者。网络延迟可能会受到网络拓扑、带宽和路由等因素的影响。  硬件性能:Kafka 集群的硬件性能,包括磁盘、内存和 CPU 的速度,会影响消息的写入和读取速度。  Kafka 内部处理:Kafka 集群的内部处理能力也是一个关键因素。消息必须经过分区、日志段和复制等处理步骤,这可能会引入一些处理延迟。  1.1.2 为什么消息延迟很重要?  消息延迟之所以如此重要,是因为它直接关系到实时数据处理应用程序的可靠性和实时性。在一些应用中,如金融交易处理,甚至毫秒级的延迟都可能导致交易失败或不一致。在监控和日志处理应用中,过高的延迟可能导致数据不准确或失去了时序性。  管理和优化 Kafka 消息延迟是确保应用程序在高负载下仍能快速响应的关键因素。不仅需要了解延迟的来源,还需要采取相应的优化策略。  1.1.3 什么是 Kafka 消息时序性? Kafka 消息时序性是指消息按照它们发送的顺序被接收。这意味着如果消息 A 在消息 B 之前发送,那么消息 A 应该在消息 B 之前被消费。保持消息的时序性对于需要按照时间顺序处理的应用程序至关重要。  维护消息时序性是 Kafka 的一个强大特性。在 Kafka 中,每个分区都可以保证消息的时序性,因为每个分区内的消息是有序的。然而,在多个分区的情况下,时序性可能会受到消费者处理速度不一致的影响,因此需要采取一些策略来维护全局的消息时序性。  1.1.4 消息延迟和时序性的关系 消息延迟和消息时序性之间存在密切的关系。如果消息延迟过大,可能会导致消息失去时序性,因为一条晚到的消息可能会在一条早到的消息之前被处理。因此,了解如何管理消息延迟也包括了维护消息时序性。  在接下来的章节中,我们将深入探讨如何管理和优化 Kafka 消息延迟,以及如何维护消息时序性,以满足实时数据处理应用程序的需求。  1.2 延迟的来源 为了有效地管理和优化 Kafka 消息延迟,我们需要深入了解延迟可能来自哪些方面。下面是一些常见的延迟来源:  1.2.1 Kafka 内部延迟 Kafka 内部延迟是指与 Kafka 内部组件和分区分配相关的延迟。这些因素可能会影响消息在 Kafka 内部的分发、复制和再平衡。  分区分布不均:如果分区分布不均匀,某些分区可能会变得拥挤,而其他分区可能会滞后,导致消息传递延迟。  复制延迟:在 Kafka 中,消息通常会进行复制以确保冗余。复制延迟是指主题的所有副本都能复制消息所需的时间。  再平衡延迟:当 Kafka 集群发生再平衡时,消息的重新分配和复制可能导致消息传递延迟。  二、衡量和监控消息延迟 在本节中,我们将深入探讨如何度量和监控 Kafka 消息延迟,这将帮助你更好地了解问题并采取相应的措施来提高延迟性能。  2.1 延迟的度量 为了有效地管理 Kafka 消息延迟,首先需要能够度量它。下面是一些常见的延迟度量方式:  2.1.1 生产者到 Kafka 延迟 这是指消息从生产者发送到 Kafka 集群之间的延迟。为了度量这一延迟,你可以采取以下方法:  记录发送时间戳:在生产者端,记录每条消息的发送时间戳。一旦消息成功写入 Kafka,记录接收时间戳。然后,通过将这两个时间戳相减,你可以获得消息的生产者到 Kafka 的延迟。  以下是如何记录发送和接收时间戳的代码示例:  // 记录消息发送时间戳 long sendTimestamp = System.currentTimeMillis(); ProducerRecord<String, String> record = new ProducerRecord<>("my_topic", "key", "value"); producer.send(record, (metadata, exception) -> {     if (exception == null) {         long receiveTimestamp = System.currentTimeMillis();         long producerToKafkaLatency = receiveTimestamp - sendTimestamp;         System.out.println("生产者到 Kafka 延迟:" + producerToKafkaLatency + " 毫秒");     } else {         System.err.println("消息发送失败: " + exception.getMessage());     } }); 1 2 3 4 5 6 7 8 9 10 11 12 2.1.2 Kafka 内部延迟 Kafka 内部延迟是指消息在 Kafka 集群内部传递的延迟。你可以使用 Kafka 内置度量来度量它,包括:  Log End-to-End Latency:这是度量消息从生产者发送到消费者接收的总延迟。它包括了网络传输、分区复制、再平衡等各个环节的时间。  以下是一个示例:  // 创建 Kafka 消费者 Properties consumerProps = new Properties(); consumerProps.put("bootstrap.servers", "kafka-broker:9092"); consumerProps.put("group.id", "my-group"); consumerProps.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); consumerProps.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);  // 订阅主题 consumer.subscribe(Collections.singletonList("my_topic"));  while (true) {     ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));     for (ConsumerRecord<String, String> record : records) {         long endToEndLatency = record.timestamp() - record.timestampType().createTimestamp();         System.out.println("Log End-to-End 延迟:" + endToEndLatency + " 毫秒");     } }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 2.1.3 消费者处理延迟 消费者处理延迟是指消息从 Kafka 接收到被消费者实际处理的时间。为了度量这一延迟,你可以采取以下方法:  记录消费时间戳:在消费者端,记录每条消息的接收时间戳和处理时间戳。通过计算这两个时间戳的差值,你可以得到消息的消费者处理延迟。 以下是如何记录消费时间戳的代码示例:  KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps); consumer.subscribe(Collections.singletonList("my_topic"));  while (true) {     ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));     for (ConsumerRecord<String, String> record : records) {         long receiveTimestamp = System.currentTimeMillis();         long consumerProcessingLatency = receiveTimestamp - record.timestamp();         System.out.println("消费者处理延迟:" + consumerProcessingLatency + " 毫秒");     } } 1 2 3 4 5 6 7 8 9 10 11 2.2 监控和度量工具 在度量和监控 Kafka 消息延迟时,使用适当的工具和系统是至关重要的。下面是一些工具和步骤,帮助你有效地监控 Kafka 消息延迟,包括代码示例:  2.2.1 Kafka 内置度量 Kafka 提供了内置度量,可通过多种方式来监控。以下是一些示例,演示如何通过 Kafka 的 JMX 界面访问这些度量:  使用 JConsole 直接连接到 Kafka Broker:  启动 Kafka Broker。 打开 JConsole(Java 监控与管理控制台)。 在 JConsole 中选择 Kafka Broker 进程。 导航到 “kafka.server” 和 “kafka.consumer”,以查看各种度量。 使用 Jolokia(Kafka JMX HTTP Bridge):  启用 Jolokia 作为 Kafka Broker 的 JMX HTTP Bridge。 使用 Web 浏览器或 HTTP 请求访问 Jolokia 接口来获取度量数据。例如,使用 cURL 进行 HTTP GET 请求: curl http://localhost:8778/jolokia/read/kafka.server:name=BrokerTopicMetrics/TotalFetchRequestsPerSec 1 这将返回有关 Kafka Broker 主题度量的信息。  2.2.2 第三方监控工具 除了 Kafka 内置度量,你还可以使用第三方监控工具,如 Prometheus 和 Grafana,来收集、可视化和警报度量数据。以下是一些步骤:  配置 Prometheus:  部署和配置 Prometheus 服务器。 创建用于监控 Kafka 的 Prometheus 配置文件,定义抓取度量数据的频率和目标。 启动 Prometheus 服务器。 设置 Grafana 仪表板:  部署和配置 Grafana 服务器。 在 Grafana 中创建仪表板,使用 Prometheus 作为数据源。 添加度量查询,配置警报规则和可视化图表。 可视化 Kafka 延迟数据:  在 Grafana 仪表板中,你可以设置不同的图表来可视化 Kafka 延迟数据,例如生产者到 Kafka 延迟、消费者处理延迟等。通过设置警报规则,你还可以及时收到通知,以便采取行动。  2.2.3 配置和使用监控工具 为了配置和使用监控工具,你需要执行以下步骤:  定义度量指标:确定你要度量的关键度量指标,如生产者到 Kafka 延迟、消费者处理延迟等。  设置警报规则:为了快速响应问题,设置警报规则,以便在度量数据超出预定阈值时接收通知。  创建可视化仪表板:使用监控工具(如 Grafana)创建可视化仪表板,以集中展示度量数据并实时监测延迟情况。可配置的图表和仪表板有助于更好地理解数据趋势。  以上步骤和工具将帮助你更好地度量和监控 Kafka 消息延迟,以及及时采取行动来维护系统的性能和可靠性。  三、降低消息延迟 既然我们了解了 Kafka 消息延迟的来源以及如何度量和监控它,让我们继续探讨如何降低消息延迟。以下是一些有效的实践方法,可以帮助你减少 Kafka 消息延迟:  3.1 优化 Kafka 配置 3.1.1 Producer 和 Consumer 参数 生产者参数示例: # 生产者参数示例 acks=all compression.type=snappy linger.ms=20 max.in.flight.requests.per.connection=1 1 2 3 4 5 acks 设置为 all,以确保生产者等待来自所有分区副本的确认。这提高了可靠性,但可能增加了延迟。 compression.type 使用 Snappy 压缩消息,减小了网络传输延迟。 linger.ms 设置为 20 毫秒,以允许生产者在发送消息之前等待更多消息。这有助于减少短暂的消息发送延迟。 max.in.flight.requests.per.connection 设置为 1,以确保在收到分区副本的确认之前不会发送新的消息。 消费者参数示例: # 消费者参数示例 max.poll.records=500 fetch.min.bytes=1 fetch.max.wait.ms=100 enable.auto.commit=false 1 2 3 4 5 max.poll.records 设置为 500,以一次性拉取多条消息,提高吞吐量。 fetch.min.bytes 设置为 1,以确保即使没有足够数据,也立即拉取消息。 fetch.max.wait.ms 设置为 100 毫秒,以限制拉取消息的等待时间。 enable.auto.commit 禁用自动提交位移,以确保精确控制消息的确认。 3.1.2 Broker 参数 优化 Kafka broker 参数可以提高整体性能。以下是示例:  # Kafka Broker 参数示例 num.network.threads=3 num.io.threads=8 log.segment.bytes=1073741824 log.retention.check.interval.ms=300000 1 2 3 4 5 num.network.threads 和 num.io.threads 设置为适当的值,以充分利用硬件资源。 log.segment.bytes 设置为 1 GB,以充分利用磁盘性能。 log.retention.check.interval.ms 设置为 300,000 毫秒,以降低清理日志段的频率。 3.1.3 Topic 参数 优化每个主题的参数以满足应用程序需求也很重要。以下是示例:  # 创建 Kafka 主题并设置参数示例 kafka-topics.sh --create --topic my_topic --partitions 8 --replication-factor 2 --config cleanup.policy=compact 1 2 --partitions 8 设置分区数量为 8,以提高并行性。 --replication-factor 2 设置复制因子为 2,以提高可靠性。 --config cleanup.policy=compact 设置清理策略为压缩策略,以减小数据保留成本。 通过适当配置这些参数,你可以有效地优化 Kafka 配置以降低消息延迟并提高性能。请根据你的应用程序需求和硬件资源进行调整。  3.2 编写高效的生产者和消费者 最后,编写高效的 Kafka 生产者和消费者代码对于降低延迟至关重要。以下是一些最佳实践:  3.2.1 生产者最佳实践 使用异步发送:将多个消息批量发送,而不是逐条发送。这可以减少网络通信的次数,提高吞吐量。  使用 Kafka 生产者的缓冲机制:充分利用 Kafka 生产者的缓冲功能,以减少网络通信次数。  使用分区键:通过选择合适的分区键,确保数据均匀分布在不同的分区上,从而提高并行性。  3.2.2 消费者最佳实践 使用多线程消费:启用多个消费者线程,以便并行处理消息。这可以提高处理能力和降低延迟。  调整消费者参数:调整消费者参数,如 fetch.min.bytes 和 fetch.max.wait.ms,以平衡吞吐量和延迟。  使用消息批处理:将一批消息一起处理,以减小处理开销。  3.2.3 数据序列化 选择高效的数据序列化格式对于降低数据传输和存储开销很重要。以下是一些建议的格式:  Avro:Apache Avro 是一种数据序列化框架,具有高度压缩和高性能的特点。它适用于大规模数据处理。  Protocol Buffers:Google Protocol Buffers(ProtoBuf)是一种轻量级的二进制数据格式,具有出色的性能和紧凑的数据表示。  四、Kafka 消息时序性 消息时序性是大多数实时数据流应用程序的核心要求。在本节中,我们将深入探讨消息时序性的概念、为何它如此重要以及如何保障消息时序性。  4.1 什么是消息时序性? 消息时序性是指消息按照它们发送的顺序被接收和处理的特性。在 Kafka 中,每个分区内的消息是有序的,这意味着消息以它们被生产者发送的顺序排列。然而,跨越多个分区的消息需要额外的工作来保持它们的时序性。  4.1.1 为何消息时序性重要? 消息时序性对于许多应用程序至关重要,特别是需要按照时间顺序处理数据的应用。以下是一些应用领域,消息时序性非常关键:  金融领域:在金融交易中,确保交易按照它们发生的确切顺序进行处理至关重要。任何失去时序性的交易可能会导致不一致性或错误的交易。  日志记录:在日志记录和监控应用程序中,事件的时序性对于分析和排查问题非常关键。失去事件的时序性可能会导致混淆和数据不准确。  电商应用:在线商店的订单处理需要确保订单的创建、支付和发货等步骤按照正确的顺序进行,以避免订单混乱和不准确。  4.2 保障消息时序性 在分布式系统中,保障消息时序性可能会面临一些挑战,特别是在跨越多个分区的情况下。以下是一些策略和最佳实践,可帮助你确保消息时序性:  4.2.1 分区和消息排序 使用合适的分区策略对消息进行排序,以确保相关的消息被发送到同一个分区。这样可以维护消息在单个分区内的顺序性。对于需要按照特定键排序的消息,可以使用自定义分区器来实现。  以下是如何使用合适的分区策略对消息进行排序的代码示例:  // 自定义分区器,确保相关消息基于特定键被发送到同一个分区 public class CustomPartitioner implements Partitioner {     @Override     public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {         // 在此处根据 key 的某种规则计算分区编号         // 例如,可以使用哈希函数或其他方法         int numPartitions = cluster.partitionsForTopic(topic).size();         return Math.abs(key.hashCode()) % numPartitions;     }      @Override     public void close() {         // 可选的资源清理     }      @Override     public void configure(Map<String, ?> configs) {         // 可选的配置     } }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 4.2.2 数据一致性 确保生产者发送的消息是有序的。这可能需要在应用程序层面实施,包括对消息进行缓冲、排序和合并,以确保它们按照正确的顺序发送到 Kafka。  以下是如何确保数据一致性的代码示例:  // 生产者端的消息排序 ProducerRecord<String, String> record1 = new ProducerRecord<>("my-topic", "key1", "message1"); ProducerRecord<String, String> record2 = new ProducerRecord<>("my-topic", "key2", "message2");  // 发送消息 producer.send(record1); producer.send(record2);  // 消费者端保证消息按照键排序 ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord<String, String> record : records) {     // 处理消息,确保按照键的顺序进行 } 1 2 3 4 5 6 7 8 9 10 11 12 13 4.2.3 消费者并行性 在消费者端,使用适当的线程和分区分配来确保消息以正确的顺序处理。这可能涉及消费者线程数量的管理以及确保每个线程只处理一个分区,以避免顺序混乱。  以下是如何确保消费者并行性的代码示例:  // 创建具有多个消费者线程的 Kafka 消费者 Properties consumerProps = new Properties(); consumerProps.put("bootstrap.servers", "kafka-broker:9092"); consumerProps.put("group.id", "my-group"); consumerProps.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); consumerProps.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");  // 创建 Kafka 消费者 KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);  // 订阅主题 consumer.subscribe(Collections.singletonList("my-topic"));  // 创建多个消费者线程 int numThreads = 3; for (int i = 0; i < numThreads; i++) {     Runnable consumerThread = new ConsumerThread(consumer);     new Thread(consumerThread).start(); }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 五、总结 在本篇技术博客中,我们深入探讨了 Kafka 消息延迟和时序性的重要性以及如何度量、监控和降低消息延迟。我们还讨论了消息时序性的挑战和如何确保消息时序性。对于构建实时数据流应用程序的开发人员来说,深入理解这些概念是至关重要的。通过合理配置 Kafka、优化网络和硬件、编写高效的生产者和消费者代码,以及维护消息时序性,你可以构建出高性能和可靠的数据流系统。  无论你的应用是金融交易、监控、日志记录还是其他领域,这些建议和最佳实践都将帮助你更好地处理 Kafka 消息延迟和时序性的挑战,确保数据的可靠性和一致性。  六、从零开始学架构:照着做,你也能成为架构师  1、内容介绍 京东购买链接:从零开始学架构:照着做,你也能成为架构师  本书的内容主要包含以下几部分:  架构设计基础,包括架构设计相关概念、历史、原则、基本方法,让架构设计不再神秘; 架构设计流程,通过一个虚拟的案例,描述了一个通用的架构设计流程,让架构设计不再依赖天才的创作,而是有章可循; 架构设计专题:包括高性能架构设计、高可用架构设计、可扩展架构设计,这些模式可以直接参考和应用; 架构设计实战,包括重构、开源方案引入、架构发展路径、互联网架构模板等 ———————————————— 原文链接:https://blog.csdn.net/guorui_java/article/details/135060020 
  • [其他] SimpleDateFormat类为何不是线程安全的?
    提起SimpleDateFormat类,想必做过Java开发的童鞋都不会感到陌生。没错,它就是Java中提供的日期时间的转化类。这里,为什么说SimpleDateFormat类有线程安全问题呢?有些小伙伴可能会提出疑问:我们生产环境上一直在使用SimpleDateFormat类来解析和格式化日期和时间类型的数据,一直都没有问题啊!我的回答是:没错,那是因为你们的系统达不到SimpleDateFormat类出现问题的并发量,也就是说你们的系统没啥负载!接下来,我们就一起看下在高并发下SimpleDateFormat类为何会出现安全问题,以及如何解决SimpleDateFormat类的安全问题。重现SimpleDateFormat类的线程安全问题为了重现SimpleDateFormat类的线程安全问题,一种比较简单的方式就是使用线程池结合Java并发包中的CountDownLatch类和Semaphore类来重现线程安全问题。有关CountDownLatch类和Semaphore类的具体用法和底层原理与源码解析在【高并发专题】后文会深度分析。这里,大家只需要知道CountDownLatch类可以使一个线程等待其他线程各自执行完毕后再执行。而Semaphore类可以理解为一个计数信号量,必须由获取它的线程释放,经常用来限制访问某些资源的线程数量,例如限流等。好了,先来看下重现SimpleDateFormat类的线程安全问题的代码,如下所示。package io.binghe.concurrent.lab06; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @author binghe * @version 1.0.0 * @description 测试SimpleDateFormat的线程不安全问题 */ public class SimpleDateFormatTest01 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; //SimpleDateFormat对象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { simpleDateFormat.parse("2020-01-01"); } catch (ParseException e) { System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }可以看到,在SimpleDateFormatTest01类中,首先定义了两个常量,一个是程序执行的总次数,一个是同时运行的线程数量。程序中结合线程池和CountDownLatch类与Semaphore类来模拟高并发的业务场景。其中,有关日期转化的代码只有如下一行。simpleDateFormat.parse("2020-01-01");当程序捕获到异常时,打印相关的信息,并退出整个程序的运行。当程序正确运行后,会打印“所有线程格式化日期成功”。运行程序输出的结果信息如下所示。Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" 线程:pool-1-thread-7 格式化日期失败 线程:pool-1-thread-9 格式化日期失败 线程:pool-1-thread-10 格式化日期失败 Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-5" Exception in thread "pool-1-thread-6" 线程:pool-1-thread-15 格式化日期失败 线程:pool-1-thread-21 格式化日期失败 Exception in thread "pool-1-thread-23" 线程:pool-1-thread-16 格式化日期失败 线程:pool-1-thread-11 格式化日期失败 java.lang.ArrayIndexOutOfBoundsException 线程:pool-1-thread-27 格式化日期失败 at java.lang.System.arraycopy(Native Method) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:597) at java.lang.StringBuffer.append(StringBuffer.java:367) at java.text.DigitList.getLong(DigitList.java:191)线程:pool-1-thread-25 格式化日期失败 at java.text.DecimalFormat.parse(DecimalFormat.java:2084) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) 线程:pool-1-thread-14 格式化日期失败 at java.text.DateFormat.parse(DateFormat.java:364) at io.binghe.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:47) 线程:pool-1-thread-13 格式化日期失败 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 线程:pool-1-thread-20 格式化日期失败 at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2084) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at io.binghe.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:47) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2084) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) Process finished with exit code 1说明,在高并发下使用SimpleDateFormat类格式化日期时抛出了异常,SimpleDateFormat类不是线程安全的!!!接下来,我们就看下,SimpleDateFormat类为何不是线程安全的。SimpleDateFormat类为何不是线程安全的?那么,接下来,我们就一起来看看真正引起SimpleDateFormat类线程不安全的根本原因。通过查看SimpleDateFormat类的源码,我们得知:SimpleDateFormat是继承自DateFormat类,DateFormat类中维护了一个全局的Calendar变量,如下所示。/** * The {@link Calendar} instance used for calculating the date-time fields * and the instant of time. This field is used for both formatting and * parsing. * * <p>Subclasses should initialize this field to a {@link Calendar} * appropriate for the {@link Locale} associated with this * <code>DateFormat</code>. * @serial */ protected Calendar calendar;从注释可以看出,这个Calendar对象既用于格式化也用于解析日期时间。接下来,我们再查看parse()方法接近最后的部分。@Override public Date parse(String text, ParsePosition pos){ ################此处省略N行代码################## Date parsedDate; try { parsedDate = calb.establish(calendar).getTime(); // If the year value is ambiguous, // then the two-digit year == the default start year if (ambiguousYear[0]) { if (parsedDate.before(defaultCenturyStart)) { parsedDate = calb.addYear(100).establish(calendar).getTime(); } } } // An IllegalArgumentException will be thrown by Calendar.getTime() // if any fields are out of range, e.g., MONTH == 17. catch (IllegalArgumentException e) { pos.errorIndex = start; pos.index = oldStart; return null; } return parsedDate; }可见,最后的返回值是通过调用CalendarBuilder.establish()方法获得的,而这个方法的参数正好就是前面的Calendar对象。接下来,我们再来看看CalendarBuilder.establish()方法,如下所示。Calendar establish(Calendar cal) { boolean weekDate = isSet(WEEK_YEAR) && field[WEEK_YEAR] > field[YEAR]; if (weekDate && !cal.isWeekDateSupported()) { // Use YEAR instead if (!isSet(YEAR)) { set(YEAR, field[MAX_FIELD + WEEK_YEAR]); } weekDate = false; } cal.clear(); // Set the fields from the min stamp to the max stamp so that // the field resolution works in the Calendar. for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) { for (int index = 0; index <= maxFieldIndex; index++) { if (field[index] == stamp) { cal.set(index, field[MAX_FIELD + index]); break; } } } if (weekDate) { int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1; int dayOfWeek = isSet(DAY_OF_WEEK) ? field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek(); if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) { if (dayOfWeek >= 8) { dayOfWeek--; weekOfYear += dayOfWeek / 7; dayOfWeek = (dayOfWeek % 7) + 1; } else { while (dayOfWeek <= 0) { dayOfWeek += 7; weekOfYear--; } } dayOfWeek = toCalendarDayOfWeek(dayOfWeek); } cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek); } return cal; }在CalendarBuilder.establish()方法中先后调用了cal.clear()与cal.set(),也就是先清除cal对象中设置的值,再重新设置新的值。由于Calendar内部并没有线程安全机制,并且这两个操作也都不是原子性的,所以当多个线程同时操作一个SimpleDateFormat时就会引起cal的值混乱。类似地, format()方法也存在同样的问题。因此, SimpleDateFormat类不是线程安全的根本原因是:DateFormat类中的Calendar对象被多线程共享,而Calendar对象本身不支持线程安全。那么,得知了SimpleDateFormat类不是线程安全的,以及造成SimpleDateFormat类不是线程安全的原因,那么如何解决这个问题呢?接下来,我们就一起探讨下如何解决SimpleDateFormat类在高并发场景下的线程安全问题。解决SimpleDateFormat类的线程安全问题解决SimpleDateFormat类在高并发场景下的线程安全问题可以有多种方式,这里,就列举几个常用的方式供参考,大家也可以在评论区给出更多的解决方案。1.局部变量法最简单的一种方式就是将SimpleDateFormat类对象定义成局部变量,如下所示的代码,将SimpleDateFormat类对象定义在parse(String)方法的上面,即可解决问题。package io.binghe.concurrent.lab06; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @author binghe * @version 1.0.0 * @description 局部变量法解决SimpleDateFormat类的线程安全问题 */ public class SimpleDateFormatTest02 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); simpleDateFormat.parse("2020-01-01"); } catch (ParseException e) { System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }此时运行修改后的程序,输出结果如下所示。所有线程格式化日期成功至于在高并发场景下使用局部变量为何能解决线程的安全问题,会在【JVM专题】的JVM内存模式相关内容中深入剖析,这里不做过多的介绍了。当然,这种方式在高并发下会创建大量的SimpleDateFormat类对象,影响程序的性能,所以,这种方式在实际生产环境不太被推荐。2.synchronized锁方式将SimpleDateFormat类对象定义成全局静态变量,此时所有线程共享SimpleDateFormat类对象,此时在调用格式化时间的方法时,对SimpleDateFormat对象进行同步即可,代码如下所示。package io.binghe.concurrent.lab06; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @author binghe * @version 1.0.0 * @description 通过Synchronized锁解决SimpleDateFormat类的线程安全问题 */ public class SimpleDateFormatTest03 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; //SimpleDateFormat对象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { synchronized (simpleDateFormat){ simpleDateFormat.parse("2020-01-01"); } } catch (ParseException e) { System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }此时,解决问题的关键代码如下所示。synchronized (simpleDateFormat){ simpleDateFormat.parse("2020-01-01"); }运行程序,输出结果如下所示。所有线程格式化日期成功需要注意的是,虽然这种方式能够解决SimpleDateFormat类的线程安全问题,但是由于在程序的执行过程中,为SimpleDateFormat类对象加上了synchronized锁,导致同一时刻只能有一个线程执行parse(String)方法。此时,会影响程序的执行性能,在要求高并发的生产环境下,此种方式也是不太推荐使用的。3.Lock锁方式Lock锁方式与synchronized锁方式实现原理相同,都是在高并发下通过JVM的锁机制来保证程序的线程安全。通过Lock锁方式解决问题的代码如下所示。package io.binghe.concurrent.lab06; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author binghe * @version 1.0.0 * @description 通过Lock锁解决SimpleDateFormat类的线程安全问题 */ public class SimpleDateFormatTest04 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; //SimpleDateFormat对象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); //Lock对象 private static Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { lock.lock(); simpleDateFormat.parse("2020-01-01"); } catch (ParseException e) { System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); }finally { lock.unlock(); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }通过代码可以得知,首先,定义了一个Lock类型的全局静态变量作为加锁和释放锁的句柄。然后在simpleDateFormat.parse(String)代码之前通过lock.lock()加锁。这里需要注意的一点是:为防止程序抛出异常而导致锁不能被释放,一定要将释放锁的操作放到finally代码块中,如下所示。finally { lock.unlock(); }运行程序,输出结果如下所示。所有线程格式化日期成功此种方式同样会影响高并发场景下的性能,不太建议在高并发的生产环境使用。4.ThreadLocal方式使用ThreadLocal存储每个线程拥有的SimpleDateFormat对象的副本,能够有效的避免多线程造成的线程安全问题,使用ThreadLocal解决线程安全问题的代码如下所示。package io.binghe.concurrent.lab06; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @author binghe * @version 1.0.0 * @description 通过ThreadLocal解决SimpleDateFormat类的线程安全问题 */ public class SimpleDateFormatTest05 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { threadLocal.get().parse("2020-01-01"); } catch (ParseException e) { System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }通过代码可以得知,将每个线程使用的SimpleDateFormat副本保存在ThreadLocal中,各个线程在使用时互不干扰,从而解决了线程安全问题。运行程序,输出结果如下所示。所有线程格式化日期成功此种方式运行效率比较高,推荐在高并发业务场景的生产环境使用。另外,使用ThreadLocal也可以写成如下形式的代码,效果是一样的。package io.binghe.concurrent.lab06; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @author binghe * @version 1.0.0 * @description 通过ThreadLocal解决SimpleDateFormat类的线程安全问题 */ public class SimpleDateFormatTest06 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(); private static DateFormat getDateFormat(){ DateFormat dateFormat = threadLocal.get(); if(dateFormat == null){ dateFormat = new SimpleDateFormat("yyyy-MM-dd"); threadLocal.set(dateFormat); } return dateFormat; } public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { getDateFormat().parse("2020-01-01"); } catch (ParseException e) { System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }5.DateTimeFormatter方式DateTimeFormatter是Java8提供的新的日期时间API中的类,DateTimeFormatter类是线程安全的,可以在高并发场景下直接使用DateTimeFormatter类来处理日期的格式化操作。代码如下所示。package io.binghe.concurrent.lab06; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @author binghe * @version 1.0.0 * @description 通过DateTimeFormatter类解决线程安全问题 */ public class SimpleDateFormatTest07 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { LocalDate.parse("2020-01-01", formatter); }catch (Exception e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }可以看到,DateTimeFormatter类是线程安全的,可以在高并发场景下直接使用DateTimeFormatter类来处理日期的格式化操作。运行程序,输出结果如下所示。所有线程格式化日期成功使用DateTimeFormatter类来处理日期的格式化操作运行效率比较高,推荐在高并发业务场景的生产环境使用。6.joda-time方式joda-time是第三方处理日期时间格式化的类库,是线程安全的。如果使用joda-time来处理日期和时间的格式化,则需要引入第三方类库。这里,以Maven为例,如下所示引入joda-time库。<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.9</version> </dependency>引入joda-time库后,实现的程序代码如下所示。package io.binghe.concurrent.lab06; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * @author binghe * @version 1.0.0 * @description 通过DateTimeFormatter类解决线程安全问题 */ public class SimpleDateFormatTest08 { //执行总次数 private static final int EXECUTE_COUNT = 1000; //同时运行的线程数量 private static final int THREAD_COUNT = 20; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd"); public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(THREAD_COUNT); final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < EXECUTE_COUNT; i++){ executorService.execute(() -> { try { semaphore.acquire(); try { DateTime.parse("2020-01-01", dateTimeFormatter).toDate(); }catch (Exception e){ System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败"); e.printStackTrace(); System.exit(1); } semaphore.release(); } catch (InterruptedException e) { System.out.println("信号量发生错误"); e.printStackTrace(); System.exit(1); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("所有线程格式化日期成功"); } }这里,需要注意的是:DateTime类是org.joda.time包下的类,DateTimeFormat类和DateTimeFormatter类都是org.joda.time.format包下的类,如下所示。import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter;运行程序,输出结果如下所示。所有线程格式化日期成功使用joda-time库来处理日期的格式化操作运行效率比较高,推荐在高并发业务场景的生产环境使用。解决SimpleDateFormat类的线程安全问题的方案总结综上所示:在解决解决SimpleDateFormat类的线程安全问题的几种方案中,局部变量法由于线程每次执行格式化时间时,都会创建SimpleDateFormat类的对象,这会导致创建大量的SimpleDateFormat对象,浪费运行空间和消耗服务器的性能,因为JVM创建和销毁对象是要耗费性能的。所以,不推荐在高并发要求的生产环境使用。synchronized锁方式和Lock锁方式在处理问题的本质上是一致的,通过加锁的方式,使同一时刻只能有一个线程执行格式化日期和时间的操作。这种方式虽然减少了SimpleDateFormat对象的创建,但是由于同步锁的存在,导致性能下降,所以,不推荐在高并发要求的生产环境使用。ThreadLocal通过保存各个线程的SimpleDateFormat类对象的副本,使每个线程在运行时,各自使用自身绑定的SimpleDateFormat对象,互不干扰,执行性能比较高,推荐在高并发的生产环境使用。DateTimeFormatter是Java 8中提供的处理日期和时间的类,DateTimeFormatter类本身就是线程安全的,经压测,DateTimeFormatter类处理日期和时间的性能效果还不错(后文单独写一篇关于高并发下性能压测的文章)。所以,推荐在高并发场景下的生产环境使用。joda-time是第三方处理日期和时间的类库,线程安全,性能经过高并发的考验,推荐在高并发场景下的生产环境使用。来自:https://zhuanlan.zhihu.com/p/395751163
  • [其他] java线程池有哪些类型
    Java中主要有四种类型的线程池,它们分别是:可缓存线程池:通过Executors.newCachedThreadPool()创建,这种线程池会根据需要创建新线程,但同时会重用空闲的线程。如果线程池中的线程超过60秒未被使用,则会被终止并移除,这样可以避免资源浪费。固定线程池:通过Executors.newFixedThreadPool(int nThreads)创建,这种线程池的特点是核心线程数和最大线程数相同,适用于执行长期任务且任务数量固定的情况。定时线程池:通过Executors.newScheduledThreadPool(int corePoolSize)创建,适用于需要周期性执行任务的场景,如定时任务、定时扫描等。单线程化线程池:通过Executors.newSingleThreadExecutor()创建,这种线程池只有一个工作线程,适用于需要保证任务按顺序执行的场景。这些线程池都是ExecutorService接口的实现类,它们各自有不同的特点和适用场景。在实际开发中,选择合适的线程池类型可以提高程序的性能和响应速度。
  • [其他] java中如何设计跨线程的数据同步问题
    在Java中,跨线程的数据同步问题可以通过以下几种方式来解决:使用synchronized关键字:通过在方法或代码块上添加synchronized关键字,可以确保同一时间只有一个线程能够访问共享资源。例如:public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } public synchronized void decrement() { count--; } public synchronized int getCount() { return count; } }使用ReentrantLock类:ReentrantLock是一个可重入的互斥锁,它提供了与synchronized相同的基本行为和语义,但具有更高的灵活性。例如:import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public void decrement() { lock.lock(); try { count--; } finally { lock.unlock(); } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }使用Semaphore类:Semaphore是一个计数信号量,可以用来控制同时访问某个资源的线程数量。例如:import java.util.concurrent.Semaphore; public class SemaphoreExample { private final Semaphore semaphore = new Semaphore(1); private int count = 0; public void increment() throws InterruptedException { semaphore.acquire(); try { count++; } finally { semaphore.release(); } } public void decrement() throws InterruptedException { semaphore.acquire(); try { count--; } finally { semaphore.release(); } } public int getCount() throws InterruptedException { semaphore.acquire(); try { return count; } finally { semaphore.release(); } } }使用Atomic类:Atomic类提供了一种无锁的方式来实现线程安全的操作。例如:import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public void decrement() { count.decrementAndGet(); } public int getCount() { return count.get(); } }以上四种方法都可以解决Java中跨线程的数据同步问题,具体选择哪种方法取决于实际需求和场景。
  • [其他] java中对Long判空Object.isNull和!=null的区别
    在Java中,对Long类型的变量进行判空时,可以使用Objects.isNull()方法和!= null两种方式。它们的区别如下:Objects.isNull()方法:这是Java 8引入的一个工具类方法,用于判断一个对象是否为null。它接受一个参数,如果参数为null,则返回true,否则返回false。这个方法主要用于处理包装类型(如Long、Integer等)的null值判断。import java.util.Objects; public class Main { public static void main(String[] args) { Long num = null; if (Objects.isNull(num)) { System.out.println("num is null"); } else { System.out.println("num is not null"); } } }!= null:这是Java中常见的判空方式,用于判断一个对象是否为null。如果对象不等于null,则表示对象不为空。这种方式适用于所有类型的对象,包括基本类型和引用类型。public class Main { public static void main(String[] args) { Long num = null; if (num != null) { System.out.println("num is not null"); } else { System.out.println("num is null"); } } }总结:Objects.isNull()方法主要用于处理包装类型的null值判断,而!= null方式适用于所有类型的对象。在实际使用中,可以根据需要选择合适的判空方式。
  • [技术干货] Java智慧校园系统源码springboot + vue智慧学校源码 微信小程序+电子班牌
    Java智慧校园系统源码springboot + vue智慧学校源码 微信小程序+电子班牌智慧校园的建设逐渐被师生、家长认可接受,智慧校园通过对在校师生、教务等所有人员的信息以及各种信息搜集与储存,进行数据优化与管理,为师生们提供更加智能化的校园服务。未来智慧校园将不再是一个陌生词,而会真正地应用在更多的校园管理中,让我们的校园生活变得更加美好智慧校园技术架构:后端:Java  框架:springboot 前端页面:vue 小程序:小程序原生开发智慧校园移动家长端应用:通知管理、图片管理、班级考勤、综合素质评价、视频管理、请假管理、成绩管理、个人信息、进离校管理、教师通讯录、家长留言、课堂点名、家长会签到、活动报名、放学管理、学生评价。智慧校园移动移动教师端应用:设备管理、通知管理、图片管理、班级考勤、综合素质评价、视频管理、请假管理、成绩管理、个人信息、进离校管理、家长通讯录、教师通讯录、教师课表、AI智能分析、课堂点名、课堂授课、家长会签到、活动报名、积分商城、倒计时、班级德育、体温检测、放学管理、学生评价。▷学校信息:学校信息自定义格式展示,可上传学校相册,自定义学校基础信息栏。支持管理员或教师对学校的基本学校信息进行编辑并浏览,通过编辑提交后全校可查看▷学科设置:学校学科展示,添加学科,双击修改学科、删除学科。▷组织架构:支持管理员单个创建以及批量创建部门,部门层级最少不低于四级架构;组织架构支持可视展示。▷教师数据:支持教师多部门多角色,支持粘贴复制简易化批量导入,批量导入支持字段错误检索,支持一键重置密码。▷学生数据:支持单个学生信息录入、修改、删除,支持粘贴复制简易化批量导入,支持一键重置密码▷教室管理:班级绑定教室,教室绑定设备,通过教室切换班级即可实现设备一键切换班级,支持添加、删除、修改教室信息。▷权限管理:数据权限、功能权限、角色管理、应用权限。▷课表管理:支持支持管理员课表模板设置,支持复制粘贴简易化批量导入,批量导入支持学科及任课教师错误检索。智慧校园系统框架技术参数要求(提供不少于3张的证明截图,并附上原厂公章,原件备查):1、系统后台采用Java开发语言,前端采用VUE开发框架2、★系统采用微服务springcloud架构的作为后台服务器架构3、★系统支持搭建开发具备服务注册发现、客户负载均衡、服务间通信的微服务架构4、系统支持使用SpringCloud Eurek、SpringCloud Ribbon、restTemplate 等组件进行开发5、注册中心:接受服务提供者的注册,提供服务注册者的存储信息(如:IP、端口号、服务名)与微服务保持心跳6、服务提供者:注册自己的服务到服务中心,服务提供者向注册中心发送自己的信息以及一些健康状态。7、服务消费者:定期向注册中心发送查询请求,以定期获得服务提供者的一些信息(如:IP,端口号,服务名)。8、使用Quartz框架实现任务调度(如:对不同的学校在不同的时间段执行上课提醒)9、使用Mina网络应用型框架(实时接收第三方进离校情况,闸机,人脸机等),后续可发展实时通信组件有:feing(实现服务对服务之间的调用)zuul(网关负载均衡,反向代理,隐藏真实ip地址)
  • [技术干货] Java+BS +saas云HIS系统源码SpringBoot+itext + POI + ureport2数字化医院系统源码
    Java+BS +saas云HIS系统源码SpringBoot+itext + POI + ureport2数字化医院系统源码医院云HIS系统是一种运用云计算、大数据、物联网等新兴信息技术的业务和技术平台。它按照现代医疗卫生管理要求,在特定区域内以数字化形式收集、存储、传递和处理医疗卫生行业的数据。通过云HIS系统,可以实现区域内医疗卫生信息资源的集中统管、统一调配、按需服务,为居民、医疗机构、卫生管理机关和其他机构提供云服务。云HIS系统的主要功能包括门诊收费管理、住院收费管理、门诊医生工作站、住院医生工作站、住院护士工作站、辅助检查科室管理、药房药品管理、药库药品管理以及报表查询等,以满足诊所业务中看诊、收费、发药、药库管理、经营分析等多环节的工作需要。云HIS药物管理系统门诊发/退药:门诊发退药,发退药历史记录可查询住院发药:住院患者发药住院汇总发药:住院处方明细可以汇总,按汇总单发药住院退药:住院退药之前需先进行审核,审核通过才能退药药物信息管理:对药品信息(基础信息、厂商信息、医嘱信息、医保信息)集中管理;可一键导入上传药品信息;可下载所有药品信息;可设置与药品外部系统进行关联入出库管理:药房药库出入库操作;出入库单据打印;出入库历史记录查询药物调拨:药房药库调拨操作,支持跨域调拨;调拨单据打印;调拨历史记录查询药物盘点:药房药库盘点操作;盘点明细查询;历史盘点记录查询药品控制:对药品信息和药品的使用情况、有效期、是否能使用、是否恢复冻结药品的状态进行控制管理;定时检查药品期效,逾期则消息栏通知提醒;药品拆分:药品拆分后,按拆分最小单位计费云HIS经济管理系统挂号统计:统计门诊挂号信息,支持多种查询方式门诊费用管理:门诊收费,支持医保结算、医保撤销;门诊收费流水查询,可进行退费、发票重打补打操作;门诊收费日结,可进行预结操作,可查看历史交账;门诊冲正交易业务;预约管理:预约挂号服务;统计预约挂号信息,支持多种查询方式排班管理:对在职的员工进行排班,可设置班次、查看本月排班情况患者管理:门诊患者信息集中管理,可查看患者个人详情、诊断历史、挂号收费记录等。住院登记:住院患者信息登记,自费登记或医保登记床位管理:住院科室床位信息管理预缴金管理:住院患者预缴金信息管理住院清单:住院患者处方明细、一日清单明细;住院清单可打印出院结算:住院患者进行出院结算,可医保结算结算报表:统计不同类型患者的结算信息票据管理:管理系统使用的发票、查询票据的使用情况、统计票据的使用或报损的情况住院费用管理:住院患者产生费用每日定时自动计费;住院患者药品和项目退费、住院患者结算明细查询云HIS报表管理系统门诊收入汇总:对门诊收入汇总,可按时间段查询住院收入汇总:对住院收入汇总,可按时间段查询收费统计报表:门诊收费统计,可按患者类型查询收费明细报表:门诊收费明细,可按患者类型查询缴款日报:每日收费后结转的金额数,用于财务每日和月末的统计审核门诊收费汇总:汇总门诊收费员每月的收费总额,用于月末统计账目住院科室日志:统计每月的出入院人数,和人次数,用来概览医院整体运行情况住院结算汇总:汇总住院收费员每月的收费总额,用于月末统计账目医疗项目统计:统计门诊或住院各科室开立的医疗项目的数量、产生费用检查项目统计:对所有患者检查项目的信息统计检验项目统计:对所有患者检验项目的信息统计月末收支汇总:汇总各科室阶段性的药品收支情况、支持结转操作、汇总票据打印药品进销存统计:可查询药品的进销存详情云HIS系统管理机构信息:管理医疗机构的基础信息科室管理:医疗机构各个科室基础信息管理员工管理:医疗机构所有员工基础信息管理;可设置角色,登录密码角色管理:机构角色定义及角色菜单使用权限的分配字典管理:机构部分字典信息集中管理(如门诊项目、收费项目等)参数设置:快捷操作参数设置、收费规则灵活设置等报表模板管理:可浏览系统所有的报表单据模板医嘱模板管理:住院医嘱模板管理;门诊处方模板管理
  • [技术干货] java+saas模式医院云HIS系统源码Java+Spring+MySQL + MyCat融合BS版电子病历系统,支持电子病历四级
    java+saas模式医院云HIS系统源码Java+Spring+MySQL + MyCat融合BS版电子病历系统,支持电子病历四级云HIS系统是一款满足基层医院各类业务需要的健康云产品。该产品能帮助基层医院完成日常各类业务,提供病患预约挂号支持、病患问诊、电子病历、开药发药、会员管理、统计查询、医生工作站和护士工作站等一系列常规功能,还能与公卫、PACS等各类外部系统融合,实现多层机构之间的融合管理。云HIS系统源码框架:技术框架(1)总体框架:SaaS应用,全浏览器访问前后端分离,多服务协同服务可拆分,功能易扩展(2)技术细节:前端:Angular+Nginx后台:Java+Spring,SpringBoot,SpringMVC,SpringSecurity,MyBatisPlus,等数据库:MySQL + MyCat缓存:Redis+J2Cache消息队列:RabbitMQ任务调度中心:XxlJob接口技术:RESTful API + WebSocket + WebService报表组件:itext + POI + ureport2数据库监控组件:Canal云HIS系统电子 病历系统简介门诊电子病历:门诊电子病历自动补充门诊信息、病历模板可定制。住院电子病历:住院病历及住院病程管理;住院病历存为模板、也可通过模板快速新建病历;住院护理记录管理;住院护理记录可存为模板、也可通过模板快速建护理记录。病历质控:医生个人质控,查看历史已归档患者的缺陷信息;查看医生提交的延期申请情况;查看已存在缺陷、缺陷申诉提交操作、以及查看缺陷申诉情况;进行病历召回操作,以及查看病历召回状态;查看个人电子病历质控评分情况。病历控制:质控人员查看和审核医生发起的病历召回申请。缺陷监控:质控人员查看一段时间内不同类型缺陷的数量,分为在院、未归档、已归档三个状态;质控人员查看和审核医生发起的病历延期书写申请;质控人员查看和审核医生发起的缺陷申诉请求;质控人员查看和审核患者病历书写质量评分结果;质控人员查看各个科室在一段时间内病历的质控等级情况。质控设置:质控规则、评分规则、评级规则等的设置。患者列表:患者信息,自动判别患者当前就诊状态(待诊、接诊、已诊);系统自动识别已登记过的患者,自动补充基础信息;支持快速选择未挂号患者接诊;云HIS电子病历模板板块合并预览:该功能仅在住院病程中使用,目的是将某个患者的住院病程中所有的病历聚合在一起形成一张大的病历并能够打印,合并预览后的病历仅支持打印功能,不支持保存以及控件编辑功能。普通病历:在该模式下,可以对单个患者的病历数据进行新建、编辑、预览、保存,以及打印的操作,是医院比较常用和重要的功能模块,暂不支持在同一窗口下打开多张病历的相关操作。自定义模板:模板编辑:医疗机构涉及的病历模板均可以按需设计制作,可通过运维运营分系统模板管理子模块病历模板中的‘编辑’功能实现该操作。存为模板:医师将当前病历通过存为模板的方式设置为医师的个人病历模板,以便相同患者的同一病历后续能够得到复用。数据同步:针对同一个患者不同病历之间的数据共享而存在的,同步功能主要是针对病历中的6类控件(提纲元素、宏元素、日期元素、选择元素、单选元素、复选元素)数据进行同步。病历打印:常规打印、PDF打印辅助输入:辅助输入提供当前日期、当前时间、医师签名等便捷操作。页面布局:调节纸张方向、大小;设置边距、打印方式。导出PDF:将当前任意病历(普通病历、自定义个人模板、合并预览、历史病历)直接导出成PDF下载到本地。云HIS系统优势(1)客户/用户角度无需安装,登录即用多终端同步,轻松应对工作环境转换系统使用简单、易上手,信息展示主次分明、重点突出极致降低用户操作负担:关联功能集中、减少跳转,键盘快捷操作,自由批量执行,自动模糊搜索全方位信息保护,确保信息安全客户方零运维(2)开发/运维角度采用主流成熟技术,软件结构简洁、代码规范易阅读支持多样化灵活配置,提取大量公共参数,无需修改代码即可满足不同客户需求服务组织合理,功能高内聚,服务间通信简练功能易扩展,轻松应对个性化定制需求专业系统运维运营工具,助力快速、准确运维,支持规模化运营
  • [技术干货] 4套java开发的智慧系统源码 智慧校园系统源码 智慧工地系统源码 智慧城管系统源码3D 智能导诊系统源码
    4套java开发的智慧系统源码 智慧校园系统源码 智慧工地系统源码 智慧城管系统源码3D 智能导诊系统源码Java智慧校园系统源码 智慧学校源码 微信小程序+电子班牌智慧校园系统简介:智慧校园的建设逐渐被师生、家长认可接受,智慧校园通过对在校师生、教务等所有人员的信息以及各种信息搜集与储存,进行数据优化与管理,为师生们提供更加智能化的校园服务。未来智慧校园将不再是一个陌生词,而会真正地应用在更多的校园管理中,让我们的校园生活变得更加美好。系统源码技术说明:后端:Java   框架:springboot  +前端页面:vue  小程序+小程序原生开发+电子班牌:Java Android移动家长端应用:通知管理、图片管理、班级考勤、综合素质评价、视频管理、请假管理、成绩管理、个人信息、进离校管理、教师通讯录、家长留言、课堂点名、家长会签到、活动报名、放学管理、学生评价。移动教师端应用:设备管理、通知管理、图片管理、班级考勤、综合素质评价、视频管理、请假管理、成绩管理、个人信息、进离校管理、家长通讯录、教师通讯录、教师课表、AI智能分析、课堂点名、课堂授课、家长会签到、活动报名、积分商城、倒计时、班级德育、体温检测、放学管理、学生评价。Java智慧工地源码 智慧工地监管平台源码 SaaS模式PC端+手机端+平板端智慧工地系统简介:智慧工地是指利用移动互联、物联网、智能算法、地理信息系统、大数据挖掘分析等信息技术,提高项目现场的“人•机•料•法•环•安”等施工要素信息化管理水平,实现工程施工可视化智能管理,并逐步实现绿色生态建造。系统源码技术说明:微服务架构+Java+Spring Cloud +UniApp +MySql,支持多端展示(PC端、手机端、平板端);数字孪生可视化大屏,一张图掌握项目整体情况;使用轻量化模型,部署三维可视化管理,与一线生产过程相融合,集成数据后台,统一前端入口,呈现多方项目信息;用户PC端、移动端数据同步,依托组件化开发平台。;依托数据交互子平台,形成用户多系统间数据融合;依托智慧工地平台,满足省、市级住建数据监管要求;利用5G及智能终端算法,实现IOT设备数据抓取与处理。Java智慧城管系统源码 城管智慧执法系统源码 数字城管APP系统源码 城市管理综合执法监督系统源码智慧城管系统简介:智慧城管系统是一个基于现代信息技术手段的综合管理平台,旨在通过强化信息获取自动化、监督管理精细化、业务职能协同化、服务手段多样化、辅助决策智能化以及执法手段人性化,实现城市管理要素、管理过程和管理决策的全方位智慧化。具体来说,智慧城管系统充分利用物联网、云计算、信息融合、网络通讯、数据分析与挖掘等技术,对城市管理进行全方位覆盖。它通过建立城市综合管理平台,将城市的信息和管理资源有机结合起来,实现城管执法、市容环卫、市政设施、园林绿化等业务的数字化和网络化。该系统不仅提高了城市管理的科学化水平,还促进了市容环境和城市形象的改善。城管综合执法监督系统是一个综合性的管理平台,旨在提高城市管理的效率和质量,确保执法活动的公开、公正和透明。该系统通过整合各种资源和信息,实现对城管执法活动的全面监督和管理。系统源码技术说明:微服务+开发语言: java+开发工具:idea、VSCode+前端框架:vue+element+后端框架:springboot+数 据 库:mysql5.7+移 动 端:uniapp功能模块本系统共分为七个模块:首页、执法办案、视频智能分析、统计分析、执法队伍、基础支撑、系统管理;首页模块下包含:待办事项、常用功能、通知公告、简易案件、普通案件、案由库、法律法规、文书模板;执法办案模块下包含:我的待办、线索管理、管理事件、简易案件、普通案件、重大案件会办、车辆违停管理、当事人管理;视频智能分析模块下包含:市容违规、监控配置;统计分析模块下包含:线索案件统计、事案件分析、上报数据分析、事案件区域统计、AI 视频识别分析、考勤统计;执法队伍模块下包含:执法调度、考勤管理、机构管理、人员管理;基础支撑模块下包含:文书管理、法律法规、案由库、通知公告;系统管理模块下包含:执法设置、菜单管理、字典管理、参数管理、日志管理、角色管理;java智慧导诊系统源码 互联网智能3D导诊系统源码 智慧医院导诊系统源码 智慧导诊系统简介:智慧导诊解决患者盲目就诊问题,减轻分诊工作压力。降低患者挂错号比例,优化患者就诊流程,有效提高线上线下医疗机构接诊效率。患者可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。系统源码技术说明:技术架构:springboot+redis+mybatis plus+mysql+RocketMQ+开发语言:java+开发工具:IDEA+前端框架:Uniapp+后端框架:springboot+数 据 库:mysql+移 动 端:微信小程序、H5应用场景:1.智慧医院:帮助患者判断应挂号科室,降低科室间转诊率:帮助医院导诊工作人员接待挂号相关问题,对接医院挂号系统,完成导诊后可直接挂号2.互联网医院:帮助患者判断应挂号科室,降低科室间转诊率,标准科室对照,精准分配在线问诊科室3.医疗健康平台:为平台赋能,向患者提供标准科室就诊建议,标准科室对照,精准分配在线问诊科室
  • [技术干货] java/C#语言开发的医疗信息系统11套源码
    java/C#语言开发的医疗信息系统11套源码java医院云HI系统简介:SaaS模式Java版云HIS系统,融合B/S版电子病历系统,支持电子病历四级,云HIS系统是一款满足基层医院各类业务需要的健康云产品。该产品能帮助基层医院完成日常各类业务,提供病患预约挂号支持、病患问诊、电子病历、开药发药、会员管理、统计查询、医生工作站和护士工作站等一系列常规功能,还能与公卫、PACS等各类外部系统融合,实现多层机构之间的融合管理。医院云HIS源码技术细节: 前端:Angular+Nginx+后台:Java+Spring,SpringBoot,SpringMVC,SpringSecurity,MyBatisPlus,等+数据库:MySQL + MyCat+缓存:Redis+J2Cache+ 消息队列:RabbitMQ+ 任务调度中心:XxlJob+ 接口技术:RESTful API + WebSocket + WebService+ 报表组件:itext + POI + ureport2+数据库监控组件:Canal C#云LIS 区域云LIS系统简介云LIS是为区域医疗提供临床实验室信息服务的计算机应用程序,可协助区域内所有临床实验室相互协调并完成日常检验工作,对区域内的检验数据进行集中管理和共享,通过对质量控制的管理,最终实现区域内检验结果互认。其目标是以医疗服务机构为主体,以医疗资源和检验信息共享为目标,集成共性技术及医疗服务关键技术,建立区域协同检验,最大化利用有限的医疗卫生资源。云LIS源码技术说明:技术架构:ASP.NET CORE 3.1 MVC + SQLserver + Redis等+开发语言:C# 6.0、JavaScript+前端框架:JQuery、EasyUI、Bootstrap+后端+MVCC语言带三维重建和还原的PACS医学影像系统简介医院医学影像PACS系统源码,集成三维影像后处理功能,包括三维多平面重建、三维容积重建、三维表面重建、三维虚拟内窥镜、最大/小密度投影、心脏动脉钙化分析等功能。PACS系统可实现检查预约、病人信息登记、计算机阅片、电子报告书写、胶片打印、数据备份等一系列满足影像科室日常工作的功能。PACS系统特点:开放式体系结构,符合DICOM3.0标准,提供HL7标准接口,可实现与提供相应标准接口的HIS系统以及其他医学信息系统间的数据通信。不同图像信息的处理。多种临床工具包,可对图像进行多种增强处理、测量、标注,充分发挥电子胶片的特点。支持WORKLIST功能,自动化工作流程。有效解决大容量图像存储问题,支持多种存储方式和多种备份方式。报告单有多种模式及自定义样式。集成三维影像后处理功能。java3D智能导诊系统简介:智慧导诊解决患者盲目就诊问题,减轻分诊工作压力。降低患者挂错号比例,优化患者就诊流程,有效提高线上线下医疗机构接诊效率。患者可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。系统源码技术说明:技术架构:springboot+redis+mybatis plus+mysql+RocketMQ+开发语言:java+开发工具:IDEA+前端框架:Uniapp+后端框架:springboot+数 据 库:mysql+移 动 端:微信小程序、H5 java医院安全(不良)事件报告系统简介:医院安全(不良)事件管理系统采用无责的、自愿的填报不良事件方式,有效地减轻医护人员的思想压力,实现以事件为主要对象,可以自动、及时、实际地反应医院的安全、不良、近失事件的情况,更好地掌握不良事件的发生趋势,为及时采取适当的管理措施和流程、制度改进提供了良好的量化依据。构建全院人员,在医疗、环境、设备、服务及相关制度体系运行过程中,发现存在的不良、隐患事件,能够以匿名、实名方式主动、方便、快捷的上报、反馈。各类事件按科学质量管理理论引导处理,临床科室处理,职能科室管理追踪,院领导监控查阅,全院分级协同管理,将有效的增强医院风险预警分析和处理,防患于未然;各级人员的处理效率、工作量、全院质量数据仪表盘动态展现,一目了然,互相提携,共同改进。系统源码技术说明:前后端分离,仓储模式+开发语言:PHP   开发工具:vscode  前端框架:vue2+element+后端框架:laravel8  数 据 库:mysql5.7C#微信预约/支付宝挂号系统简介微信公众号预约挂号系统、支付宝小程序预约挂号系统主要是让自费、医保患者在手机上就能实现就医全过程,实时预约挂号、自费、医保结算,同时还可以查询检查检验报告等就诊信息,真正实现了让信息“多跑路”,让群众“少跑腿”。系统与HIS对接,通过医院微信公众号,患者用身份证注册以后,可以预约看诊的时间、医生挂号缴费。预约成功后,会收到预约码或二维码,患者可以在预约的时间段,前往医院看诊。既可以节约患者的等待时间,又可以降低医院的负荷。系统源码技术说明:技术架构:net+开发语言:C#+开发工具:VS2019+前端框架:uni-app+后端框架:net+数 据 库:SqlServer 2012+移 动 端:微信公众号、支付宝小程序Java药物不良反应智能监测 ADR智能监测系统简介:ADR指的是药品不良反应,即在合格药品在正常用法用量下,出现与用药目的无关或意外的有害反应。ADR数据辨别引擎、药品ADR信号主动监测引擎、ADR处置行为分析引擎。ADR数据辨别引擎,通过主动监测患者具象临床指标,比如检验异常指标实现及时预警。药品ADR信号主动监测引擎,根据以往真实世界中临床不良反应经验,以及国内外指南,形成不良反应知识库,智能引擎结构化知识库主动访问患者临床特征,实现不良反应主动预判。ADR处置行为分析引擎,主动监测患者临床处置行为,推理患者潜在发生的不良生命体征,深度挖掘潜在不良反应患者。系统源码技术说明开发语言:Java+技术架构:B/S+开发工具:IntelliJ、IDEA+前端框架:Vue+后端框架:SpringBoot+数 据 库:MySQLJava医院绩效考核系统简介作为医院用综合绩效核算系统,系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。系统源码技术说明开发语言:java+技术架构:B/S架构+开发工具:maven、Visual Studio Code+前端框架:avue+后端框架:springboot、mybaits数 据 库:MySQLC#医院体检系统 PEIS系统简介体检系统,是专为体检中心/医院体检科等体检机构,专门开发的全流程管理系统,通过软件实现检测仪器数据自动提取,内置多级医生工作台,细化工作将体检检查结果汇总,生成体检报告登记到计算机系统中。通过软件系统进行数据分析统计与评判以及建立体检相关的体检档案。从而实现体检流程的信息化,提高工作效率,减少手动结果录入的一些常犯错误。在实际应用中,医院体检系统能够解决传统体检中手工操作带来的问题,如工作量大、效率低下、易漏检、重检或错检等。通过与医院信息系统(如HIS、LIS、PACS等)的连接,系统能够满足体检中心的日常工作流程,提供更好的管理、统计和查询分析功能。同时,基于网络基础的系统可以在网上传输各种体检、检验检查结果,减少中间环节,提高安全性和可靠性。系统源码技术说明技术架构:C/S架构+开发语言:C#+开发工具:VS2016+数 据 库:SQLSERVER 2008C#智慧手术室管理平台系统简介手术麻醉临床信息系统有着完善的临床业务功能,能够涵盖整个围术期的工作,能够采集、汇总、存储、处理、展现所有的临床诊疗资料。通过该系统的实施,能够规范麻醉科的工作流程,实现麻醉手术过程的信息数字化,自动生成麻醉的各种医疗文书,完成共享HIS、LIS、PACS和EMR等手术患者信息,从而提高麻醉、手术工作的管理水平系统源码技术说明技术架构:前后端分离+开发语言:C#.net6.0+开发工具:vs2022,vscode+前端框架:Vue,Ant-Design+后端框架:百小僧开源框架+数 据 库:sqlserver2019
  • [专题汇总] 2024年3月技术干货专题汇总来啦 。速进
     大家好,三月的合集又来了,本次涵盖了java,mysql,spirngboot,oracle,nginx,webpack,css,python,mongoDB,devops,golang诸多内容供大家学习。 1.Python中数据解压缩的技巧分享【转】 https://bbs.huaweicloud.com/forum/thread-0274147063634001023-1-1.html  2.CSS如何设置背景模糊周边有白色光晕(解决方案)【转】 https://bbs.huaweicloud.com/forum/thread-02121147063476589021-1-1.html  3.CSS实现渐变式圆点加载动画【转】 https://bbs.huaweicloud.com/forum/thread-0274147063231589021-1-1.html  4. Nginx access.log日志详解及统计分析小结【转】 https://bbs.huaweicloud.com/forum/thread-0274147062581229019-1-1.html  5.Webpack部署本地服务器的方法【转】 https://bbs.huaweicloud.com/forum/thread-02110147062400691022-1-1.html  6.Nginx漏洞整改实现限制IP访问&隐藏nginx版本信息【转】 https://bbs.huaweicloud.com/forum/thread-0273147062364276013-1-1.html  7.Nginx加固的几种方式(控制超时时间&限制客户端下载速度&并发连接数)【转】 https://bbs.huaweicloud.com/forum/thread-02109147062305049017-1-1.html  8.Nginx配置http和https的实现步骤【转】 https://bbs.huaweicloud.com/forum/thread-0274147061484998017-1-1.html  9.MongoDB内存过高问题分析及解决【转】 https://bbs.huaweicloud.com/forum/thread-0276147061392824019-1-1.html  10.Oracle数据库中字符串截取最全方法总结【转】 https://bbs.huaweicloud.com/forum/thread-0240147061327119025-1-1.html  11.MySQL数据库如何克隆(带脚本)【转】 https://bbs.huaweicloud.com/forum/thread-0294147061178556014-1-1.html  12.mysql5.6建立索引报错1709问题及解决【转】 https://bbs.huaweicloud.com/forum/thread-0240147061135949024-1-1.html  13.修改Mysql索引长度限制解决767 byte限制问题【转】 https://bbs.huaweicloud.com/forum/thread-0240147060706517023-1-1.html  14.SQL实现模糊查询的四种方法小结【转】 https://bbs.huaweicloud.com/forum/thread-02121147060465820020-1-1.html  15.MySql查询中按多个字段排序的方法【转】 https://bbs.huaweicloud.com/forum/thread-0240147060411844022-1-1.html  16. Devops-01-devops 是什么?【转】 https://bbs.huaweicloud.com/forum/thread-02127146828359305034-1-1.html  17.使用 Java 在Excel中创建下拉列表【转】 https://bbs.huaweicloud.com/forum/thread-0297146827870477028-1-1.html  18.管理与控制平面设计 https://bbs.huaweicloud.com/forum/thread-0239146030318575010-1-1.html  19.cisco https://bbs.huaweicloud.com/forum/thread-0292146030276181005-1-1.html  20. NSX-V整体架构 https://bbs.huaweicloud.com/forum/thread-02127146028999196007-1-1.html  21.从NVP到NSX https://bbs.huaweicloud.com/forum/thread-0279146028887825006-1-1.html  22.【监控】spring actuator源码速读-转载 https://bbs.huaweicloud.com/forum/thread-0239145954118788007-1-1.html  23.SpringCloud-RabbitMQ消息模型-转载 https://bbs.huaweicloud.com/forum/thread-0282145954036229004-1-1.html  24.【Golang入门教程】Go语言变量的声明-转载 https://bbs.huaweicloud.com/forum/thread-0279145953985911003-1-1.html  25. Spring Boot 3核心技术与最佳实践-转载 https://bbs.huaweicloud.com/forum/thread-0239145953918959006-1-1.html 
  • [分享交流] java22发布了,你们公司现在都在用哪个版本开发呢?
    java22发布了,你们公司现在都在用哪个版本开发呢?
  • [技术干货] IntelliJ IDEA 常用快捷键一览表
    1-IDEA的日常快捷键 第1组:通用型 说明    快捷键 复制代码-copy    ctrl + c 粘贴-paste    ctrl + v 剪切-cut    ctrl + x 撤销-undo    ctrl + z 反撤销-redo    ctrl + shift + z 保存-save all    ctrl + s 全选-select all    ctrl + a 第2组:提高编写速度(上) 说明    快捷键 智能提示-edit    alt + enter 提示代码模板-insert live template    ctrl+j 使用xx块环绕-surround with ...    ctrl+alt+t 调出生成getter/setter/构造器等结构-generate ...    alt+insert 自动生成返回值变量-introduce variable ...    ctrl+alt+v 复制指定行的代码-duplicate line or selection    ctrl+d 删除指定行的代码-delete line    ctrl+y 切换到下一行代码空位-start new line    shift + enter 切换到上一行代码空位-start new line before current    ctrl +alt+ enter 向上移动代码-move statement up    ctrl+shift+↑ 向下移动代码-move statement down    ctrl+shift+↓ 向上移动一行-move line up    alt+shift+↑ 向下移动一行-move line down    alt+shift+↓ 方法的形参列表提醒-parameter info    ctrl+p 第3组:提高编写速度(下) 说明    快捷键 批量修改指定的变量名、方法名、类名等-rename    shift+f6 抽取代码重构方法-extract method ...    ctrl+alt+m 重写父类的方法-override methods ...    ctrl+o 实现接口的方法-implements methods ...    ctrl+i 选中的结构的大小写的切换-toggle case    ctrl+shift+u 批量导包-optimize imports    ctrl+alt+o 第4组:类结构、查找和查看源码 说明    快捷键 如何查看源码-go to class...    ctrl + 选中指定的结构 或 ctrl+n 显示当前类结构,支持搜索指定的方法、属性等-file structure    ctrl+f12 退回到前一个编辑的页面-back    ctrl+alt+← 进入到下一个编辑的页面-forward    ctrl+alt+→ 打开的类文件之间切换-select previous/next tab    alt+←/→ 光标选中指定的类,查看继承树结构-Type Hierarchy    ctrl+h 查看方法文档-quick documentation    ctrl+q 类的UML关系图-show uml popup    ctrl+alt+u 定位某行-go to line/column    ctrl+g 回溯变量或方法的来源-go to implementation(s)    ctrl+alt+b 折叠方法实现-collapse all    ctrl+shift+ - 展开方法实现-expand all    ctrl+shift+ + 第5组:查找、替换与关闭 说明    快捷键 查找指定的结构    ctlr+f 快速查找:选中的Word快速定位到下一个-find next    ctrl+l 查找与替换-replace    ctrl+r 直接定位到当前行的首位-move caret to line start    home 直接定位到当前行的末位 -move caret to line end    end 查询当前元素在当前文件中的引用,然后按 F3 可以选择    ctrl+f7 全项目搜索文本-find in path ...    ctrl+shift+f 关闭当前窗口-close    ctrl+f4 第6组:调整格式 说明    快捷键 格式化代码-reformat code    ctrl+alt+l 使用单行注释-comment with line comment    ctrl + / 使用/取消多行注释-comment with block comment    ctrl + shift + / 选中数行,整体往后移动-tab    tab 选中数行,整体往前移动-prev tab    shift + tab 2-Debug快捷键 说明    快捷键 单步调试(不进入函数内部)- step over    F8 单步调试(进入函数内部)- step into    F7 强制单步调试(进入函数内部) - force step into    alt+shift+f7 选择要进入的函数 - smart step into    shift + F7 跳出函数 - step out    shift + F8 运行到断点 - run to cursor    alt + F9 继续执行,进入下一个断点或执行完程序 - resume program    F9 停止 - stop    Ctrl+F2 查看断点 - view breakpoints    Ctrl+Shift+F8 关闭 - close    Ctrl+F4 ————————————————           原文链接:https://blog.csdn.net/m0_59281987/article/details/137051712