爱分享 - Java
type: channel
link: http://gxxblw.com/category/java/
items: 同事看了想打人！这8个SQL写法让数据库慢到崩溃
items: Java的泛型应用与了解
items: Java 插件模式SPI学习与理解（Java SPI 、Spring SPI、Dubbo SPI）
items: JWT一文全解，禁止这样使用，被发现立即领盒饭！
items: Java基础面试题—— 包装类型和基本类型的区别是什么？
items: Java基础面试题 ——参数传递是按值还是按引用？
items: Java中String、StringBuffer 和 StringBuilder有什么区别？
items: 在国企和央企当程序员的不同体验，太真实了。。
items: 开发中常见的加密方式，对称加密,非对称加密,摘要签名算法的区别
items: Java中基本数据类型与对应的包装类型介绍

同事看了想打人！这8个SQL写法让数据库慢到崩溃
type: item
date: 2025-02-17T21:34:49+08:00
description: 坑点1：翻页越往后越卡当你用LIMIT 1000000,10查数据时，数据库就像翻一本100万页的书——明明只要第100万页的10行字，却得从第一页开始数。优化大招：记住上次看到哪儿。比如上次翻页最后一条的创建时间是"2023-01-01 12:00:00"，下页查询直接加条件WHERE create_time > '2023-01-01 12:00:00'，秒出结果。问题案例：-- 传统分页（偏移量越大性能越差） SELECT * FROM logs WHERE log_type='ERROR' ORDER BY create_time LIMIT 1000000, 10; -- 需遍历前100万条记录优化方案：-- 游标分页法（基于最后时间戳） SELECT * FROM logs WHERE log_type='ERROR' AND create_time > '2024-02-17 00:00:00' -- 使用上一页末尾时间 ORDER BY create_time LIMIT 10; -- 执行时间稳定在5ms内坑点2：乱填参数害死索引字段明明是varchar类型，程序却传了数字进来。这就好比用家门钥匙开汽车锁，数据库只能暴力拆锁全表扫描。检查方法：执行SHOW WARNINGS看到类型转换警告，赶紧让程序员改代码对齐类型。问题案例：-- 字段phone定义为VARCHAR(20)，但传入数值参数 SELECT * FROM users WHERE phone = 13800138000; -- 触发类型转换，索引失效 -- 查看执行计划警告 SHOW WARNINGS; > Warning: Cannot use ref access on index 'phone' due to type conversion 优化方案：-- 添加引号保持类型一致 SELECT * FROM users WHERE phone = '13800138000'; -- 正确使用索引坑点3：更新语句慢如蜗牛用UPDATE...WHERE id IN (子查询)更新数据？MySQL会傻乎乎地循环查几千次。救星方案：改成JOIN写法，让数据库一次性关联更新。实测有个案例从7秒降到2毫秒。问题案例：-- 错误写法（嵌套子查询） UPDATE orders o SET status = 'expired' WHERE o.id IN ( SELECT id FROM ( SELECT id FROM orders WHERE create_time < '2023-01-01' LIMIT 1000 ) tmp ); -- 执行时间>10秒优化方案：-- 改用JOIN重写 UPDATE orders o JOIN ( SELECT id FROM orders WHERE create_time < '2023-01-01' LIMIT 1000 ) tmp ON o.id = tmp.id SET status = 'expired'; -- 执行时间<100ms坑点4：混搭排序逼疯数据库 既要按评分升序，又要按时间降序，索引直接罢工。破解术：拆成两次查询再合并。比如先查未回复的订单按时间排序，再查已回复的，最后拼起来。问题案例：-- 混合排序导致全表扫描 SELECT * FROM product_reviews ORDER BY is_approved ASC, review_time DESC LIMIT 20; -- 无法使用(is_approved,review_time)索引优化方案：-- 拆分查询+UNION ALL (SELECT * FROM product_reviews WHERE is_approved=0 ORDER BY review_time DESC LIMIT 20) UNION ALL (SELECT * FROM product_reviews WHERE is_approved=1 ORDER BY review_time DESC LIMIT 20) ORDER BY is_approved ASC, review_time DESC LIMIT 20; -- 执行效率提升80倍坑点5：乱用SELECT * 拖垮网络动不动就SELECT *，把不要的字段也查出来。特别是text大字段，传输速度直接腰斩。保命原则：需要什么字段就写什么，别让数据库做快递小哥送垃圾。问题案例：-- 低效写法 SELECT * FROM employees e WHERE EXISTS ( SELECT 1 FROM salaries s WHERE s.emp_id = e.id AND s.year = 2023 …

Java的泛型应用与了解
type: item
date: 2025-01-15T09:55:03+08:00
description: Java泛型的功能主要是在编译阶段进行类型安全的检查，让开发者能够编写出更具通用性和灵活性的代码，有效避免在程序运行过程中出现类型转换的错误。泛型的作用：确保类型安全：泛型能够在编译时进行严格的类型检查，确保在操作集合或其他泛型类时，不会出现类型不一致的情况，从而降低运行时出现ClassCastException异常的风险。提升代码复用性：借助泛型，代码可以适用于多种不同的数据类型，减少了代码的重复编写，使得代码的可读性和可维护性得到提升。避免显式类型转换：通过在编译时明确指定类型参数，泛型省去了在运行时进行显式类型转换的步骤，简化了代码的编写。示例代码：List<String> stringList = new ArrayList<>(); stringList.add("Hello"); String message = stringList.get(0); // 无需进行类型转换泛型的实际应用场景集合框架中的应用：Java的集合框架大量运用了泛型。像List<T>、Set<T>以及Map<K, V>等接口，能够针对不同的数据类型实现统一的操作方式。泛型方法的定义：不仅可以定义泛型类，还可以定义泛型方法，这样可以使方法具备处理多种不同数据类型的能力。例如：public static <T> void printElements(T[] elements) { for (T element : elements) { System.out.println(element); } }为何需要泛型的通俗解释在Java 5版本之前，泛型是不存在的，那时的代码也能正常运行，那么为何要引入泛型呢，它能为我们带来哪些好处呢？先来看下面这段代码：List list = new ArrayList(); list.add("yes"); // 添加字符串 list.add(233); // 添加整数在没有泛型的时期，集合中添加的数据不会受到任何类型限制，全部被视为Object类型。或许有人会觉得这样很自由，确实，自由度很高，但是代码的约束性越弱，出错的概率就越高，在使用上也会带来诸多不便，比如在获取数据时需要进行强制类型转换。如果一不小心获取了错误的类型，虽然代码能够通过编译，但在运行时却会抛出异常。综上所述，Java引入了泛型机制。泛型的作用就是增加了一层类型约束。有了这层约束，由于已经声明了类型，所以在编译阶段就能识别出不准确的类型元素。这样可以让错误提前被发现，避免在运行时才暴露出来。并且也不需要在代码中显式地进行强制类型转换，从下面的代码可以看出，可以直接获取到String类型的元素。我们再总结一下泛型的优势：提升了代码的可读性，能够一眼看出集合（或其他泛型类）所存储的数据类型能在编译阶段检查类型安全，增强程序的健壮性省去了显式类型转换的麻烦（实际上内部已经完成了类型转换）提高了代码的复用性，定义好的泛型可以让一个方法（或类）适用于所有类型（虽然以前使用Object也可以实现，但相对较为繁琐）为何有人说Java的泛型是伪泛型来看下面这段代码：可以看到，虽然声明的是一个String类型的集合，但通过反射的方式却能够成功地向集合中插入int类型的数据。这表明在运行时，泛型并没有起到任何作用！也就是说，在运行过程中，JVM无法获取到泛型的信息，也不会对其进行任何约束。可以认为Java的泛型仅在编译时有效，而在运行时则不存在泛型，这也是人们常说Java是伪泛型的原因。简而言之，Java的泛型仅在编译阶段发挥作用，而在JVM运行时则不存在泛型的概念。近期才哥整理出了一个可用于快速刷面试题的小程序，其中收录了常见面试题及其答案，涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型，感兴趣的扫下方的小程序码进行体验。

Java 插件模式SPI学习与理解（Java SPI 、Spring SPI、Dubbo SPI）
type: item
date: 2025-01-14T22:13:00+08:00
description: SPI 全称为 Service Provider Interface，是一种服务发现机制，其核心在于将接口实现类的全限定名配置在文件中，由服务加载器读取并加载实现类，从而可在运行时为接口动态替换实现类，为程序提供拓展功能。1. 重新阐述 Java SPI 示例定义接口和实现类：首先定义一个名为 Robot 的接口，包含 sayHello 方法。public interface Robot { void sayHello(); } 然后创建两个实现类 OptimusPrime 和 Bumblebee，它们都实现了 Robot 接口并实现了 sayHello 方法。 public class OptimusPrime implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Optimus Prime."); } } public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } } 配置文件：在 META-INF/services 文件夹下创建名为 org.apache.spi.Robot 的文件（这里假设接口全限定名为 org.apache.spi.Robot），文件内容包含实现类的全限定名： org.apache.spi.OptimusPrime org.apache.spi.Bumblebee 测试代码： public class JavaSPITest { @Test public void sayHello() throws Exception { ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class); System.out.println("Java SPI"); // 两种遍历模式 serviceLoader.forEach(Robot::sayHello); Iterator<Robot> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { Robot robot = iterator.next(); // 可以调用 robot.sayHello() 进行测试 } } } 2. 经典 Java SPI 应用：JDBC DriverManager传统 JDBC 驱动加载：在 JDBC4.0 之前，需要先使用 Class.forName("com.mysql.jdbc.Driver") 加载数据库驱动，然后进行连接操作。 // STEP 1: Register JDBC driver Class.forName("com.mysql.jdbc.Driver"); // STEP 2: Open a connection String url = "jdbc:xxxx://xxxx:xxxx/xxxx"; Connection conn = DriverManager.getConnection(url,username,password); 使用 SPI 后的 JDBC 驱动加载：JDBC4.0 之后利用 Java 的 SPI 扩展机制，无需手动调用 Class.forName 加载驱动，可直接获取连接。DriverManager 类是驱动管理器，在其静态初始化块中调用 loadInitialDrivers 方法，其中涉及 SPI 的使用： static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } 加载驱动的四个步骤：从系统变量获取驱动定义。用 SPI 获取驱动实现类（字符串形式）。ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); 遍历使用 SPI 获取的实现，实例化各实现类。 Iterator<Driver> driversIterator = loadedDrivers.iterator(); while(driversIterator.hasNext()) { driversIterator.next(); } 根据第一步的驱动列表实例化具体实现类。3. Java SPI 机制源码解析ServiceLoader 类的 load 方法：public static Servi…

JWT一文全解，禁止这样使用，被发现立即领盒饭！
type: item
date: 2024-12-24T21:56:49+08:00
description: 在当今的网络开发领域，JSON Web Tokens（JWT）被广泛提及和应用，但对于其在存储 Session 方面的适用性，却存在诸多误解。本文旨在深入探讨为何 JWT 并不适合担当存储 Session 的重任，并揭示其可能引发的安全问题，以期帮助开发者们更全面、准确地理解和运用 JWT。一、术语明确在展开讨论之前，先对一些关键术语进行清晰的界定，这有助于避免后续的混淆和歧义：无状态 JWT（Stateless JWT）：这类 JWT Token 直接将 Session 数据编码于其中，所有与 Session 相关的信息都被封装在 Token 内部。有状态 JWT（Stateful JWT）：此类型的 JWT Token 仅包含对 Session 的引用或者 Session ID，实际的 Session 数据则存放在服务端。Session token（亦称 Session cookie）：这是一种标准的、可被签名的 Session ID 形式，许多 Web 框架（如 Laravel）长期以来都采用这种成熟的 Session 机制，其 Session 数据同样是存储在服务端。需要着重强调的是，本文的目的并非倡导“绝对不使用 JWT”，而是要明确指出 JWT 在作为 Session 机制时存在的弊端和风险。实际上，JWT 在其他特定场景下仍具有其独特的价值和用途，在本文结尾部分，将会简要介绍一些 JWT 的合理应用场景。二、对比对象的澄清常常有人错误地将 Cookies 和 JWT 进行不恰当的比较，这就如同将内存和硬盘简单对比一样，缺乏实际意义。Cookies 本质上是一种存储机制，而 JWT Tokens 则是经过加密和签名处理的令牌，二者并非相互对立的关系，相反，它们既可以独立使用，也能够结合运用。正确的比较方式应该是：Session 与 JWT 相对比，以及 Cookies 与 Local Storage 相对比。在后续的讨论中，本文将主要围绕 JWT Tokens 和 Session 的对比展开，并在必要时涉及 Cookie 和 Local Storage 的比较，这样的对比方式更能准确地反映实际情况和问题本质。三、JWT 被广泛宣扬的优势剖析（一）易于水平扩展？在众多关于 JWT 的宣传中，易于水平扩展被列为其优势之一。从技术层面来看，这一说法在使用无状态 JWT Tokens 时确实存在一定的合理性，但前提是要有相应的应用场景需求。然而，在实际情况中，绝大多数项目几乎并不需要这种高度的横向扩展能力。在常见的系统扩展场景中，存在许多更为简便易行的解决方案。例如：当需要在单台服务器上运行多个后端进程时，只需在该服务器上部署 Redis 服务来存储 Session 即可，这种方式简单高效，能够满足大部分常规业务的需求。对于运行多台服务器的情况，配备一台专门的 Redis 服务器用于集中存储 Session 数据，就能轻松实现 Session 的共享和管理，确保系统的稳定运行。在更为复杂的多集群环境中，通过会话保持（即粘滞会话）技术，也能够很好地应对 Session 的管理问题，而且这些技术在现有的软件系统架构中都得到了良好的支持，通常不需要应用层进行特殊的复杂处理。或许有人会考虑为未来的业务发展预留更多的调整空间，担心以后可能会需要无状态的会话机制。但从实际经验来看，后期对 Session 机制进行替换并非难事，唯一可能产生的影响是在迁移过程中所有用户需要重新登录一次。因此，综合考虑 JWT 可能带来的负面影响，在项目前期并没有必要为了一个不太确定的未来需求而强行采用 JWT 来实现 Session 管理。另外，对于近期有面试跳槽计划的开发者，建议在[Java面试库]()小程序上进行在线刷题，该小程序涵盖了 3000 余道 Java 面试题，几乎囊括了所有主流技术面试题，能够帮助开发者更好地准备面试，提升自身竞争力。（二）易于使用？实际上，JWT 在使用便利性方面并没有优势。使用 JWT 时，无论是在客户端还是服务端，都需要开发者自行处理 Session 的管理逻辑。相比之下，标准的 Session cookies 则具有开箱即用的特点，许多 Web 框架都对其进行了良好的封装和支持，开发者无需过多关注底层的实现细节，即可轻松使用 Session 功能。而 JWT 目前尚未被广泛集成到各种开箱即用的框架中，这就意味着研发人员需要投入更多的精力来处理与 JWT 相关的复杂逻辑，从这个角度来看，JWT 并不比传统的 Session 机制更易于使用。（三）更加灵活？关于 JWT 更加灵活这一说法，目前并没有确凿的证据和清晰的阐述来支撑。在实际应用中，几乎每个主流的 Session 实现都允许开发者直接将数据存储到 Sessio…

Java基础面试题—— 包装类型和基本类型的区别是什么？
type: item
date: 2024-12-11T22:59:36+08:00
description: 面试回答重点基本类型 ：Java 中有 8 种基本数据类型（int、long、float、double、char、byte、boolean、short），它们是直接存储数值的变量，位于栈上（局部变量），性能较高，且不支持 null。包装类型 ：每个基本类型都有一个对应的包装类型（Integer、Long、Float、Double、Character、Byte、Boolean、Short）。包装类型是类，存储在堆中，可以用于面向对象编程，并且支持 null。区别总结：（1）性能区别：基本类型：占用内存小，效率高，适合频繁使用的简单操作。包装类型：因为是对象，涉及内存分配和垃圾回收，性能相对较低。（2）比较方式不同：基本类型 ：比较用 ==，直接比较数值。包装类型 ：比较时，== 比较的是对象的内存地址，而 equals() 比较的是对象的值。（3）默认值不同：基本类型：默认值是 0，false 等。包装类型：默认为 null。（4）初始化的方式不同：基本类型：直接赋值。包装类型：需要采用 new 的方式创建。（5）存储方式不同：基本类型：如果是局部变量则保存在栈上面，如果是成员变量则在堆中。包装类型：保存在堆上（成员变量，在不考虑 JIT 优化的栈上分配时，都是随着对象一起保存在堆上的）。扩展知识自动装箱与拆箱Java 是一种面向对象语言，很多地方都需要使用对象而不是基本数据类型。比如，在集合类中，我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是 Object类型。为了让基本类型也具有对象的特征，就出现了包装类型，它相当于将基本类型"包装起来"，使得它具有了对象的性质，并且为其添加了属性和方法，丰富了基本类型的操作。装箱：基本类型自动转换为包装类型对象。拆箱：包装类型对象自动转换为基本类型的值。缓存机制包装类型中的 Byte、Short、Integer 和 Long 对某些范围内的值（例如 Integer 缓存 -128 到 127）会使用对象缓存来提升性能。因此，同一数值的包装类型对象可能是同一个实例。例如： Integer a = 100; Integer b = 100; System.out.println(a == b); // true Integer c = 200; Integer d = 200; System.out.println(c == d); // false 基础类型与包装类长度和范围分类基本数据类型包装类长度表示范围布尔型booleanBoolean// byteByte1 字节-128 ~ 127 shortShort2 字节-32768 ~ 32767整型intInteger4 字节-2,147,483,648 ~ 2,147,483,647 longLong8 字节-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807字符型charCharacter2 字节Unicode 字符集中的任何字符浮点型floatFloat4 字节约 -3.4E38 ~ 3.4E38 doubleDouble8 字节约 -1.7E308 ~ 1.7E308另外，近期才哥整理出了一个可用于快速刷面试题的小程序，其中收录了常见面试题及其答案，涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型。可以看下方小程序试试

Java基础面试题 ——参数传递是按值还是按引用？
type: item
date: 2024-12-10T08:48:00+08:00
description: 回答重点在 Java 中，参数传递只有按值传递，不论是基本类型还是引用类型。基本数据类型 （如 int, char, boolean 等）：传递的是值的副本，即基本类型的数值本身。因此，对方法参数的任何修改都不会影响原始变量。引用数据类型（如对象引用）：传递的是引用的副本，即对象引用的内存地址。因此，方法内可以通过引用修改对象的属性，但不能改变引用本身，使其指向另一个对象。扩展知识基本类型与引用类型的区别基本类型 ：包括 int, float, double, char, boolean 等，存储在栈内存中。方法中对基本类型参数的操作只会影响传递的副本，原始变量的值不受影响。引用类型 ：包括所有的对象和数组，引用类型的变量存储的是对象在堆内存中的地址。当引用类型作为参数传递时，传递的是这个地址的副本。因此，方法内的修改可以影响到传入的对象的内容，但不会影响对象引用本身的地址。示例代码分析 public class ParameterPassing { public static void main(String[] args) { int a = 5; modifyPrimitive(a); System.out.println("After modifyPrimitive: " + a); // 输出: 5 MyObject obj = new MyObject(); obj.value = 10; modifyObject(obj); System.out.println("After modifyObject: " + obj.value); // 输出: 20 resetReference(obj); System.out.println("After resetReference: " + obj.value); // 输出: 20 } public static void modifyPrimitive(int num) { num = 10; // 仅仅修改了副本，不影响原始变量 } public static void modifyObject(MyObject obj) { obj.value = 20; // 修改了对象的属性，会影响原始对象 } public static void resetReference(MyObject obj) { obj = new MyObject(); // 修改的是引用的副本，不影响原始对象 obj.value = 30; } } class MyObject { int value; } 在 modifyPrimitive 方法中，num 是基本类型的副本，因此对它的修改不影响原始变量 a。在 modifyObject 方法中，obj 是引用类型的副本，但这个副本仍指向原始对象，因此修改 value 属性会影响原始对象。在 resetReference 方法中，obj 被重新赋值为一个新对象，这个变化只影响副本，不影响原始引用。另外，近期才哥整理出了一个可用于快速刷面试题的小程序，其中收录了常见面试题及其答案，涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型，感兴趣的微信扫码体验

Java中String、StringBuffer 和 StringBuilder有什么区别？
type: item
date: 2024-12-09T15:45:12+08:00
description: 面试回答重点这三个都是 Java 中处理字符串的类，区别主要体现在可变性 、线程安全性 和性能上一、各自特征（1）String不可变 ：String 是不可变类，字符串一旦创建，其内容无法更改。每次对 String 进行修改操作（如拼接、截取等），都会创建新的 String 对象。适合场景 ：String 适用于字符串内容不会频繁变化的场景，例如少量的字符串拼接操作或字符串常量。（2）StringBuffer可变 ：StringBuffer 是可变的，可以进行字符串的追加、删除、插入等操作。线程安全 ：StringBuffer 是线程安全的，内部使用了 synchronized 关键字来保证多线程环境下的安全性。适合场景 ：StringBuffer 适用于在多线程环境中需要频繁修改字符串的场景。（3）StringBuilder可变 ：StringBuilder 也是可变的，提供了与 StringBuffer 类似的操作接口。非线程安全 ：StringBuilder 不保证线程安全，性能比 StringBuffer 更高。适合场景 ：StringBuilder 适用于单线程环境中需要大量修改字符串的场景，如高频拼接操作。二、总结String：不可变，适合少量字符串操作。StringBuffer：可变且线程安全，适合多线程环境中的频繁字符串修改。StringBuilder：可变且非线程安全，适合单线程环境中的高性能字符串处理。扩展知识在 Java 8 及以后，编译器会对字符串的常量拼接做优化，将字符串拼接转换为 StringBuilder 操作。这种优化提高了代码性能，但是在动态拼接或多线程场景下，手动使用 StringBuilder 和 StringBuffer 仍然更合适。String 是 Java 中基础且重要的类，并且 String 也是 Immutable 类的典型实现，被声明为 final class，除了 hash 这个属性其它属性都声明为 final。因为它的不可变性，所以例如拼接字符串时候会产生很多无用的中间对象，如果频繁的进行这样的操作对性能有所影响。StringBuffer 就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类 ，提供 append 和 insert 方法，可以将字符串添加到已有序列的末尾或指定位置。它的本质是一个线程安全的可修改的字符序列，把所有修改数据的方法都加上了 synchronized。但是保证了线程安全是需要性能的代价的。在很多情况下我们的字符串拼接操作不需要线程安全，这时候StringBuilder登场了，StringBuilder是JDK1.5发布的，它和 StringBuffer 本质上没什么区别，就是去掉了保证线程安全的那部分，减少了开销。所以如果我们有大量的字符串拼接，如果能预知大小的话最好在 new StringBuffer 或者 StringBuilder 的时候设置好 capacity，避免多次扩容的开销（扩容要抛弃原有数组，还要进行数组拷贝创建新的数组）。另外，近期才哥整理出了一个可用于快速刷面试题的小程序，其中收录了常见面试题及其答案，涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型，感兴趣的可以点击下方扫码试试

在国企和央企当程序员的不同体验，太真实了。。
type: item
date: 2024-11-30T22:49:00+08:00
description: 已经年底了，明年过了今年明年求职季来临，一份来自某垄断央企研发工程师岗位的工作经历分享，或许能为正在寻觅工作机会的小伙伴们提供一些有益参考。一、入职央企研发岗位初印象校招有幸踏入这家央企，开启研发工程师之旅。起初，怀揣着对技术研发的热情与憧憬，但很快便发现国企研发工作与想象中存在差异。在国企体系里，开发岗位处于相对底层，与互联网企业不同，这里更多遵循领导决策，缺乏像互联网中程序员与产品经理平等讨论的氛围，即便领导决策的需求不合理或可能引发诸多后续问题，作为开发人员，首要任务仍是坚决执行，努力达成领导交付的任务指标。二、国企开发工作日常剖析（一）大量内部项目入职初期，面临基于国产编辑器的培训任务。集团领导受市场上 Eclipse、Idea 等编辑器启发，决定打造内部专属编辑器，并要求所有项目强制基于此开展。这导致开发过程繁琐，常常需在项目中整合大量其他项目插件。原本基于 Eclipse 可轻松完成的任务，在国企环境下可能需在 2 - 3 个项目间跳转操作。由于国企项目以领导演示为导向，实用性并非首要考量，项目集成后犹如复杂的“X 山”。而且，集成的其他项目时常出现莫名错误，影响自身项目运行，可因项目或插件使用的强制性，开发人员只能无奈应对。（二）外包模式国企开发离不开外包团队，公司用工形式分为直聘与劳务派遣（外包）。直聘校招生享有公司统一编制，理论上可在系统内部调动，然而实际中 99.9%的普通员工难以实现。劳务派遣则多为社招或外包人员，在本公司项目中，干活主力往往是外包团队。起初我曾误判外包人员能力，实际工作中却发现部分外包人员技术执行力极强，多数项目实际控制权在外包手中，我们直聘人员更多负责管理资金拨付等事务，甚至在项目了解深度与颗粒度上不及外包。曾与一位近 40 岁、有腾讯、京东等互联网大厂工作经历的外包人员交流，他因身体原因转战国企，可见国企对于身体欠佳但有技术经验者亦是不错选择。（三）技术栈现状日常开发使用的技术相对陈旧，前端以JSP为主，后端是 Springboot系列。开发过程较少涉及多线程、高并发等前沿技术，多为基础的表设计与增删改查操作。对于技术追求不高的员工，可能每日仅需 2 - 3 小时即可完成工作任务。若有技术探索意愿，可利用剩余时间钻研新技术，自由度较高。这也意味着国企基层员工拥有较多可自主支配时间，可用于刷手机消遣，但若想自我提升，也可用于复盘学习新技术，毕竟在国企并非环境使人懈怠，而是个人态度决定发展走向。三、职业发展路径解读（一）升职空间分析央企研发岗位有技术与管理两种职级序列。管理序列晋升机会多倾向于有关系且有能力者，在国企中，人脉关系某种程度上可视为一种能力体现，若关系能助力解决公司问题，则更具优势。技术序列晋升多数情况下与工龄长短及 PPT 能力密切相关。国企研发工作深度有限，技术难度类似大专生在培训机构 3 个月所学成果。若想在技术序列向上发展，需着重锻炼 PPT 制作与表达能力，精准把握领导需求与关注点进行汇报展示。若志在技术钻研且有跳槽互联网打算，可在国企工作期间持续提升技术水平，否则需适应国企以管理和人际关系为主导的晋升模式。（二）薪资待遇构成公司作为成本单位，工资由总部发放，主要由工资 + 年终奖 + 福利组成。工资构成无绩效部分，取决于级别与职称，等级晋升主要依据工作年限，加班与否与工资增长无直接关联。年终奖由总部下发总包至公司，大领导分配至各部门，部门领导再根据个人工作情况分配到个人，部门内员工年终奖差距相对较小。福利方面，常见通信补助、房补、饭补及其他杂项福利，此为多数国企共有的福利模式。四、总结与感悟综合来看，在这家央企工作：其一，薪资待遇能保障一线城市基本生活开销，但购房压力仍较大。其二，开发技术更新滞后，工作以项目管理和执行领导决策为主。其三，若想在国企谋求领导职位，需着重提升 PPT 及沟通汇报能力，建立良好人际关系。若追求平淡稳定生活，可将更多时间留给家人，做一名普通员工亦无不可。国企工作犹如一把双刃剑，关键在于个人如何根据自身目标与追求，在这个环境中找准定位，实现自身价值与职业理想的平衡发展。 才哥最近开发了一个对于Java开发人员在线面试刷题的小程序，有需要的可以看看文章末尾，目前是免费阶段，也没有广告，才哥自己也在刷题备战明年黄金期，在线刷题很方便

开发中常见的加密方式，对称加密,非对称加密,摘要签名算法的区别
type: item
date: 2023-01-31T21:28:00+08:00
description: 目录目录对称与非对称区别对称加密非对称加密两种方式的优缺点摘要签名加密算法国密介绍加密一般分为两种，对称加密和非对称加密。对称与非对称区别对称加密对称加密就是加密解密都用同一个秘钥，比如DES、3DES（TripleDES）和AES等。非对称加密非对称加密就是加密和解密不是用的同一种秘钥，比如RSA算法、DSA算法、ECC算法、DH算法等。在非对称加密中，用来加密的秘钥叫公钥，用来解密的秘钥叫私钥。公钥和私钥都是成对生成的，公钥分发给其他人用来加密，私钥用来解密，典型的应用场景为支付宝开放平台的签名和数据加解密两种方式的优缺点对称加密：解密速度快，但保密性差。非对称加密：加密算法保密性好，它消除了最终用户交换密钥的需要。但是加解密速度要远远低于对称加密。摘要签名加密算法摘要算法是一种能产生特殊输出格式的算法，这种算法的特点是：无论用户输入什么长度的原始数据，经过计算后输出的密文都是固定长度的，这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取，这种提取就是摘要，被摘要的数据内容与原数据有密切联系，只要原数据稍有改变，输出的“摘要”便完全不同，因此，基于这种原理的算法便能对数据完整性提供较为健全的保障。但是，由于输出的密文是提取原数据经过处理的定长值，所以它已经不能还原为原数据，即消息摘要算法是不可逆的，理论上无法通过反向运算取得原数据内容，因此它通常只能被用来做数据完整性验证。常见的摘要算法有：MD5，SHA，国密SM3等，一般用于登录后的token生成等，数据加密后交易等，典型场景如微信支付签名验证国密介绍国密为国家密码局认定的国产密码算法，即商用密码。国密算法是国家密码局制定标准的一系列算法。其中包括了对称加密算法，椭圆曲线非对称加密算法，杂凑算法。具体包括SM1,SM2,SM3，SM4等，密钥长度和分组长度均为 128 位。SM1 为对称加密SM2 为非对称加密SM3 摘要签名SM4 对称加密,分组密码算法。

Java中基本数据类型与对应的包装类型介绍
type: item
date: 2023-01-16T22:47:00+08:00
description: 目录目录一. Java基本类型与包装类型介绍二 类型的自动装箱与拆箱举例说明一. Java基本类型与包装类型介绍Java中有8种基本数据类型，分别为：数字类型(6种) ：byte、short、int、long、float、double字符类型(1种) ：char布尔型(1种) ：boolean。这八种基本类型都有对应的包装类分别为：Byte、Short、Integer、Long、Float、DoubleCharacterBoolean基本类型位数字节默认值int3240short1620long6480Lbyte810char162'u0000'float3240fdouble6480dboolean1官方文档未明确定义，它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1位，但是实际中会考虑计算机高效存储因素。false二 类型的自动装箱与拆箱装箱与拆箱的含义：装箱：将基本类型用它们对应的引用类型包装起来；拆箱：将包装类型转换为基本数据类型；Java基本类型的包装类的大部分实现了常量池技术，即Byte,Short,Integer,Long,Character,Boolean；前面4种包装类默认创建了数值[-128，127]的相应类型的缓存数据，Character创建了数值在[0,127]范围的缓存数据，Boolean 直接返回True Or False。如果超出对应范围仍然会去创建新的对象。其中仅浮点数类型的包装类Float与Double 没有实现常量池技术。Boolean常量池public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); // 其中TRUE与FALSE为定义的静态常量 }CharacterCache 缓存常量池private static class CharacterCache { private CharacterCache(){} static final Character cache[] = new Character[127 + 1]; static { for (int i = 0; i < cache.length; i++) cache[i] = new Character((char)i); } }/** *此方法将始终缓存-128 到 127（包括端点）范围内的值，并可以缓存此范围之外的其他值。 */ public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) //如果在缓存范围值内 return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); // 不如不在范围内者使用new运算符创建一个堆内存返回新的对象 }举例说明例子1 Integer i1 = 33; Integer i2 = 33; System.out.println(i1 == i2);// 输出 true, 因为33在-128-127范围内使用的是缓存常量，即两个数值的内存地址一致 Integer i11 = 333; Integer i22 = 333; System.out.println(i11 == i22);// 输出 false // 超出范围，返回的都是new的新对象 Double i3 = 1.2; Double i4 = 1.2; System.out.println(i3 == i4);// 输出 false // Double未实现缓存常量池 例子2需要注意一旦使用new运算符创建对象，则将创建新的数据，不会使用缓存常量值，如以下Integer i1 = 40; Integer i2 = new Integer(40); System.out.println(i1==i2);//输出 falseInteger i1=40；Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);，从而使用常量池中的对象。Integer i1 = new Integer(40);这种情况下会创建新的对象。例子3Integer i1 = 40; Integer i2 = 40; Integer i3 = 0; Integer i4 = new Integer(40); Integer i5 = new Integer(40); Integer i6 = new Integer(0); System.out.println("i1=i2 : " + (i1 == i2)); // tru…