作者: iuu

Agent、RAG、Function Call与MCP

Agent(智能体)
RAG(检索增强生成)
Function Call(函数调用)
MCP(模型上下文协议)

特性 MCP (模型上下文协议) RAG (检索增强生成) Agent (智能体) Function Call (函数调用)
核心思想 标准化 AI 与外部数据/工具的通信协议 检索外部知识 + 增强提示 + 生成回答 LLM驱动的自主决策与任务执行系统 LLM请求执行外部预定义函数/工具的能力
本质 协议/规范 技术框架/方法 系统/应用范式 模型能力/特性
通俗比喻 标准化的 USB 接口 写论文前先查资料 能干的私人助理 助理按指令使用 App
关系链 可作为 Agent 调用工具的底层标准 常被 Agent 用作获取知识的手段 核心指挥官,使用 RAG/Function Call 等工具 Agent 执行具体动作的基本手段

简单来说,它们的关系就像:

• Agent (智能体) 是那个目标导向的项目经理/大脑。
• RAG 和 Function Call 是它工具箱里的得力工具:RAG 负责查资料、找依据;Function Call 负责执行具体操作、调用外部 API。
• MCP 则致力于提供一个标准化的接口规范,让 Agent 能更方便、更统一地接入和使用各种工具(无论是 RAG 功能还是其他 Function Call 实现的工具)。

• 这是啥? Function Call 是 LLM 的一项内置“特异功能”。它允许 LLM 在需要的时候,请求外部程序帮它做点事。注意,是“请求”,不是“亲自做”。
• 为啥要它? 因为 LLM 自己查不了实时股价、订不了机票、发不了邮件。有了 Function Call,LLM 就能“指挥”其他工具来完成这些操作。
• 通俗比喻: 就像你让智能音箱帮你“查下今天北京天气”。音箱(LLM)自己感知不到天气,但它知道要去调用“天气查询”这个App(预定义的函数/工具)。它生成指令(“查北京天气”),App 执行后把结果(“晴,25度”)告诉音箱,音箱再用自然语言告诉你。
• 简单例子: 你问 AI:“AAPL 股价多少?” AI 判断需要查实时数据,于是生成一个“请求”:{调用函数: "查股价", 参数: {"股票代码": "AAPL"}}。外部程序收到请求,查询API,返回结果 {"价格": 180.50}。AI 再根据这个结果回答你:“苹果当前股价是 180.50 美元。”

• 这是啥? RAG (Retrieval-Augmented Generation) 是一种让 AI 回答更靠谱的技术框架。简单说,就是在 AI 回答问题 之前,先让它去指定的资料库(比如公司内部文档、最新的行业报告)里查找 (Retrieval) 相关信息。
• 为啥要它? 防止 AI一本正经地“胡说八道”(专业术语叫“幻觉”),让它的回答基于最新的、准确的、特定的事实依据。
• 通俗比喻: 好比你写论文要引用最新数据。你不会光凭记忆(LLM 的内部知识)瞎写,而是会先去图书馆或数据库查资料 (检索),把找到的关键信息整合 (增强)进你的论据里,最后才下笔写作 (生成)。RAG 就是让 AI 也学会这种“先查再答”的好习惯。
• 简单例子: 你问 AI:“我们公司最新的报销政策是啥?” RAG 系统先去公司内部知识库检索“报销政策”文档,找到相关段落。然后把这些段落和你的问题一起“喂”给 AI,AI 参考着这些最新政策,给你一个准确的回答。

• 这是啥? Agent(智能体)是一个更高级、更自主的 AI 系统。它以 LLM 作为核心“大脑”,不仅能理解你的目标,还能自己思考、规划步骤,并主动调用工具(比如上面说的 RAG 和 Function Call)来执行任务,与外部环境互动。
• 为啥要它? 为了完成那些光靠聊天解决不了的复杂任务,比如“帮我规划下周去上海的出差行程,包括订机票酒店,并把日程发给我”。
• 通俗比喻: Agent 就像一个超级能干的私人助理。你给个目标,它自己就能拆解任务、查信息(可能用 RAG 查公司差旅标准,用 Function Call 查航班酒店)、做决策、执行操作(用 Function Call 调用预订 API),最后给你结果。它是有自主“行动力”的。
• 简单例子: 你让 Agent:“分析一下竞品 X 的最新动态,写个简报。” Agent 会自己规划:① 搜索最新新闻(调用 Function Call);② 查内部研究报告(调用 RAG);③ 分析总结信息(LLM 大脑);④ 生成简报(调用 Function Call)。

• 这是啥? MCP (Model Context Protocol) 是 Anthropic 公司(就是搞出 Claude 那个)在 2024 年底提出并开源的一种标准化通信协议。它定义了一套规则,让 AI 应用(客户端)能以统一的方式,与各种外部数据源或工具(服务器)进行交互。
• 为啥要它? 想象一下,如果每个工具都有自己独特的接口,那 Agent 想用多个工具时,岂不是要学 N 种“方言”?MCP 就是想统一这个接口标准,让工具“即插即用”。
• 通俗比喻: MCP 就像是给 AI 大脑和外部工具之间制定了一个通用的 USB 接口标准。无论是本地文件系统、数据库,还是 Slack、GitHub 这些应用,只要它们提供符合 MCP 标准的“服务器”,AI 应用(客户端)就能轻松连接并使用它们的功能,无需为每个工具单独适配。
• 简单例子: 在支持 MCP 的编辑器里,你可以让 AI“把我 /docs 目录最新的 Markdown 文件总结一下,发到 Slack 的 #general 频道”。编辑器(MCP 客户端)通过 MCP 协议,与本地的“文件系统 MCP 服务器”和“Slack MCP 服务器”沟通,协调完成整个任务。

支持 MCP 的客户端/服务器:

• 客户端: Claude Desktop App, Cursor, Windsurf, Cherry Studio 等 AI 编辑器或应用。
• 服务器: Anthropic 官方和社区提供了针对 Google Drive, Slack, GitHub, Git, Postgres, Puppeteer, Milvus (向量数据库), Firecrawl (网页抓取) 等的开源 MCP 服务器实现。开发者也可以根据 MCP 规范自定义服务器。目前,为安全起见,MCP 服务器通常在本地运行。

SGLang 多节点集群部署Qwen系列大模型

比起Ollama的方便,有些时候高并发更重要,因此这篇文章将实现在两台电脑(双节点)上部署 SGLang(当然如果你还有多余的也可以加进来当节点),运行 Qwen2.5-7B-Instruct 模型,实现本地资源的充分利用。

硬件

• 节点 0:IP 192.168.0.12,1 个 英伟达显卡
• 节点 1:IP 192.168.0.13,1 个 英伟达显卡

模型

Qwen2.5-7B-Instruct,FP16 下约需 14GB 显存,使用 --tp 2 后每 GPU 约 7GB(权重)+ 2-3GB(KV 缓存)。

网络

两节点通过以太网(TCP)通信,网络接口为 eno1。

不量化

使用 FP16 精度以保留最大精度,显存占用较高,需优化配置。

操作系统

• 推荐 Ubuntu 20.04/22.04 或其他 Linux 发行版(Windows 不推荐,需 WSL2)
• 两节点最好是一致的环境,当然os的环境不是必须,但是Python的环境需要一样

网络连通性

• 节点 0(192.168.0.12)和节点 1(192.168.0.13)可互相 ping 通:

ping 192.168.0.12  # 从节点 1
ping 192.168.0.13  # 从节点 0

• 端口 50000(分布式初始化)和 30000(HTTP 服务器)未被防火墙阻挡:

sudo ufw allow 50000
sudo ufw allow 30000

• 确认网络接口 eno1:

# 具体网卡根据实际调整
ip addr show eno1

若 eno1 不存在,替换为实际接口(如 eth0 或 enp0s3)。

GPU 驱动和 CUDA

• 安装 NVIDIA 驱动(版本 ≥ 470)和 CUDA Toolkit(推荐 12.x):

nvidia-smi  # 确认驱动和 CUDA 版本

输出应显示 英伟达和 CUDA 版本(如 12.4)。

若未安装,参考 NVIDIA 官网 自行安装即可:

Python 环境

• Python 3.9+(推荐 3.10)
• 两节点需一致的 Python 版本:

python3 --version

磁盘空间

• Qwen2.5-7B-Instruct 模型约需 15GB 磁盘空间
• 确保 /opt/models/Qwen/Qwen2.5-7B-Instruct 路径有足够空间

在两节点上分别安装 SGLang 和依赖。以下步骤在每台电脑上执行。

创建虚拟环境(conda)

conda create -n sglang_env python=3.10
conda activate  sglang_env

安装 SGLang

备注: 安装过程会自动安装 对应显卡相关的依赖,如 torch,transformers,flashinfer等

pip install --upgrade pip
pip install uv
uv pip install "sglang[all]>=0.4.5" --find-links https://flashinfer.ai/whl/cu124/torch2.5/flashinfer-python

验证安装:

python -m sglang.launch_server --help

应显示 SGLang 的命令行参数帮助信息。

下载 Qwen2.5-7B-Instruct 模型

国外使用 huggingface,国内使用 modelscope
在两节点上下载模型到相同路径(如 /opt/models/Qwen/Qwen2.5-7B-Instruct):

pip install modelscope
modelscope download Qwen/Qwen2.5-7B-Instruct --local-dir /opt/models/Qwen/Qwen2.5-7B-Instruct

或手动从 Hugging Face 或者 modelscope 下载并解压到指定路径。确保两节点模型文件一致。

配置双节点部署

使用张量并行(--tp 2)将模型分布到 2 个 GPU(每节点 1 个)。以下是详细的部署步骤和命令。

部署命令

• 节点 0(IP: 192.168.0.12):

NCCL_IB_DISABLE=1 NCCL_P2P_DISABLE=1 GLOO_SOCKET_IFNAME=eno1 NCCL_SOCKET_IFNAME=eno1 python3 -m sglang.launch_server \
  --model-path /opt/models/Qwen/Qwen2.5-7B-Instruct \
  --tp 2 \
  --nnodes 2 \
  --node-rank 0 \
  --dist-init-addr 192.168.0.12:50000 \
  --disable-cuda-graph \
  --host 0.0.0.0 \
  --port 30000 \
  --mem-fraction-static 0.7

• 节点 1(IP: 192.168.0.13):

NCCL_IB_DISABLE=1 NCCL_P2P_DISABLE=1 GLOO_SOCKET_IFNAME=eno1 NCCL_SOCKET_IFNAME=eno1 python3 -m sglang.launch_server \
  --model-path /opt/models/Qwen/Qwen2.5-7B-Instruct \
  --tp 2 \
  --nnodes 2 \
  --node-rank 1 \
  --dist-init-addr 192.168.0.12:50000 \
  --disable-cuda-graph \
  --host 0.0.0.0 \
  --port 30000 \
  --mem-fraction-static 0.7

注意: 如果出现 OOM的情况则调整 --mem-fraction-static 参数,默认是 0.9,改为 0.7 即可。0.9 调整到0.7 时 当前7B模型 占用显存直接下降 2G左右。
CUDA Graph 会额外分配少量显存(通常几百 MB)来存储计算图。如果显存接近上限,启用 CUDA Graph 可能触发 OOM 错误。

参数说明

以下是命令中每个参数的详细解释,结合你的场景:

环境变量

• NCCL_IB_DISABLE=1:禁用 InfiniBand,因为你的节点通过以太网通信
• NCCL_P2P_DISABLE=1:禁用 GPU 间的 P2P 通信(如 NVLink),因为 GPU 在不同电脑上
• GLOO_SOCKET_IFNAME=eno1:指定 GLOO 分布式通信的网络接口(替换为实际接口,如 eth0)
• NCCL_SOCKET_IFNAME=eno1:指定 NCCL 通信的网络接口

SGLang 参数

• --model-path /opt/models/Qwen/Qwen2.5-7B-Instruct:模型权重路径,两节点必须一致
• --tp 2:张量并行,使用 2 个 GPU(每节点 1 个)。模型权重和计算任务平分到 2 个 GPU
• --nnodes 2:指定 2 个节点。tp_size (2) ÷ nnodes (2) = 1 GPU 每节点,满足整除要求
• --node-rank 0 / --node-rank 1:节点编号,节点 0 和 1 分别设置为 0 和 1
• --dist-init-addr 192.168.0.12:50000:分布式初始化地址,指向节点 0 的 IP 和端口,两节点一致
• --disable-cuda-graph:禁用 CUDA 图
• --trust-remote-code:允许加载 Qwen 模型的远程代码(Hugging Face 模型必需),模型下载好的直接不用这个参数即可
• --host 0.0.0.0:服务器监听所有网络接口,允许外部访问
• --port 30000:HTTP 服务器端口,可根据需要调整(若冲突,节点 1 可设为 --port 30001)
• --mem-fraction-static 0.7:KV 缓存池占用 70% 显存(默认 0.9),降低显存占用以适配 低显存显卡

如果选择量化

使用 --quantization 或 --torchao-config. SGLang 支持以下基于 torchao 的量化方法。["int8dq", "int8wo", "fp8wo", "fp8dq-per_tensor", "fp8dq-per_row", "int4wo-32", "int4wo-64", "int4wo-128", "int4wo-256"]

请注意:

使用--quantization fp8量化,PyTorch 的 FP8(8 位浮点)量化功能(依赖 torch._scaled_mm)要求 GPU 的 CUDA 计算能力(Compute Capability)达到 8.9 或 9.0 以上,或者使用 AMD ROCm MI300+ 架构.因此如果你的显卡不咋地则不建议使用这个参数。

• 节点 0(IP: 192.168.0.12):

NCCL_IB_DISABLE=1 NCCL_P2P_DISABLE=1 GLOO_SOCKET_IFNAME=eno1 NCCL_SOCKET_IFNAME=eno1 python3 -m sglang.launch_server \
  --model-path /opt/models/Qwen/Qwen2.5-7B-Instruct \
  --tp 2 \
  --nnodes 2 \
  --node-rank 0 \
  --dist-init-addr 192.168.0.12:50000 \
  --disable-cuda-graph \
  --torchao-config int4wo-32 \
  --host 0.0.0.0 \
  --port 30000 \
  --mem-fraction-static 0.7

• 节点 1(IP: 192.168.0.13):

NCCL_IB_DISABLE=1 NCCL_P2P_DISABLE=1 GLOO_SOCKET_IFNAME=eno1 NCCL_SOCKET_IFNAME=eno1 python3 -m sglang.launch_server \
  --model-path /opt/models/Qwen/Qwen2.5-7B-Instruct \
  --tp 2 \
  --nnodes 2 \
  --node-rank 1 \
  --dist-init-addr 192.168.0.12:50000 \
  --disable-cuda-graph \
  --torchao-config int4wo-32 \
  --host 0.0.0.0 \
  --port 30000 \
  --mem-fraction-static 0.7

部署完成

如下在主节点出现类似下面日志则启动成功,如果有异常 则自己看日志排查

[2025-04-17 14:50:24 TP0] Attention backend not set. Use flashinfer backend by default.
[2025-04-17 14:50:24 TP0] Disable chunked prefix cache for non-MLA backend.
[2025-04-17 14:50:24 TP0] Init torch distributed begin.
[2025-04-17 14:50:32 TP0] sglang is using nccl==2.21.5
[2025-04-17 14:50:32 TP0] Custom allreduce is disabled because this process group spans across nodes.
[2025-04-17 14:50:32 TP0] Init torch distributed ends. mem usage=0.11 GB
[2025-04-17 14:50:32 TP0] Load weight begin. avail mem=11.44 GB
Loading safetensors checkpoint shards:   0% Completed | 0/4 [00:00<?, ?it/s]
Loading safetensors checkpoint shards:  25% Completed | 1/4 [00:00<00:01,  2.71it/s]
.....
[2025-04-17 14:50:34 TP0] Load weight end. type=Qwen2ForCausalLM, dtype=torch.bfloat16, avail mem=4.22 GB, mem usage=7.22 GB.
[2025-04-17 14:50:34 TP0] KV Cache is allocated. #tokens: 29357, K size: 0.39 GB, V size: 0.39 GB
[2025-04-17 14:50:34 TP0] Memory pool end. avail mem=3.09 GB
...
[2025-04-17 14:50:36] INFO:     Uvicorn running on http://0.0.0.0:30000 (Press CTRL+C to quit)
[2025-04-17 14:50:37] INFO:     127.0.0.1:32902 - "GET /get_model_info HTTP/1.1" 200 OK
[2025-04-17 14:50:37 TP0] Prefill batch. #new-seq: 1, #new-token: 6, #cached-token: 0, token usage: 0.00, #running-req: 0, #queue-req: 0, 
[2025-04-17 14:50:39] INFO:     127.0.0.1:32908 - "POST /generate HTTP/1.1" 200 OK
[2025-04-17 14:50:39] The server is fired up and ready to roll!

从主节点机器访问服务器, 查看模型信息,至此部署完成。

curl http://192.168.0.12:30000/v1/models

访问测试

curl http://192.168.0.12:30000/v1/chat/completions   -H "Content-Type: application/json"   -d '{
      "model": "Qwen2.5-7B-Instruct", 
      "messages": [{
          "role": "user", 
          "content": "你是谁?"
      }], 
      "temperature": 0.3
  }'

PHP JAVA 面向对象疑惑整理

java中面向对象的修饰符

修饰符 本类内 同包内 子类中(不同包) 其他包中
private
默认(不写)
protected
public
修饰符 口诀
private 自家独享
默认 同包共享
protected 同包 + 子女共享
public 人人共享

PHP中面向对象的修饰符

修饰符 本类内 子类中 外部访问
private
protected
public
修饰符 口诀
private 自家独享
protected 自家 + 子女共享
public 人人共享

构造方法

java构造方法的名称就是类名。构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。
一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句,类似这样:

class Person {
    public Person() {
    }
}

自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来

// 构造方法
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Xiao Ming", 15); // 既可以调用带参数的构造方法
        Person p2 = new Person(); // 也可以调用无参数构造方法
    }
}

class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分

如果调用new Person("Xiao Ming", 20);,会自动匹配到构造方法public Person(String, int)。

如果调用new Person("Xiao Ming");,会自动匹配到构造方法public Person(String)。

如果调用new Person();,会自动匹配到构造方法public Person()。

一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…):

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this(name, 18); // 调用另一个构造方法Person(String, int)
    }

    public Person() {
        this("Unnamed"); // 调用另一个构造方法Person(String)
    }
}

方法重载

在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在Hello类中,定义多个hello()方法:

class Hello {
    public void hello() {
        System.out.println("Hello, world!");
    }

    public void hello(String name) {
        System.out.println("Hello, " + name + "!");
    }

    public void hello(String name, int age) {
        if (age < 18) {
            System.out.println("Hi, " + name + "!");
        } else {
            System.out.println("Hello, " + name + "!");
        }
    }
}

这种方法名相同,但各自的参数不同,称为方法重载(Overload)。
注意:方法重载的返回值类型通常都是相同的。
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
举个例子,String类提供了多个重载方法indexOf(),可以查找子串:
int indexOf(int ch):根据字符的Unicode码查找;
int indexOf(String str):根据字符串查找;
int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;
int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。

继承

Student从Person继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。
Java使用extends关键字来实现继承:

class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

class Student extends Person {
    // 不要重复name和age字段/方法,
    // 只需要定义新增score字段/方法:
    private int score;

    public int getScore() { … }
    public void setScore(int score) { … }
}

子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
在OOP的术语中,我们把Person称为超类(super class),父类(parent class),基类(base class),把Student称为子类(subclass),扩展类(extended class)。
注意到我们在定义Person的时候,没有写extends。在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类。
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName 例如:

class Student extends Person {
    public String hello() {
        return "Hello, " + super.name;
    }
}

Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法
即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

阻止继承

正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。

public sealed class Shape permits Rect, Circle, Triangle {
    ...
}

Shape 类 只能 Rect, Circle, Triangle 来继承

向上转型

如果一个引用变量的类型是Student,那么它可以指向一个Student类型的实例:

Student s = new Student();

如果一个引用类型的变量是Person,那么它可以指向一个Person类型的实例:

Person p = new Person();

现在问题来了:如果Student是从Person继承下来的,那么,一个引用类型为Person的变量,能否指向Student类型的实例?

Person p = new Student(); // ???

测试一下就可以发现,这种指向是允许的!

这是因为Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的!

这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。

向上转型实际上是把一个子类型安全地变为更加抽象的父类型:

Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok

注意到继承树是Student > Person > Object,所以,可以把Student类型转型为Person,或者更高层次的Object。

向下转型

和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。例如:

Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!

如果测试上面的代码,可以发现:

Person类型p1实际指向Student实例,Person类型变量p2实际指向Person实例。在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException。

为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:

Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false

Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true

Student n = null;
System.out.println(n instanceof Student); // false

instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null,那么对任何instanceof的判断都为false。

利用instanceof,在向下转型前可以先判断:

Person p = new Student();
if (p instanceof Student) {
    // 只有判断成功才会向下转型:
    Student s = (Student) p; // 一定会成功
}

从Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型。例如,对于以下代码:

Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}

可以改写如下:

// instanceof variable:
public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // 可以直接使用变量s:
            System.out.println(s.toUpperCase());
        }
    }
}

这种使用instanceof的写法更加简洁。

Java初学第一篇

常用jdk版本
1.8(8)、17、23
下载地址
https://repo.huaweicloud.com/java/jdk/
https://repo.huaweicloud.com/openjdk/
https://d.injdk.cn/download/

基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:

整数类型:byte,short,int,long
浮点数类型:float,double
字符类型:char
布尔类型:boolean

Java基本数据类型的字节大小

在 Java 中,每种基本数据类型占用的字节数是固定的,具体如下:
byte:1 字节
short:2 字节
int:4 字节
long:8 字节
float:4 字节
double:8 字节
char:2 字节(用于存储 Unicode 字符)

数据类型占用字节数

byte:1 字节(8 位)。适合表示 -128 到 127 范围内的整数。
short:2 字节(16 位)。适合表示 -32,768 到 32,767 范围内的整数。
int:4 字节(32 位)。适合表示 -2^31 到 2^31-1 范围内的整数。
long:8 字节(64 位)。适合表示 -2^63 到 2^63-1 范围内的整数。
float:4 字节(32 位)。用于表示单精度浮点数。
double:8 字节(64 位)。用于表示双精度浮点数。
char:2 字节(16 位)。用于表示单个字符(Unicode 编码)。

整型

对于整型类型,Java只定义了带符号的整型,因此,最高位的bit表示符号位(0表示正数,1表示负数)。各种整型能表示的最大范围如下:
byte:-128 ~ 127
short: -32768 ~ 32767
int: -2147483648 ~ 2147483647
long: -9223372036854775808 ~ 9223372036854775807
我们来看定义整型的例子:

// 定义整型
public class Main {
    public static void main(String[] args) {
        int i = 2147483647;
        int i2 = -2147483648;
        int i3 = 2_000_000_000; // 加下划线更容易识别 
        // 等价 int i3 = 2000000000;
        int i4 = 0xff0000; // 十六进制表示的16711680
        int i5 = 0b1000000000; // 二进制表示的512

        long n1 = 9000000000000000000L; // long型的结尾需要加L
        long n2 = 900; // 没有加L,此处900为int,但int类型可以赋值给long
        int i6 = 900L; // 错误:不能把long型赋值给int
    }
}

特别注意:同一个数的不同进制的表示是完全相同的,例如15=0xf=0b1111
注意 在 Java 7(JDK1.7)开始,引入了这个语法特性,允许在数字字面量中使用下划线作为“分隔符”,目的是提高大数字的可读性,比如像:

int oneBillion = 1_000_000_000;
long creditCardNumber = 1234_5678_9012_3456L;
int hexBytes = 0xFF_EC_DE_5E;

语法规则虽然你可以加下划线,但要遵守一些规则:

✅ 合法的 ❌ 非法的
1_000_000 _1000000(不能开头)
1000000_ _不能在结尾
1_000_000L 0x_FF_EC(不能在前缀 0x 和数字之间)
3.14_15 3._1415(不能在小数点前后直接用)
1_2_3_4 1__2(不能连续多个下划线)

浮点型

浮点类型的数就是小数,因为小数用科学计数法表示的时候,小数点是可以“浮动”的,如1234.5可以表示成12.345x102,也可以表示成1.2345x103,所以称为浮点数。
下面是定义浮点数的例子:

float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
float f3 = 1.0; // 错误:不带f结尾的是double类型,不能赋值给float
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324

对于float类型,需要加上f后缀。
float 类型可表示的最大值是:3.4028235e+38
double 类型可表示的最大值是:1.7976931348623157e+308

布尔类型

布尔类型boolean只有true和false两个值,布尔类型总是关系运算的计算结果:

boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // 计算结果为true
int age = 12;
boolean isAdult = age >= 18; // 计算结果为false

Java语言对布尔类型的存储并没有做规定,因为理论上存储布尔类型只需要1 bit,但是通常JVM内部会把boolean表示为4字节整数。

字符类型

字符类型char表示一个字符。Java的char类型除了可表示标准的ASCII外,还可以表示一个Unicode字符:

// 字符类型
public class Main {
    public static void main(String[] args) {
        char a = 'A';
        char zh = '中';
        System.out.println(a);
        System.out.println(zh);
    }
}

注意char类型使用单引号',且仅有一个字符,要和双引号"的字符串类型区分开。

引用类型

除了上述基本类型的变量,剩下的都是引用类型。例如,引用类型最常用的就是String字符串:

String s = "hello";

常量

定义变量的时候,如果加上final修饰符,这个变量就变成了常量:

final double PI = 3.14; // PI是一个常量
double r = 5.0;
double area = PI * r * r;
PI = 300; // compile error!

常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。
常量的作用是用有意义的变量名来避免魔术数字(Magic number),例如,不要在代码中到处写3.14,而是定义一个常量。如果将来需要提高计算精度,我们只需要在常量的定义处修改,例如,改成3.1416,而不必在所有地方替换3.14。
为了和变量区分开来,根据习惯,常量名通常全部大写。

var关键字

有些时候,类型的名字太长,写起来比较麻烦。例如:

StringBuilder sb = new StringBuilder();

这个时候,如果想省略变量类型,可以使用var关键字:

var sb = new StringBuilder();

编译器会根据赋值语句自动推断出变量sb的类型是StringBuilder。对编译器来说,语句:

var sb = new StringBuilder();

实际上会自动变成:

StringBuilder sb = new StringBuilder();

因此,使用var定义变量,仅仅是少写了变量类型而已。

Dify 社区版Docker部署-(私有化部署知识库1)

克隆代码

git clone https://github.com/langgenius/dify.git

启动Dify

进入 Dify 源代码的 Docker 目录

cd dify/docker
复制环境配置文件
cp .env.example .env
启动 Docker 容器

根据你系统上的 Docker Compose 版本,选择合适的命令来启动容器。你可以通过 $ docker compose version 命令检查版本,详细说明请参考 Docker 官方文档:

如果版本是 Docker Compose V2,使用以下命令:

docker compose up -d

如果版本是 Docker Compose V1,使用以下命令:

docker-compose up -d

更新 Dify

进入 dify 源代码的 docker 目录,按顺序执行以下命令:

cd dify/docker
docker compose down
git pull origin main
docker compose pull
docker compose up -d

同步环境变量配置 (重要!)
如果 .env.example 文件有更新,请务必同步修改你本地的 .env 文件。

检查 .env 文件中的所有配置项,确保它们与你的实际运行环境相匹配。你可能需要将 .env.example 中的新变量添加到 .env 文件中,并更新已更改的任何值。

修改默认端口

EXPOSE_NGINX_PORT=80
EXPOSE_NGINX_SSL_PORT=443
1 2 3 4 5 16