理解 Kubernetes 的 Configmap

安装说明 通过 docker desktop 可以安装适用于单机和开发环境单机版的 K8S,如果 docker desktop 无法启动 Kubernates 通过以下方式解决: 一:添加国内镜像源 为 Docker 的 daemon.json 添加配置: { "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://registry.docker-cn.com" ] } 二:通过脚本下载 Kubernetrs 所需要的镜像 在 GitHub 中的 k8s-for-docker-desktop 项目中下载 Kubernetes 版本对应的分支,然后执行脚本即可,启动完成后,验证 K8S 集群状态: $ kubectl cluster-info $ kubectl get nodes $ kubectl describe node 理解 Pod 先通过一个简单的示例理解 Pod,Pod 是 Kubernetes 中的基本部署单元,这里看看如何用 Pod 创建一个 nginx 服务。使用 kubectl 命令部署一个 nginx 的服务: $ kubectl create deployment nginx-arm --image=nginx 创建部署后,您可以使用以下命令检查 Pod 的状态: ...

November 16, 2023 · 3 min

大模型的 Prompt 使用技巧

概述 前段时间在 DeepLearning 学了一门 Prompt 的课程,感觉受益匪浅,因此在这里总结分享一下学习笔记,希望可以帮到大家。 为什么要学习 Prompt ? 因为在未来的 AIGC 年代,学习有效的 Promot 提示词有效的利用 AI 来完成一些重复性的工作。这也我认为未来每个人都必备的技能之一。 以下是我个人学完这门课程的总结: 更好的完成任务:试想一下,如果你给 AI 一个模糊的问题,那么你得到的只会是一个模糊的回答 多元化的结果:可以让 AI 更多维的结果,但不限于:代码,JSON,XML,HTML 等格式文本,甚至是图片,视频等 避开 AI 的局限:喜欢编造事实,这是目前 AI 已知的缺陷,但有效的 Prompt 可以帮助你有效的避开这个已知,但目前还无法解决的缺陷 不再迷信完美的 Prompt:了解真相后,你将不再迷信类似于 awesome-chatgpt-prompts-zh 各种所谓的魔法,速成的调教指南,因为不存在完美的 Prompt 了解 AI 的能力:目前大模型的能力局限在:摘要,推理,转换,扩展等能力上,目前的 AI 并非无所不能,不要过分神话,也不要过分贬低它 总而言之,学习 Prompt 提示词可以帮助您更好地与 LLM 模型进行交互,指导其生成符合您需求的文本,并提高效率和准确性。也推荐大家有时间可以看完完整的视频课程。我就不过多展开了。以下是我对课程的学习笔记。 第一章: Introduction 引言 第一章节,引言主要介绍和 ChatGPT 或类似的 LLM 交流时,要遵循的几个基本原则,如下: 明确的指令:清晰的指令会得到更准确的回复。例如,而不是问 “我应该吃什么?",你可以问 “我应该在素食饮食中添加哪些蛋白质来源 ?” 合理的期待:模型的知识储备和它的训练参数和训练方向有关,例如对于 ChatGPT 这样一个作为通用领域的大模型,对于一些特别复杂、需要深度专业知识,它是无法提供准确的答案的,特定领域的问题必须由特定领域的专用模型来解决。 验证结果:如上,对于特别复杂和专业的问题,AI 有时候会虚构信息,你必须对 AI 的回复进行验证,如果发现了错误,可以尝试用不同的方式提问。 等待 AI 思考的时间:AI 需要理解你的问题,并生成一个有用的响应,这可能需要一些时间,特别是对于复杂的问题。要有一点耐心 以上就是向第一章课程中包含的向 AI 提问的基本原则,希望对你有所帮助。 ...

June 5, 2023 · 2 min

Java 泛型的理解和应用

概述 泛型是一种将类型参数化的动态机制,使用得到的话,可以从以下的方面提升的你的程序: 安全性:使用泛型可以使代码更加安全可靠,因为泛型提供了编译时的类型检查,使得编译器能够在编译阶段捕捉到类型错误。通过在编译时检查类型一致性,可以避免在运行时出现类型转换错误和 ClassCastException 等异常。减少由于类型错误引发的bug。 复用和灵活性:泛型可以使用占位符 <T> 定义抽象和通用的对象,你可以在使用的时候再来决定具体的类型是什么,从而使得代码更具通用性和可重用性。 简化代码,增强可读性:可以减少类型转换的需求,简化代码,可以使代码更加清晰和易于理解。通过使用具有描述性的泛型类型参数,可以更准确地表达代码的意图,还可以避免使用原始类型或Object类型,从而提供更多的类型信息,使代码更加具有表达力 这就是泛型的概念,是 Java 后期的重大变化之一。泛型实现了参数化类型,可以适用于多种类型。泛型为 Java 的动态类型机制提供很好的补充,但是 Java 的泛型本质上是一种高级语法糖,也存在类型擦除导致的信息丢失等多种缺点,我们可以在本篇文章中深度探讨和分析。 简单的示例 泛型在 Java 的主要作用就是创建类型通用的集合类,我们创建一个容器类,然后通过三个示例来展示泛型的使用: 没有使用泛型的情况 使用 Object 类型作为容器对象 使用泛型作为容器对象 示例1:没有使用泛型的情况 public class IntList { private int[] arr; // 只能存储整数类型的数据 private int size; public IntList() { arr = new int[10]; size = 0; } public void add(int value) { arr[size++] = value; } public int get(int index) { return arr[index]; } public int size() { return size; } public static void main(String[] args) { IntList list = new IntList(); list.add(1); list.add(2); list.add(3); int value = list.get(1); // 需要显式进行类型转换 System.out.println(value); // 输出: 2 } } 在上述示例中,使用了一个明确的 int 类型存储整数的列表类 IntList,但是该类只能存储整数类型的数据。如果想要存储其他类型的数据,就需要编写类似的类,导致类的复用度较低。 ...

May 24, 2023 · 6 min

Java 反射的理解和应用

概述 反射(Reflection)机制是指在运行时动态地获取类的信息以及操作类的成员(字段、方法、构造函数等)的能力。通过反射,我们可以在编译时期未知具体类型的情况下,通过运行时的动态查找和调用。 虽然 Java 是静态的编译型语言,但是反射特性的加入,提供一种直接操作对象外的另一种方式,让 Java 具备的一些灵活性和动态性,我们可以通过本篇文章来详细了解它 为什么需要反射 ? Java 需要用到反射的主要原因包括以下几点: 运行时动态加载,创建类:Java中的类是在编译时加载的,但有时希望在运行时根据某些条件来动态加载和创建所需要类。反射就提供这种能力,这样的能力让程序可以更加的灵活,动态 动态的方法调用:根据反射获取的类和对象,动态调用类中的方法,这对于一些类增强框架(例如 Spring 的 AOP),还有安全框架(方法调用前进行权限验证),还有在业务代码中注入一些通用的业务逻辑(例如一些日志,等,动态调用的能力都非常有用 获取类的信息:通过反射,可以获取类的各种信息,如类名、父类、接口、字段、方法等。这使得我们可以在运行时检查类的属性和方法,并根据需要进行操作 一段示例代码 以下是一个简单的代码示例,展示基本的反射操作: import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) { // 假设在运行时需要调用某个类的方法,但该类在编译时未知 String className = "com.example.MyClass"; try { // 使用反射动态加载类 Class<?> clazz = Class.forName(className); // 使用反射获取指定方法 Method method = clazz.getMethod("myMethod"); // 使用反射创建对象 Object obj = clazz.newInstance(); // 使用反射调用方法 method.invoke(obj); } catch (ClassNotFoundException e) { System.out.println("类未找到:" + className); } catch (NoSuchMethodException e) { System.out.println("方法未找到"); } catch (IllegalAccessException | InstantiationException e) { System.out.println("无法实例化对象"); } catch (Exception e) { System.out.println("其他异常:" + e.getMessage()); } } } 在这个示例中,我们假设在编译时并不知道具体的类名和方法名,但在运行时需要根据动态情况来加载类、创建对象并调用方法。使用反射机制,我们可以通过字符串形式传递类名,使用 Class.forName() 动态加载类。然后,通过 getMethod() 方法获取指定的方法对象,使用 newInstance() 创建类的实例,最后通过 invoke() 方法调用方法。 ...

May 21, 2023 · 4 min

Java.nio.file 文件库的介绍和使用

概述 在早期的 Java 版本中,文件 IO 操作功能一直相对较弱,主要存在以下问题: 缺乏对现代文件系统的支持:只提供的基础的文件操作,不支持很多现代的文件系统 API 不够直观:文件操作的 API 设计相对较为复杂和冗长,使用体验感很差 对于大文件处理和并发性能不够:简单的 I/O 模型,没有充分利用现代硬件的性能优势,而且还有很多同步的问题 但 Java 在后期版本中引入了 java.nio.file 库来提高 Java 对文件操作的能力。还增加的流的功能,似乎使得文件变成更好用了。所以本章,我们就来主要介绍 java.nio.file 中常用的类和模块,大致如下: Path 路径:Paths 模块和 Path 工具类介绍 Files 文件:File 和 FileSystems 工具类介绍 文件管理服务:WatchService 、PathMatcher 等等文件服务 Path 路径 java.nio.file.Paths 和 java.nio.file.Path 类在 Java NIO 文件 I/O 框架中用于处理文件系统路径。以下是对它们的简单介绍: Paths 模块:Paths 模块提供了一些静态方法来创建 Path 对象,Path 对象表示文件系统中的路径。例如,可以使用 Paths.get() 方法创建一个 Path 对象,这个对象表示一个文件路径。 Path 类:Path 类代表一个文件系统中的路径,它提供了一系列的方法来操作文件路径。例如,可以使用 Path.toAbsolutePath() 方法获取一个绝对路径,或者使用 Path.getParent() 方法获取路径的父路径。 关于跨平台:Path 对象可以工作在不同操作系统的不同文件系统之上,它帮我们屏蔽了操作系统之间的差异 以下是一些简单使用场景示例: import java.nio.file.Path; import java.nio.file.Paths; public class PathExample { public static void main(String[] args) { // 创建一个绝对路径 Path absolutePath = Paths.get("C:\Users\phoenix\file.txt"); // 这里传入 "example\file.txt" 创建的相对路径 System.out.println("Absolute path: " + absolutePath); // 获取父路径 System.out.println("Parent path: " + absolutePath.getParent()); // 获取文件名 System.out.println("File name: " + absolutePath.getFileName()); // 获取根路径 System.out.println("Root path: " + absolutePath.getRoot()); // 合并路径 Path resolvePath = Paths.get("C:\Users\phoenix").resolve("file.txt"); System.out.println("Merged path:" + resolvePath); } } 输出结果: ...

May 9, 2023 · 4 min

提高代码质量的几条参考建议

单元测试 什么是单元测试 ? 单元测试通常是指对一个函数或方法测试。单元测试的目的是验证每个单元的行为是否符合预期,并且在修改代码时能够快速检测到任何潜在的问题。通过编写测试用例,我们可以验证这些模块在特定输入下是否产生正确的输出。单元测试的目的是确保每个模块在各种情况下都能正常运行。 写单元测试的好处 可以带来以下几个好处: 提高代码质量:单元测试可以我们提前的发现代码中的潜在问题,例如边界条件、异常情况等,从而减少出错的概率。 提高代码可维护性:单元测试可以帮助开发人员理解代码的功能和实现细节,从而更容易维护和修改代码。 提高代码可靠性:修改代码后,可以通过单元测试可以帮助开发人员验证代码的正确性,从而提高代码的可靠性。 写单元测试是一种良好的软件开发实践,可以提高代码质量、可维护性和可靠性,同时也可以提高开发效率和支持持续集成和持续交付。 单元测试入门 上手单元测试,通常同时从静态测试(Static Test)开始,因为它简单,好理解,静态测试(Static Test)是指在编写测试用例时,我们提前定义好所有的测试方法和测试数据。这些测试方法和数据在编译时就已经确定,不会在运行时发生变化。Junit 中的静态测试通常的常规注解,如 @Test、@Before、@After 等。先来看看一组简单的静态测试示例。 首先,确保你的 pom.xml 文件包含 JUnit 的依赖: <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.8.0</version> <scope>test</scope> </dependency> </dependencies> 然后,创建一个简单的计算器类,通常这里替换为你实际要测试的业务类: public class SimpleCalculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } } 然后在 /test 的相同目录下创建对应的测试类 import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.assertEquals; public class SimpleCalculatorTest { // 在所有测试方法执行前,仅执行一次。这个方法需要是静态的。 @BeforeAll static void setup() { System.out.println("BeforeAll - 初始化共享资源,例如数据库连接"); } // 在所有测试方法执行后,仅执行一次。这个方法需要是静态的。 @AfterAll static void tearDown() { System.out.println("AfterAll - 清理共享资源,例如关闭数据库连接"); } // 在每个测试方法执行前,都会执行一次。用于设置测试方法所需的初始状态。 @BeforeEach void init() { System.out.println("BeforeEach - 初始化测试实例所需的数据"); } // 在每个测试方法执行后,都会执行一次。用于清理测试方法使用的资源。 @AfterEach void cleanup() { System.out.println("AfterEach - 清理测试实例所用到的资源"); } // 标注一个测试方法,用于测试某个功能。 @Test void testAddition() { System.out.println("Test - 测试加法功能"); SimpleCalculator calculator = new SimpleCalculator(); assertEquals(5, calculator.add(2, 3), "2 + 3 应该等于 5"); } // 再添加一个测试方法 @Test void testSubtraction() { System.out.println("Test - 测试减法功能"); SimpleCalculator calculator = new SimpleCalculator(); assertEquals(1, calculator.subtract(3, 2), "3 - 2 应该等于 1"); } } 以上程序,可以看到 Junit 常用注解使用说明: ...

April 25, 2023 · 4 min

Redis Stream:更完善的消息队列

该数据结构需要 Redis 5.0.0 + 版本才可用使用 概述 Redis stream 是 Redis 5 引入的一种新的数据结构,它是一个高性能、高可靠性的消息队列,主要用于异步消息处理和流式数据处理。在此之前,想要使用 Redis 实现消息队列,通常可以使用例如:列表,有序集合、发布与订阅 3 种数据结构。但是 stream 相比它们具有以下的优势: 支持范围查找:内置的索引功能,可以通过索引来对消息进行范围查找 支持阻塞操作:避免低效的反复轮询查找消息 支持 ACK:可以通过确认机制来告知已经成功处理了消息,保证可靠性 支持多个消费者:多个消费者可以同时消费同一个流,Redis 会确保每个消费者都可以独立地消费流中的消息 话不多说,接下来具体看看如何使用它。(PS:万字长文,行驶途中请系好安全带) XADD 添加元素 XADD 命令的语法格式如下: XADD stream-name id field value [field value] stream-name: 指定 redis stream 的名字 id: 是指 stream 中的消息 ID,通常使用 * 号表示自动生成 field value: 就是消息的内容,是 K-V 格式的键值对 关于使用 XADD 添加元素,还有以下特点: 自动创建流:当 my-stream 流不存在时,redis 会自动创建,然后将元素追加在流的末尾处 任意键值对:流中的每个元素可以包含一个或任意多个键值对 下面是一个使用 XADD 命令添加新消息的示例: XADD my-stream * name John age 30 email john@example.com 上述命令的说明: ...

April 17, 2023 · 8 min

Java 异常处理的使用和思考

概念 异常处理的概念起源于早期的编程语言,如 LISP、PL/I 和 CLU。这些编程语言首次引入了异常处理机制,以便在程序执行过程中检测和处理错误情况。异常处理机制随后在 Ada、Modula-3、C++、Python、Java 等编程语言中得到了广泛采用和发展。在 Java 中,异常处理是提供一种在程序运行时处理错误和异常情况的方法。异常处理机制使得程序能够在遇到错误时继续执行,而不是立即崩溃。这种机制使程序更具有健壮性和容错性。异常分为两类:受检异常(Checked Exceptions)和非受检异常(Unchecked Exceptions) 受检异常(Checked Exceptions): 受检异常是指那些在编译时必须处理的异常。它们通常是由程序员的错误或外部资源问题引起的。例如,IOException、FileNotFoundException 等。受检异常必须在方法签名中使用 throws 关键字声明,或者在方法体内用 try-catch 块捕获和处理。 非受检异常(Unchecked Exceptions): 非受检异常是指那些在编译时不强制要求处理的异常。它们通常是由编程错误引起的,如空指针异常(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。非受检异常继承自 java.lang.RuntimeException 类,不需要在方法签名中声明,也不需要强制捕获和处理。 它们的关系如下: 异常处理 Java 使用 try/catch 关键字进行异常捕获,使用 throw 声明抛出异常,示例代码如下: public class NullPointerExceptionExample { public static void main(String[] args) { String nullString = null; try { int length = nullString.length(); } catch (NullPointerException e) { System.out.println("Caught NullPointerException!"); e.printStackTrace(); } } } 在这个示例中,我们尝试获取一个 null 字符串的长度。当调用 nullString.length() 时,会抛出 NullPointerException。我们使用 try-catch 语句捕获异常并处理它 自定义异常 Java 官方异常不可能预见所有可能发生的错误,有时候你需要结合自己的业务场景,例如以下场景: ...

April 9, 2023 · 6 min

Java Stream 流式编程的介绍

概述 Stream API 是 Java 中引入的一种新的数据处理方法。它提供了一种高效且易于使用的方法来处理数据集合。Stream API 支持函数式编程,可以让我们以简洁、优雅的方式进行数据操作,还有使用 Stream 的两大原因: 在大多数情况下,将对象存储在集合中就是为了处理它们,因此你会发现你把编程 的主要焦点从集合转移到了流上。 当 Lambda 表达式和方法引用(method references),流(Stream)结合使用的时候会让人感觉自成一体,行云流水的感觉 先展示一段简单的流式编程: import java.util.Random; public class Randoms { public static void main(String[] args) { // 随机展示 5 至 20 之间不重复的整数并进行排序 new Random(47) .ints(5, 20) .distinct() // 使流中的整数不重复 .limit(7) // 获取前 7 个元素 .sorted() // 排序 .forEach(System.out::println); } } 输出结果: 6 10 13 16 17 18 19 实际上函数式的编程风格是声明式(Declarative programming)的,它声明了要做什么, 而不是指明(每一步)如何做。 相同的程序,相比声明式风格,命令式(Imperative)编程的形式(指明每一步如何做),代码阅读起来会更难理解: import java.util.Random; import java.util.SortedSet; import java.util.TreeSet; public class ImperativeRandoms { public static void main(String[] args) { Random rand = new Random(47); SortedSet<Integer> rints = new TreeSet<>(); while (rints.size() < 7) { int r = rand.nextInt(20); if (r < 5) continue; rints.add(r); } System.out.println(rints); } } 输出结果: ...

April 6, 2023 · 12 min

Java Lambda 函数式编程方法介绍

概述 背景 函数式编程的理论基础是阿隆佐·丘奇(Alonzo Church)于 1930 年代提出的 λ 演算(Lambda Calculus)。λ 演算是一种形式系统,用于研究函数定义、函数应用和递归。它为计算理论和计算机科学的发展奠定了基础。随着 Haskell(1990年)和 Erlang(1986年)等新一代函数式编程语言的诞生,函数式编程开始在实际应用中发挥作用。 函数式的价值 随着硬件越来越便宜,程序的规模和复杂性都在呈线性的增长。这一切都让编程工作变得困难重重。我们想方设法使代码更加一致和易懂。我们急需一种 语法优雅,简洁健壮,高并发,易于测试和调试 的编程方式,这一切恰恰就是 函数式编程(FP) 的意义所在。 函数式语言已经产生了优雅的语法,这些语法对于非函数式语言也适用。 例如:如今 Python,Java 8 都在吸收 FP 的思想,并且将其融入其中,你也可以这样想: OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数 式编程)是抽象行为。 新旧对比 用传统形式和 Java 8 的方法引用、Lambda 表达式分别演示。代码示例: interface Strategy { String approach(String msg); } class Soft implements Strategy { public String approach(String msg) { return msg.toLowerCase() + "?"; } } class Unrelated { static String twice(String msg) { return msg + " " + msg; } } public class Strategize { Strategy strategy; String msg; Strategize(String msg) { strategy = new Soft(); // [1] 构建默认的 Soft this.msg = msg; } void communicate() { System.out.println(strategy.approach(msg)); } void changeStrategy(Strategy strategy) { this.strategy = strategy; } public static void main(String[] args) { Strategy[] strategies = { new Strategy() { // [2] Java 8 以前的匿名内部类 public String approach(String msg) { return msg.toUpperCase() + "!"; } }, msg -> msg.substring(0, 5), // [3] 基于 Ldmbda 表达式,实例化 interface Unrelated::twice // [4] 基于 方法引用,实例化 interface }; Strategize s = new Strategize("Hello there"); s.communicate(); for(Strategy newStrategy : strategies) { s.changeStrategy(newStrategy); // [5] 使用默认的 Soft 策略 s.communicate(); // [6] 每次调用 communicate() 都会产生不同的行为 } } } 输出结果: ...

April 2, 2023 · 12 min