DI实现中构造器注入为什么在单构造器时可省略 @Autowired

三种实现方式(属性注入、构造器注入、setter注入)中的构造器注入,为什么只有一个构造器时,可以省略@Autowired? 这种情况还需要一个大前提:该类也被标注为了归spring创建并管理,将会成为Bean对象存放在容器里。(无论是什么方式标注的@component/@Bean 等都可以) 在该类B归Spring管的这个大前提下,作为类创建的关键——“构造函数”,如果它要求一个A类的参数传入,而A类也是归Spring管,已经注册过了,那么Spring就会自动进行依赖注入DI。 而为什么需要“只有一个构造器”作为前提:因为只有一个有参构造器时,Spring 会自动把它当作注入点,没有注入点歧义。Spring知道你想用这个构造器去创建对象,它就可以为这个构造器里的参数注入对象 为什么另外两种实现方式都得加?Spring会自动管类的创建,但内部需要自己去指定,告诉spring这个地方需要帮我注入对象。字段/Setter 并不是创建对象必须步骤,如可能只是声明个引用型变量作为字段,并不想要对象。

2026-01-29 · 1 min · 8 words · Lou Feiyu

回顾明确:CPU运算、对象读取、栈与堆的CPU性能

对一个字段+1 取到这个字段的内存地址 把地址对应的数据load到寄存器「先拿着地址在cache里找,如果cache miss,先把其内存中所在的cache line拉到cache中」 寄存器中作运算 结果store回去(一般先写入cache,再择机写回内存) 对象的读取 以 obj.x 为例(obj 是一个引用,x 是字段) obj 这个“引用值”(可以理解为一个地址或压缩地址)通常在: 栈帧的局部变量里,或 寄存器里(JIT 优化后很常见),或 被优化掉(逃逸分析后甚至不真正分配对象) 没被优化掉时,访问路径 先拿到obj的引用值(寄存器/从栈里load) 根据引用值定位到堆上的对象起始地址 按偏移值读取字段。对象地址+字段偏移量->该字段的最终内存地址 先尝试从 L1/L2/L3 cache 命中这条地址所在的 cache line;没命中才去内存 计算,把结果store回去 栈 vs 堆 局部变量在栈 对象在堆 从CPU性能角度: 栈内存通常更连续、更局部(同一线程栈帧相邻,缓存友好) 堆对象分配与回收导致分布更碎片化(尤其大量短命对象),更容易出现 cache miss 但 HotSpot/JIT 会尽量通过 TLAB 分配、分代收集等机制改善局部性;只是你不能指望它总能把链表节点变成连续存储 慢的根源:慢的根源是访问堆对象时形成的随机访问模式,而不是因为对象在堆上 JIT 会把很多“内存访问”变成“寄存器里的值” 在 Java 里,运行时(HotSpot C2 等)会做大量优化,导致你“以为每次都在堆上读写”的东西,实际可能不是: 标量替换(Scalar Replacement)/ 逃逸分析:对象不逃逸时可能不分配在堆上,字段直接拆成几个寄存器或栈上的标量 循环优化:把重复读取的字段提升到寄存器里(类似 C/C++ 的常见优化) 消除边界检查:ArrayList/数组循环中常见 这也解释了一个现象:同样是“读对象字段”,有时非常快(被优化到寄存器),有时非常慢(真实堆访问 + cache miss)。

2026-01-26 · 1 min · 64 words · Lou Feiyu

注解在体系中存在的位置

接上文“反射中的Class对象、类加载、对象、堆内存、JVM内部类元数据的关系”,那么注解在这个体系中处于什么位置?注解本质是什么?ChatGPT 5.1 Thinking 先把结论丢给你,方便挂在你现在那张“大图”上: 注解 = 写在源码上的“带类型的标签(配置)”, 编译后变成:存放在“类元数据里的额外一块结构”。 运行时通过 Class/Method/Field 等反射入口把这块结构读出来。 所以,如果把你文档里的那张关系图再扩一层: 堆上普通对象 └─ 对象头: 类型指针 ─────> JVM 内部类元数据 (方法表、字段布局、常量池、注解信息...) └─ 映射 ─────> 堆上的 java.lang.Class 对象 └─ 反射 API 读到注解 注解就是“类元数据里的一部分信息”,跟“字段信息、方法信息”是同一层级的那种东西。 一、先回答“注解本质是什么?” 分 3 个层次说: 1. 语言层面(你写代码时看到的) @MyAnno(role = "admin") public class User { ... } public @interface MyAnno { String role(); } 对你来说,注解就是: 跟着类/方法/字段/参数/类型一起写的**“标签 + 配置”** 语法上跟 @Something(...) 这种形式出现 本身不干任何事情,只是“写在那儿” 它什么时候“有用”? 完全取决于: 编译器 / 工具(比如 Lombok、MapStruct)看到它,决定生成什么代码 框架(Spring、JPA、JUnit…)在运行时用反射读它,决定怎么处理这个类/方法 所以——注解不是魔法,它只是被别人约好要看的“标记 + 参数”。 2. 编译后的物理形态:class 文件里的一段“注解属性” 编译器看到你写的 @MyAnno(...),会把这东西塞进 class 文件的某些 Attribute 里,比如: ...

2026-01-21 · 2 min · 327 words · Lou Feiyu

反射中的Class对象、类加载、对象、堆内存、JVM内部类元数据的关系

引子 对象是“类型系统+运行时实体”。运行时实体存在于堆内存中,那么类型系统物理上是怎么实现这个概念? 发射第一步拿的Class对象到底是个什么? 明确运行时类型概念 都是运行时概念下,JVM拉起之后 并不是编译时类型,是运行时类型,是真正属于的具体类。「注意多态」 物理上怎么实现“运行时类型”这个概念? JVM必须能在给定一个对象引用时,快速得到“这个对象属于哪个类” 因此对象在内存中会存在一个对象头,包含有指向“类元数据”的指针/引用 大多数JVM 实现(HotSpot 也是这样思路)都会把对象内存分成两块: 对象头(object header) 实例数据(fields / array elements) 对象头中通常包含两类信息 同步/GC/哈希相关的运行时信息(例如锁、hashcode、GC 标记等) 类型指针:指向“类元数据”的指针(HotSpot 里常被描述为指向 Klass 元数据的指针) 所以obj.getClass() 的底层本质:读取对象头里的“类型指针”,再把它映射到 java.lang.Class 对象返回。「Class对象是一个对象,在堆内存里,与内部元信息同阶段产生」 因此说java.lang.Class 对象是 Java 层可见的“句柄/门面”(a handle/facade) 指针指向的“类元数据”是什么?放在哪里? 对象头的类型指针会指向JVM 内部的“类元数据结构”(概念上可以叫它 klass metadata)。里面包含有 方法表(用于虚调用/动态分派) 字段布局(字段偏移量、类型) 常量池引用 父类/接口信息 访问标志、注解信息(或其索引) 以及许多用于 JIT、验证、解析的结构 关系图: ...

2026-01-20 · (updated 2026-01-29) · 1 min · 142 words · Lou Feiyu

到底什么是java中的「对象」本质

在 Java 语境里,通常说“对象”,指的是: 运行时存在的一块堆内存实体,它的运行时类型是某个引用类型(reference type),并且可以用引用变量指向。 注意:“对象”的判定依据是类型系统 + 运行时实体 而“某个引用类型”的最上层就是 java.lang.Object。所以有一个非常硬的事实: 只要一个东西的运行时类型是 Object 的子类,那么它就是一个对象。 对象是运行时实体; 引用类型实例就是对象; 所有引用类型都继承自 Object(除非是接口/基本类型等),因此它们的实例都是对象。

2026-01-19 · (updated 2026-01-20) · 1 min · 17 words · Lou Feiyu

从 “单文件运行java Foo.java”说起:ClassLoader 与 Java 的“类型身份

Java 21/22 之后,java Foo.java 这种“单文件运行(single-file)”变得很顺手:写完一个文件,直接运行,不用显式 javac。 它非常适合做小实验、写脚本、快速验证想法。但一旦代码不再是“单文件自包含”,例如你把 Person、Order、Utils 等拆成多个 .java 文件,继续用单文件方式运行,就可能遇到一些让人摸不着头脑的运行时错误——甚至出现经典的: Person cannot be cast to Person 这篇笔记就从 single-file 的运行方式出发,解释背后的根因:ClassLoader 与 Java 的“类型身份”,以及如何避免踩坑。 简简简简简述“单文件运行” 传统方式: javac Demo.java Person.java java Demo 单文件方式(source launcher): java Demo.java 但本质上,第二种是: Java 启动器先把 Demo.java 文件编译成字节码(通常在临时位置/内存中) 然后立即运行编译产物 但当程序不止一个源文件时,事情开始变复杂。 多源文件情况 假设有: Person.java:一个 public class Person Demo.java:演示代码(无论用传统 public static void main,还是 Java 21/22 的“隐式声明类/无名类”写法,编译器最终都会生成某个 class 来承载入口逻辑) 运行时至少会涉及两个类: Person 承载入口逻辑的那个类(比如 Demo) ClassLoader 有很多种 ...

2026-01-18 · (updated 2026-01-19) · 1 min · 170 words · Lou Feiyu

多顶级类、内部类、可见性与最佳实践:写练习时弄明白的基础体系

梳理搭建地基「需要明确的知识点」 访问权限 顶层(类的可见性):public、包可见 方法/字段:public、包可见、private、protect 最佳方式是一个类文件一个类。想要实现一个文件里多个类,有两种方式选择:多个顶级类or嵌套类「内部类(非静态嵌套类)、静态嵌套类」 一个类文件中可以有很多顶级类,但只有那个与文件名同名的类允许被public修饰;嵌套类不允许被public修饰,只能是默认的包可见。 内部类和静态嵌套类 内部类是非static的,是“外部类的真正内部类”,可以直接访问外部类的成员字段/方法,“相当于外部类的一个成员”,非static与对象绑定,想要实例化,必须实例化out之后用out.new; 静态嵌套类是static,“相当于外部类的一个静态成员(与静态方法同类)”,使用与静态字段、静态方法相同,直接用即可 思维锚点:使用static标识符意味着和任何外部的实例对象脱钩,要确保其内部调用的所有实例成员都是自己重新实例化而来、静态成员都是类.成员或者直接调用成员而来,与外界无调用关系 在外包访问时,顶级类、内部类、静态嵌套类各自的可见性? 有几种情况: 包下面有很多的类文件,每个文件一个类。 包下面有一个类文件,文件中有很多顶级类 包下面有一个类文件,只有一个顶级类,类中有很多的内部类 第一种情况下,只要每个文件的类,是用public的,就可以被包外访问 第二种情况下,顶级类中,可以用public修饰的只有文件同名类,其他顶级类只允许是包可见。这种情况下,外部包永远不可能访问到其他顶级类 第三种情况下,(在顶级类修饰为public情况下):嵌套类设置为public,就可以包外可见;设置为默认(包可见),就包外不可见。和是否有static修饰符无关,static 只影响“要不要依赖外部类实例”,不影响访问权限 从语义上看,静态嵌套类更像“放在 Outer 名字空间里的(其他)顶级类” 两者相同: 调用public顶级类的静态字段/方法:可以直接类.字段/方法调用 调用public顶级类的实例字段/方法:必须实例化这个public顶级类后,用对象.字段/方法调用 区别: 同个文件的各个顶级类,是包的成员;而静态嵌套类,是外部类的成员 各个顶级类,只有顶层访问权限(public或者包可见),意味着它一定对包内所有类可见 静态嵌套类,因为是外部类的成员,拥有成员访问权限(四种),意味着可以做到包内其他类不可见 静态嵌套类在外是 外部类.静态嵌套类,命名空间永远是在外部类之下。意味着永远表达:是外部类的附庸含义 「关键」静态嵌套类作为一个附庸的“特权”:外部类的 private static 成员,静态嵌套类是可以访问到的,其他顶级类不行 「单文件+多个顶级类」和「单文件+多个静态嵌套类」场景对比 「单文件+多个内部类」表示强绑定关系了,是外部类的对象的成员(实例成员)。这里对两个绑定关系没那么强的两个进行对比 ...

2025-12-01 · (updated 2025-12-02) · 1 min · 113 words · Lou Feiyu

从mapMulti到Stream的底层逻辑

首先,mapMulti或许用的不是特别多——和flatmap相比就只是多了一个优点:不会产生对于每个元素的中间stream对象,减少了开销。但是因为对它写法的一系列疑惑,结果促使探究到了stream的链路逻辑。 场景与mapMulti&faltMap //场景:将一个数字字符串列表转为Integer类型的列表,同时去除不合法字符 //实现:使用更省开销的mapMulti方法 List<String> strings = List.of("1", " ", "2", "3 ", "", "3"); List<Integer> ints = strings.stream() .<Integer>mapMulti( (string, consumer) -> { try { consumer.accept(Integer.parseInt(string)); } catch (NumberFormatException ignored) { } }) .toList(); IO.println("ints = " + ints); 一些基础 java16后引入 该方法传入一个BiConsumer 需要被映射的元素 调用一个Consumer,用来存放最终结果流,达到不产生中间过程流的目的(相比于flatMap) 因为这里逻辑稍复杂,防止编译器混乱,泛型参数类型推断出错,手动进行了参数类型指定。 对于方法:泛型写在方法名前「更准确是写在返回类型前」 对于类:泛型写在类名后 代码块逻辑: 若元素能被Integer.parseint准确转化,就将此结果传入一个结果流中,而不是形成一个个小的stream对象再进行展平(flatMap) 若失败,报出受检异常,并被catch捕获处理 传的consumer是什么东西? 怎么会蹦一个consumer出来?内部定义的?定义这个干嘛? 要弄明白这个问题就需要深入stream的底层了 直觉式解释: stream以“pipeline”式处理流式数据闻名。源数据经过一系列中间操作累积处理逻辑,最后在最终操作那一口气处理。那么是怎么将这些操作逻辑累积下去的呢? 答:consumer。每次中间操作都会进行这样的逻辑:承载接收上流操作、将本次操作加入——“consumer.accept()”,传递到终端操作处,统一处理 mapMulti这里是显式地将内部进行的consumer操作作为参数传递,一旦有合法字符转化了,就将它传递。从而达成不产生中间流的作用 实际:AbstractPipeline + Sink。不过还是拿consumer来搭建心智模型: [源数据] → [map1] → [filter] → [map2] → [终止操作 toList] headSink → map1Sink → filterSink → map2Sink → terminalSink Stream 是惰性的:只有调用终止操作(如 toList())时,才真正开始把元素从源头流过整个管道。 终止操作首先创建了最底层的“真实 consumer”:比如 x -> result.add(x)。 然后,从最后一个中间操作开始,每一层都拿到“下游 consumer”并返回一个“包了一层逻辑的上游 consumer”。 这一层层 wrap 下来,最外层的那个 consumer,就对应管道最前面的操作(第一个 map/filter)。 当源数据被遍历时,只调用这个最外层的 head.accept(x),它内部会按顺序调用各个中间操作逻辑,最后传到终止操作的 consumer 上。 //伪实现 //终端操作: List<T> toList() { List<T> result = new ArrayList<>(); // 1. 在终止操作里,先创建最底层的 consumer Consumer<T> terminal = x -> result.add(x); // 2. 从“最后一个中间操作”开始,往前一层层 wrap Consumer<?> head = terminal; Stage<?> s = this; // this = 最后一个 stage(最近的 map/filter) while (s != null) { head = s.wrap(head); // 每一层都“包住”下游 consumer s = s.upstream; // 然后跳到上游那层 } // 3. 最终得到 head,是最前面的那个 map/filter 封装出来的“总入口” for (Object element : getSource()) { ((Consumer<Object>) head).accept(element); // 启动整条链 } return result; } //中间操作: class FilterStage<T> extends Stage<T> { private final Predicate<T> pred; FilterStage(Stage<T> upstream, Predicate<T> pred) { super(upstream); this.pred = pred; } @Override <X> MyConsumer<T> wrap(MyConsumer<X> downstream) { return (T value) -> { if (pred.test(value)) { ((MyConsumer<T>) downstream).accept(value); } // 否则就“拦截”掉,不往下传 }; } } 传的consumer就是该步操作产生的新consumer(将来会作为upStream向上传递,等待被包装)。 ...

2025-11-29 · (updated 2025-12-02) · 2 min · 337 words · Lou Feiyu

「你怎么能直接...」

有好多版本,好有意思 git 你怎么能直接 commit 到我的 main 分支啊?!GitHub 上不是这样!你应该先 fork 我的仓库,然后从 develop 分支 checkout 一个新的 feature 分支,比如叫 feature/confession。然后你把你的心意写成代码,并为它写好单元测试和集成测试,确保代码覆盖率达到95%以上。接着你要跑一下 Linter,通过所有的代码风格检查。然后你再 commit,commit message 要遵循 Conventional Commits 规范。之后你把这个分支 push 到你自己的远程仓库,然后给我提一个 Pull Request。在 PR 描述里,你要详细说明你的功能改动和实现思路,并且 @ 我和至少两个其他的评审。我们会 review 你的代码,可能会留下一些评论,你需要解决所有的 thread。等 CI/CD 流水线全部通过,并且拿到至少两个 LGTM 之后,我才会考虑把你的分支 squash and merge 到 develop 里,等待下一个版本发布。你怎么直接上来就想 force push 到 main?!GitHub 上根本不是这样!我拒绝合并! RAG系统 你怎么能直接 client.chat.completions.create 去调大模型啊?!企业级 RAG 系统里不是这样!你应该先通过 ETL 流水线把 PDF 和 PPT 清洗一遍,跑一遍 OCR 和 Layout Analysis 提取图片里的图表信息。接着你要用 Recursive Character Text Splitter 按照语义完整性做 Chunking,并调用 CLIP 或者 BGE-M3 模型生成多模态 Embeddings。然后你要把这些高维向量存进 Milvus 或者 Weaviate,构建好 HNSW 索引。接着用户提问的时候,你要先做 Query Rewriting,生成多路查询,去走一遍 Hybrid Search,同时结合 BM25 关键词和 Dense Vector 召回 Top-K 文档。然后你要过一遍 Cross-Encoder 的 Reranker 模型,把 Relevancy Score 低的脏数据过滤掉。之后你把这些图文切片按照 Token Limit 精心塞进 Prompt Template 的 Context Window 里,并严格要求模型基于引用回答。在生成 Response 之前,你要跑一遍 Guardrails 甚至用 RAGAS 框架做一轮评估,检查有没有幻觉,确保 Faithfulness 和 Answer Relevance 分数达标。等 Grounding Check 全部通过,并且没有触发 Sensitive Word Filter 之后,我才会考虑把 token 一个个 stream 推回给前端,等待用户反馈。你怎么直接上来就想裸调 API 靠模型瞎编?!知识库增强根本不是这样!我拒绝生成! ...

2025-11-26 · 2 min · 328 words · Lou Feiyu

lambda写法:不捕获外部变量(non-capturing)

#最佳实践 一般来说不会有这个问题,老老实实进行“定义接口->写实现类/匿名类->new对象->调用方法”自然会进行变量层面的抽象。 但lambda这种语法糖太简洁了,导致写的时候会忍不住追求更方便写法,导致jvm运行时的不方便 List<String> strings = List.of("one", "two", "three", "four", "five", "six", "seven"); Map<Integer, String> map = new HashMap<>(); for (String word: strings) { int length = word.length(); map.merge(length, word, (existingValue, newWord) -> existingValue + ", " + newWord);//如果length不在map中,会绑定到word;如果存在,会用现有值和word调用双函数,双函数的结果替换当前值 } map.forEach((key, value) -> IO.println(key + " :: " + value)); 为什么这些lambda明明可以直接用外面的变量,如上面的newword,但还要把它作为一个参数传进来? 可以让lambda变成“non-capturing”(不捕获外部变量),性能更好 对于non-capturing,JVM 可以把它当成纯函数对象、无状态对象来优化 可以实现成一个没有字段的单例对象,甚至整个程序里就用这一份;一次创建,到处复用。 不需要每次创建新对象; 更容易被 JIT 内联、优化。 如果进行捕获了,lambda 用到了外部的变量,编译器必须生成一个带字段的对象,里面存着 bonus 的值。在某些场景下,甚至每次使用都要 new 一份(或者至少要为每个不同捕获环境生成不同实例),分配成本更高,优化空间更小。

2025-11-15 · 1 min · 65 words · Lou Feiyu

Map的entry方法名字由来「助力记忆」

大伙自然地将entry和键值对联系在一起**“一个键值对就是一个 entry”**,于是java官方就把其作为造键值对的静态方法名 为什么会“自然地将entry和键值对联系在一起”? entry 有“目录/词典里的一个条目”语义 map也很像词典/目录:词头、释义

2025-11-13 · 1 min · 6 words · Lou Feiyu

“长尾效应”概念「长尾应用」与今天

“长尾”指的是:主流需求之外,那条又长又细、数量巨大的“小需求带” 除开很多人需求外,还有一部分“细长”区域,特点是 每个需求对应的人数不是那么多 需求种类很多 以前没人管这条尾巴,原因:软件开发成本高:人工成本、维护成本、需要“比较规范”才上线 现在:LLM、app生成工具、低成本部署使得面向特定小群体的app也值得一做。应用=内容,如15年前YouTube兴起一样

2025-11-10 · 1 min · 6 words · Lou Feiyu

到底什么是真正的ai-agent?

注:观点来自锦恢 注:内容来自该帖子评论区的回答 Q:训练岗应该能无缝转agent算法开发吧 A:只是把大模型接入应用,很简单,但是如果需要在特定任务上不微调模型的情况下把准确率提升到95%以上,就很难了。目前agent开发难点不在接入一个agent框架上,这件事没有任何技术难度,难在不断调整策略,在业务数据的迭代中设计出可以满足客户要求的准确率和可靠性的系统。后训练模型只是一种解决思路,但是考虑到成本和实际的性能,频繁使用这个方法和只使用这个方法都是不可靠的。事实上,大模型到最终落地之间有一道很大的鸿沟,这道鸿沟就是agent开发的发力空间。很多纯算法出身的人都认为只要大模型一训好,就能自动落地让客户满意,事实上,并非如此。

2025-11-10 · 1 min · 4 words · Lou Feiyu

迭代和遍历翻译问题iteration

背景 看dev.java,学集合框架时,原文写了iteration,翻译总翻成遍历,觉得奇怪 结论 都可以。“iterate” 既可以表示 “迭代”,也可以表示 “遍历” iterate演化出两层含义 机械地重复执行(即迭代算法中“反复求近似解”那种含义); 依次访问集合中的每个元素(即遍历集合)。 编程中 “迭代器(iterator)” 是一种机制 “遍历(iteration)” 是这种机制实现的动作 参考 https://www.zhihu.com/question/39854900

2025-11-05 · 1 min · 17 words · Lou Feiyu

CUDA 的系统组成-面向他人讲解

很棒的资料 https://jamesakl.com/posts/cuda-ontology/ 详细系统讲解了CUDA整体 从顶往下:GPU 软件栈的层次结构 可以把 CUDA 系统理解成从硬件到应用的四层结构: ┌──────────────────────────┐ │ 你的深度学习程序 (PyTorch / TensorFlow) │ ← 应用层 ├──────────────────────────┤ │ CUDA Toolkit / cuDNN / cuBLAS / 驱动API │ ← 开发与加速库层 ├──────────────────────────┤ │ NVIDIA 驱动 (Driver) │ ← 驱动层 ├──────────────────────────┤ │ GPU 硬件 (2080 Ti 等) │ ← 硬件层 └──────────────────────────┘ “我的 Python 程序通过 PyTorch 调用 CUDA Runtime API, CUDA Runtime 依赖 NVIDIA 驱动与硬件通信, 驱动再控制显卡完成底层计算。” “CUDA 生态是一整套 GPU 加速栈: 从最底层的硬件到驱动,再到 Toolkit 和加速库,最后被深度学习框架调用。 驱动连接硬件,Toolkit 提供编译和库接口, 框架调用这些接口完成并行计算。” ...

2025-11-01 · 1 min · 184 words · Lou Feiyu

泪水之城的艺术风格

泪水之城描述 如 一座由繁华转为死寂的城市,总是下着雨 阴暗的小巷,总是潮湿多雨,高耸入云/不切实际的尖塔,幽深的地下城 风格? 搜索失败了,并没有准确的答案。 reddit上确实有相关讨论,但是都是模糊模棱两可的 感觉最像的是哥特式 城市:Prague 如果天气是雨天就更好了。这种感觉绝了 一位似乎是艺术领域或者爱好者?的回答 《空洞骑士》中的几种设计可能受到了历史悠久的维多利亚式建筑(更多例子)和第二帝国风格的启发,带有巴洛克和哥特式的影响和风格,并带有一丝新艺术风格。 是的,维多利亚式,高耸的尖塔,笔直的墙壁

2025-04-25 · 1 min · 13 words · Lou Feiyu

Homebrew

mac里的一款软件、包管理工具,提供快捷的依赖关系整理 安装命令「使用官网脚本安装」:/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 检查:brew --version 使用:brew install mysql 首次配置参考教程:https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/ 注意 配置brew本身的镜像源「否则其自身的update将会很慢」 配置仓库的镜像源「否则其下载会很慢」

2025-01-27 · 1 min · 15 words · Lou Feiyu