阅读视图
🚀JS 为什么能跑这么快?一文把 V8 “翻译官 + 加速器” 机制讲透(AST / 字节码 / JIT / 去优化)
你的 JS 到底怎么跑起来的?一文看懂 V8:从源码到机器码的“流水线”(含图解)
写下
console.log('hi')的那一刻,CPU 其实完全看不懂。
真正让 JS “跑起来”的,是 JavaScript 引擎——尤其是 Chrome/Node.js 背后的 V8。
这篇文章用一条清晰的流水线,把 V8 的核心机制讲透:Parse → AST → Ignition(字节码) → TurboFan(机器码) → Deopt(去优化回退) 。
文章推荐:
代码10倍提速!吃透底层架构就是如此简单-腾讯云开发者社区-腾讯云
先建立直觉:V8 是一条“翻译+加速”的流水线
可以把 V8 想象成一个“会学习的翻译官”:
- 第一目标:让代码尽快跑起来(启动快)
- 第二目标:把经常跑的代码越跑越快(热点优化)
- 第三目标:发现假设错了就回退重来(去优化 Deopt)
接下来所有细节,都围绕这三句话展开。
01|为什么 CPU 才是最终执行者
CPU(中央处理器)执行的是机器语言——一串二进制指令。它不认识 JavaScript、也不认识“高级语言”的语法糖。
所以:CPU 是“执行者”,V8 是“翻译官 + 加速器”。
再看一张更直观的图:代码最终一定要落到 CPU 可执行的机器码上。
02|JavaScript 引擎在浏览器里处在什么位置
浏览器内核并不是“只有渲染”,它通常至少包含两大块:
- 渲染相关:HTML/CSS 解析、布局、绘制
- 脚本相关:解析并执行 JavaScript
以 WebKit 举例:它可以拆成 WebCore 和 JavaScriptCore 两部分(JS 引擎就是内核的一部分)。
03|V8 全流程:从源码到机器码
把 V8 的执行流程浓缩成 6 步,会非常清晰:
- Parse(解析) :源码 → AST(抽象语法树),并采用 Lazy Parsing(函数即将执行时才完整解析)
- Ignition(解释器) :AST → 字节码 Bytecode
- 执行字节码:先跑起来,并收集运行信息(类型、分支、调用频率…)
- TurboFan(优化编译器) :热点代码 → 优化后的机器码
- Deopt(去优化) :假设不成立(常见是类型变化)→ 回退到字节码
- 机器码执行:最终交给 CPU
用一张图把这条流水线钉死在脑子里:
同时,AST 长什么样?大概是这种结构化树形表示:
04|Parse 细节:词法分析、语法分析与 AST
很多人卡在“Parse 解析”这一步,原因是:概念名词多,但直觉不够。
4.1 词法分析:把代码拆成 token(最小语法单元)
可以理解为“拆词”——把一段 JS 源码拆成一个个最小的记号(token):
- 关键字
function - 标识符
sayHi - 运算符
=,+ - 标点符号
(),{},; - 字面量
"Hi "
4.2 语法分析:把 token 重新组装成树(AST)
可以理解为“造句”——把 token 按语法规则组装成结构化表达,这棵树就是 AST。
一个好记的口诀:
先词后语:先把“单词”拆出来,再把“语句结构”搭起来。
05|为什么要保留“字节码”这一层
直觉上会觉得:少一层转换就更快,那为什么不直接 AST → 机器码?
因为工程里真正的目标不是“某一步最快”,而是“整体更快、更稳、更可控”。保留字节码主要带来:
- 跨平台:字节码不绑定某一种 CPU 指令集
- 优化更聪明:先跑字节码,收集运行数据,再决定怎么生成更优机器码
- 更安全、更可控:更容易做隔离、策略、内存管理
- 更容易调试:断点/单步在字节码层更容易实现
配合这张图理解,会很顺:
06|架构拆解:Parse / Ignition / TurboFan 各做什么
用“岗位职责”来记:
- Parse:把 JS 代码变成 AST(解释器不直接认识 JS 源码)
- Ignition:把 AST 变成字节码并执行,同时收集 TurboFan 需要的运行信息(比如类型信息)
- TurboFan:把热点字节码编译成更快的机器码(并持续迭代优化)
这里有一个非常关键的运行规律:
热点函数会被优化,但类型变化等情况会触发去优化回退。
07|预解析 vs 全量解析:Lazy Parsing 为什么能让启动更快
V8 并不会“上来就把一切都解析得巨细无遗”,它会做取舍:
7.1 预解析(Pre-parsing)
- 目标:快速扫描,提取结构信息(变量/函数声明等)
- 特点:不深挖函数体内部逻辑 → 更快
7.2 全量解析(Full parsing)
- 目标:把函数体、表达式、语句细节全部建出来
- 特点:AST 更完整 → 便于后续生成字节码与优化
因此,“函数没执行会不会生成 AST?”更准确的回答是:
- 会生成一个简化的结构架子(预解析)
- 真要执行之前,会补齐为完整 AST(全量解析)
08|走一遍官方图:token、AST、字节码到底怎么来的
先准备一段模板代码:
name = "XiaoWu"
console.log(name)
function sayHi(name) {
console.log("Hi " + name)
}
sayHi(name)
8.1 官方流程图:从输入到字节码
这张图非常经典,建议收藏:
按图理解就是:
- Scanner:扫描字符流 → 生成 tokens
- PreParser:做预解析(快速判断结构)
- Parser:构建 AST
- Bytecode:AST → 字节码
8.2 token 长什么样(词法分析结果)
下面是典型 token 形态(摘取关键类型,方便理解):
Token(type='Keyword', value='const') // 关键字
Token(type='Identifier', value='name') // 标识符
Token(type='Operator', value='=') // 运算符
Token(type='StringLiteral', value='"coderwhy and XiaoYu"') // 字符串字面量
Token(type='Punctuation', value=';') // 标点符号
Token(type='Identifier', value='console')
Token(type='Punctuation', value='.')
Token(type='Identifier', value='log')
Token(type='Punctuation', value='(')
Token(type='Identifier', value='name')
Token(type='Punctuation', value=')')
Token(type='Punctuation', value=';')
8.3 语法分析:预解析如何参与
这张图专门解释“预解析/解析”的关系:
09|热点优化与去优化:为什么“有时突然变慢”
V8 会把被频繁执行的函数标记为 热点函数,然后交给 TurboFan 编译为更快的机器码。
但注意:优化是有前提假设的。最常见的假设就是“类型稳定”。
来看这个例子:
function sum (num1,num2){
return num1 + num2
}
// 多次调用 -> 可能成为热点函数 -> 被优化
sum(20,20)
sum(20,20)
// 类型突然变化 -> 之前的机器码假设不成立 -> 去优化回退
sum('xiaoyu','coderwhy')
发生了什么?
- 前两次传入
number,优化器可能会假设“这里一直是 number 加法” - 第三次突然变成
string拼接,机器码可能无法正确处理 → 回退到字节码重新收集信息,再决定是否重新优化
这就是性能“抖一下”的根源之一:Deopt(去优化) 。
10|字节码与机器码(了解即可):JIT 到底做了什么
机器码的生成通常依赖 JIT(Just-In-Time Compilation,即时编译) :
- 把字节码转换成本地机器码
- 把结果缓存起来
- 后续执行直接复用缓存的机器码(更快)
TurboFan 作为优化编译器,会基于 IR(中间表示)做多层优化(类型、内联、控制流等):
同时,字节码到机器码的过程中,会存在不同优化策略:
这里还有两张配图(保持原样保留):
结尾:把知识用起来
理解 V8 的意义,不是为了背名词,而是为了形成“性能直觉”:
- 让热点函数更容易被优化:参数类型尽量稳定
- 减少去优化回退:避免同一段热点路径里频繁出现类型漂移
- 理解启动性能:Lazy Parsing 的策略决定了“先跑起来”的快慢
React Router v6 全面指南:从入门到实战,看这篇就够了
1. Vue3必学:defineAsyncComponent四大配置全攻略,组件懒加载秒上手
在 Vue 3 中,defineAsyncComponent 是实现组件懒加载的核心 API,它能帮助我们按需加载组件、优化应用首屏加载速度,尤其适用于大型应用中组件数量多、体积大的场景。本文将从基础用法入手,详细拆解其 loading、error、delay、timeout 四大配置的功能与实践,帮你彻底掌握组件异步加载的精髓。
一、defineAsyncComponent 基础用法
组件懒加载的核心逻辑是“在需要时才加载组件代码”,而非应用初始化时一次性加载所有组件。Vue 3 提供了 defineAsyncComponent 方法封装异步组件,支持两种基础用法:简单语法和完整配置语法。
1. 简单语法(仅指定加载函数)
最简洁的用法是传入一个返回 Promise 的加载函数,该函数内部通过动态 import 加载组件。当组件被渲染时,会自动执行加载函数,加载完成后渲染组件。
// 引入 defineAsyncComponent
import { defineAsyncComponent } from 'vue'
// 定义异步组件(简单语法)
const AsyncDemo = defineAsyncComponent(() =>
// 动态 import 加载组件,返回 Promise
import('./components/AsyncDemo.vue')
)
// 在组件中正常使用
export default {
components: {
AsyncDemo
}
}
注意:动态 import() 是 ES 语法,会返回一个 Promise 对象,Vue 内部会自动处理 Promise 的成功与失败状态。
2. 完整配置语法(支持加载/错误状态等配置)
当需要自定义加载状态、错误处理、加载延迟等场景时,可传入一个配置对象,这也是实际开发中更常用的方式。完整配置包含 loader、loadingComponent、errorComponent、delay、timeout 等属性,后续将逐一详解。
const AsyncDemo = defineAsyncComponent({
// 加载函数(必选),同简单语法的加载函数
loader: () => import('./components/AsyncDemo.vue'),
// 加载中显示的组件
loadingComponent: Loading,
// 加载失败显示的组件
errorComponent: Error,
// 延迟显示加载组件的时间(毫秒)
delay: 200,
// 加载超时时间(毫秒)
timeout: 3000,
// 其他可选配置...
})
二、四大核心配置详解
下面针对完整配置中的四大核心属性(loading/error/delay/timeout),结合场景与实例逐一拆解,说明其作用、用法及注意事项。
1. loadingComponent:加载中状态组件
当异步组件正在加载时,Vue 会渲染 loadingComponent 指定的组件,用于提示用户“加载中”(如骨架屏、加载动画等)。
使用要点:
-
loadingComponent需是一个已定义的 Vue 组件,可全局注册或局部引入。 - 加载成功后,加载组件会自动被替换为目标异步组件。
- 若加载时间极短(如小于
delay配置的时间),加载组件可能不会显示,避免频繁切换导致的闪烁。
实例:
// 引入加载组件和错误组件
import Loading from './components/Loading.vue'
import Error from './components/Error.vue'
const AsyncDemo = defineAsyncComponent({
loader: () => import('./components/AsyncDemo.vue'),
// 加载中显示 Loading 组件
loadingComponent: Loading,
// 加载失败显示 Error 组件
errorComponent: Error
})
Loading.vue 示例(简单加载动画):
<template>
<div class="loading">
<span>加载中...</span>
</div>
</template>
<style scoped>
.loading {
text-align: center;
padding: 20px;
color: #666;
}
</style>
2. errorComponent:加载失败状态组件
当异步组件加载失败(如网络错误、组件路径错误)时,Vue 会渲染 errorComponent 指定的组件,用于提示用户加载失败,并可提供重试等交互。
使用要点:
- 加载失败的原因包括:网络中断、动态 import 路径错误、组件内部报错等。
- Vue 会向
errorComponent传递一个error属性,包含错误信息,可在组件中使用。 - 可在错误组件中提供“重试加载”按钮,通过调用
error.retry()重新触发加载函数;调用error.fail()标记加载失败(不再重试)。
实例(带重试功能的错误组件):
// Error.vue
<template>
<div class="error">
<p>组件加载失败:{{ error.message }}</p>
<button @click="error.retry()">重试加载</button>
<button @click="error.fail()">确认失败</button>
</div>
</template>
<script setup>
// 接收 Vue 传递的 error 属性
const props = defineProps({
error: {
type: Object,
required: true
}
})
</script>
<style scoped>
.error {
text-align: center;
padding: 20px;
color: #ff4d4f;
}
button {
margin: 0 8px;
padding: 4px 12px;
}
</style>
3. delay:延迟显示加载组件的时间
delay 用于设置“延迟多久后显示加载组件”,单位为毫秒(默认值为 200)。其核心作用是避免“加载组件闪烁”——若组件加载速度极快(如本地资源、缓存资源),加载组件仅显示几毫秒就消失,会给用户带来不良体验。
逻辑说明:
- 若组件加载时间 ≤
delay:不显示加载组件,直接渲染目标组件。 - 若组件加载时间 >
delay:从加载开始经过delay毫秒后,显示加载组件,直到加载完成或失败。
实例:
const AsyncDemo = defineAsyncComponent({
loader: () => import('./components/AsyncDemo.vue'),
loadingComponent: Loading,
errorComponent: Error,
// 延迟 300 毫秒显示加载组件,避免快速加载时的闪烁
delay: 300
})
4. timeout:加载超时时间
timeout 用于设置组件加载的超时时间,单位为毫秒(默认无超时限制)。若加载时间超过设定值,Vue 会判定为加载失败,渲染 errorComponent。
使用要点:
- 若网络环境较差,建议设置合理的超时时间(如 5000 毫秒),避免用户长时间等待无反馈。
- 超时后触发的错误,可通过错误组件的
error.retry()重试加载。 - 若需禁用超时限制,可设置
timeout: Infinity。
实例:
const AsyncDemo = defineAsyncComponent({
loader: () => import('./components/AsyncDemo.vue'),
loadingComponent: Loading,
errorComponent: Error,
delay: 300,
// 加载超时时间设为 5 秒,超过则显示错误组件
timeout: 5000
})
三、进阶用法与注意事项
1. 结合 Suspense 使用
Vue 3 的 Suspense 组件可与 defineAsyncComponent 配合,实现更灵活的异步组件控制。Suspense 提供 <template #default>(异步组件成功渲染内容)和 <template #fallback>(加载中内容),此时可省略 loadingComponent配置。
<template>
<Suspense>
<template #default>
<AsyncDemo />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent, Suspense } from 'vue'
const AsyncDemo = defineAsyncComponent(() => import('./components/AsyncDemo.vue'))
</script>
2. 动态控制加载函数
加载函数可根据条件动态返回不同的组件,实现“按需加载不同组件”的场景(如根据用户权限加载不同组件)。
const AsyncComponent = defineAsyncComponent(() => {
// 根据权限动态加载组件
if (userRole === 'admin') {
return import('./components/AdminComponent.vue')
} else {
return import('./components/UserComponent.vue')
}
})
3. 注意事项
- 异步组件不能直接在
<script setup>中通过import引入后立即使用,需通过defineAsyncComponent封装。 - 加载函数返回的 Promise 若被 reject,会触发加载失败,渲染错误组件。
- 生产环境中,动态 import() 会被打包工具(如 Vite、Webpack)分割为独立的代码块,实现真正的按需加载。
四、总结
defineAsyncComponent 是 Vue 3 优化应用性能的重要工具,通过基础加载函数实现组件懒加载,再结合 loading、error、delay、timeout 四大配置,可覆盖绝大多数异步组件的使用场景:loading 组件提升用户等待体验,error 组件处理加载异常,delay 避免组件闪烁,timeout 防止无限等待。
在实际开发中,建议根据组件的体积、加载场景(如首屏、弹窗)合理配置参数,结合 Suspense 组件实现更灵活的异步控制,让应用加载更快、体验更优。
Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(十一)
CSS3 星球大战:用纯 CSS 实现经典片头字幕动画
DOM-Offset:精准定位与尺寸测量的基石
贪心(Python/Java/C++/C/Go/JS/Rust)
为方便计算差值,先把 $\textit{nums}$ 从小到大排序。
把 $\textit{nums}$ 中的元素画在一维数轴上。如果 $\textit{nums}[i]$ 是 $k$ 个数中的最大值,那么最小值的下标至多为 $i-k+1$(要在最小值和最大值之间再选 $k-2$ 个数)。但最小值越小,差值越大,所以最小值的下标恰好为 $i-k+1$ 是最优的。
枚举最大值的下标 $i = k-1,k,k+1,\ldots, n-1$,计算差值 $\textit{nums}[i] - \textit{nums}[i-k+1]$ 的最大值,即为答案。
class Solution:
def minimumDifference(self, nums: List[int], k: int) -> int:
nums.sort()
n = len(nums)
return min(nums[i] - nums[i - k + 1] for i in range(k - 1, n))
class Solution:
def minimumDifference(self, nums: List[int], k: int) -> int:
nums.sort()
return min(mx - mn for mx, mn in zip(nums[k - 1:], nums))
class Solution {
public int minimumDifference(int[] nums, int k) {
Arrays.sort(nums);
int ans = Integer.MAX_VALUE;
for (int i = k - 1; i < nums.length; i++) {
ans = Math.min(ans, nums[i] - nums[i - k + 1]);
}
return ans;
}
}
class Solution {
public:
int minimumDifference(vector<int>& nums, int k) {
ranges::sort(nums);
int ans = INT_MAX;
for (int i = k - 1; i < nums.size(); i++) {
ans = min(ans, nums[i] - nums[i - k + 1]);
}
return ans;
}
};
#define MIN(a, b) ((b) < (a) ? (b) : (a))
int cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
int minimumDifference(int* nums, int numsSize, int k) {
qsort(nums, numsSize, sizeof(int), cmp);
int ans = INT_MAX;
for (int i = k - 1; i < numsSize; i++) {
ans = MIN(ans, nums[i] - nums[i - k + 1]);
}
return ans;
}
func minimumDifference(nums []int, k int) int {
slices.Sort(nums)
ans := math.MaxInt
for i := k - 1; i < len(nums); i++ {
ans = min(ans, nums[i]-nums[i-k+1])
}
return ans
}
var minimumDifference = function(nums, k) {
nums.sort((a, b) => a - b);
let ans = Infinity;
for (let i = k - 1; i < nums.length; i++) {
ans = Math.min(ans, nums[i] - nums[i - k + 1]);
}
return ans;
};
impl Solution {
pub fn minimum_difference(mut nums: Vec<i32>, k: i32) -> i32 {
nums.sort_unstable();
let k = k as usize;
let mut ans = i32::MAX;
for i in k - 1..nums.len() {
ans = ans.min(nums[i] - nums[i - k + 1]);
}
ans
}
}
复杂度分析
- 时间复杂度:$\mathcal{O}(n\log n)$,其中 $n$ 是 $\textit{nums}$ 的长度。瓶颈在排序上。
- 空间复杂度:$\mathcal{O}(1)$。忽略排序的栈开销。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府
Next.js第二十三章(Next配置文件)
next.config.js配置
本章会讲解Next.js的配置文件next.config.js的配置项。注:本章不会讲所有的配置项,只会讲使用率50%的配置项,以及项目中真实使用的配置项。
查看完整版配置项观看: next.config.js配置
根据不同环境进行配置
例如我想在开发环境配置 XXX,或者生产环境配置YYY,那么我们可以使用next/constants来判断当前环境。
//Next.js next/constants内置的常量
export declare const PHASE_EXPORT = "phase-export"; // 导出静态站点
export declare const PHASE_PRODUCTION_BUILD = "phase-production-build"; // 生产环境构建
export declare const PHASE_PRODUCTION_SERVER = "phase-production-server"; // 生产环境服务器
export declare const PHASE_DEVELOPMENT_SERVER = "phase-development-server"; // 开发环境服务器
export declare const PHASE_TEST = "phase-test"; // 测试环境
export declare const PHASE_INFO = "phase-info"; // 信息
我们要根据不同环境配置,需要返回一个函数,而不是直接返回一个对象,在函数中会接受一个参数phase,这个参数是Next.js的环境,我们可以根据这个参数来判断当前环境。
//next.config.ts
import { PHASE_DEVELOPMENT_SERVER, PHASE_TYPE } from 'next/constants'
import type { NextConfig } from 'next'
export default (phase: PHASE_TYPE): NextConfig => {
const nextConfig: NextConfig = {
reactCompiler: false,
}
if (phase === PHASE_DEVELOPMENT_SERVER) {
nextConfig.reactCompiler = true // 开发环境使用reactCompiler
}
//if() 其他环境.....
return nextConfig
}
Next.js配置端口号
这是Next.js很迷的一个操作,通过一般脚手架或者其他项目都会在配置文件进行配置端口号,但是Next.js却没有,而是在启动命令中进行配置。(默认是3000端口)
"scripts": {
"dev": "next dev -p 8888", // 开发环境端口号
"build": "next build",
"start": "next start -p 9999 " // 生产环境端口号
},
Next.js导出静态站点
需要在next.config.js文件中配置output为export,表示导出静态站点。distDir表示导出目录,默认为out。
具体用法请查看: 静态导出SSG
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
output: "export", // 导出静态站点
distDir: "dist", // 导出目录
trailingSlash: true, // 添加尾部斜杠,生成 /about/index.html 而不是 /about.html
};
export default nextConfig;
Next.js配置图片优化
Next.jsImage组件默认只允许加载本地图片,如果需要加载远程图片,需要配置next.config.js文件。
详细用法请查看: 图片优化
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [
{
protocol: 'https', // 协议
hostname: 'eo-img.521799.xyz', // 主机名
pathname: '/i/pc/**', // 路径
port: '', // 端口
},
],
formats: ['image/avif', 'image/webp'], //默认是 ['image/webp']
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], // 设备尺寸
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // 图片尺寸
},
};
自定义响应标头
例如配置CORS跨域,或者是自定义响应标头等,只要是http支持的响应头都可以配置。
HTTP响应头参考: HTTP响应头
const nextConfig: NextConfig = {
headers: () => {
return [
{
source: '/:path*', // 匹配路径 所有路径 也支持精准匹配 例如/api/user 包括支持动态路由等 /api/user/:id
headers: [
{
key: 'Access-Control-Allow-Origin', //允许跨域
value: '*' // 允许所有域名访问
},
{
key: 'Access-Control-Allow-Methods', //允许的请求方法
value: 'GET, POST, PUT, DELETE, OPTIONS' // 允许的请求方法
},
{
key: 'Access-Control-Allow-Headers', //允许的请求头
value: 'Content-Type, Authorization' // 允许的请求头
}
]
},
{
source: '/home', // 精准匹配 /home 路径
headers: [
{
key: 'X-Custom-Header', //自定义响应头
value: '123456' // 值
},
]
}
]
}
}
assetsPrefix配置
assetsPrefix配置用于配置静态资源前缀,例如:部署到CDN后,静态资源路径会发生变化,需要配置这个配置项。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
assetsPrefix: 'https://cdn.example.com', // 静态资源前缀
};
export default nextConfig;
未配置assetsPrefix时:
/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js
配置assetsPrefix后:
https://cdn.example.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js
basePath配置
应用前缀:也就是跳转路径中增加前缀,例如前缀是/docs,那么跳转/home就需要跳转到/docs/home。访问根目录也需要增加前缀,例如访问/就需要跳转到/docs。这儿可以使用重定向来实现。访问/自动跳转到/docs。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
basePath: '/docs', // 基础路径
redirects() {
return [
{
source: '/', // 源路径
destination: '/docs', // 目标路径
basePath: false, // 是否使用basePath 默认情况下 source 和 destination 都会自动加上 basePath 前缀 就变成了/docs/docs 所以这儿不需要增加
permanent: false, // 是否永久重定向
},
]
},
};
export default nextConfig;
如果使用link跳转的话,无需增加basePath前缀,因为Link组件会自动增加basePath前缀。
当他跳转/home时,会自动跳转到/docs/home。
import Link from 'next/link'
export default function Page() {
return (<div>
<h1>小满zs Page</h1>
<Link href="/home">Home</Link>
</div>)
}
compress
compress配置用于配置压缩,例如:压缩js/css/html等。默认情况是开启的,如果需要关闭,可以配置为false。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
compress: true, // 压缩
};
export default nextConfig;
日志配置
日志配置用于配置日志,例如:显示完整的URL等。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
logging:{
fetches: {
fullUrl: true, // 显示完整的URL
},
}
};
export default nextConfig;
页面扩展
默认情况下,Next.js 接受以下扩展名的文件:.tsx.js、 .js .ts、.jsx.md、.js.js。可以修改此设置以允许其他扩展名,例如 markdown(.md.md、.md .mdx)。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
};
export default nextConfig;
devIndicators
关闭调试指示器,默认情况下是开启的,如果需要关闭,可以配置为false。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
devIndicators: false, // 关闭开发指示器
// devIndicators:{
// position:'bottom-right', //也支持放入其他位置 bottom-right bottom-left top-right top-left
// },
};
export default nextConfig;
generateEtags
Next.js会为静态文件生成ETag,用于缓存控制。默认情况下是开启的,如果需要关闭,可以配置为false。
浏览器会根据ETag来判断文件是否发生变化,如果发生变化,则重新下载文件。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
generateEtags: false, // 关闭生成ETag 默认开启
};
export default nextConfig;
turbopack
Next.js已内置turbopack进行打包编译等操作,所以允许透传配置项给turbopack。
一般情况下是不需要做太多优化的,因为它都内置了例如tree-shaking、压缩 按需编译 语法降级 等优化。
具体用法请查看: turbopack
例如我们需要编译其他文件less配置如下:
npm i less-loader -D
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
turbopack:{
rules:{
'*.less':{
loaders:['less-loader'],
as:'*.css',
}
}
}
}
export default nextConfig;
每日一题-学生分数的最小差值🟢
给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。
从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分 和 最低分 的 差值 达到 最小化 。
返回可能的 最小差值 。
示例 1:
输入:nums = [90], k = 1 输出:0 解释:选出 1 名学生的分数,仅有 1 种方法: - [90] 最高分和最低分之间的差值是 90 - 90 = 0 可能的最小差值是 0
示例 2:
输入:nums = [9,4,1,7], k = 2 输出:2 解释:选出 2 名学生的分数,有 6 种方法: - [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5 - [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8 - [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2 - [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3 - [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3 - [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6 可能的最小差值是 2
提示:
1 <= k <= nums.length <= 10000 <= nums[i] <= 105
【宫水三叶】排序 + 滑动窗口运用题
排序 + 滑动窗口
从 $n$ 个元素里找 $k$ 个,使得 $k$ 个元素最大差值最小。
最大值最小化问题容易想到「二分」,利用答案本身具有「二段性」,来将原本的求解问题转化为判断定问题。
回到本题,容易证明,这 $k$ 个元素必然是有序数组中(排序后)的连续段。反证法,若最佳 $k$ 个选择不是连续段,能够调整为连续段,结果不会变差。
因此我们可以先对 $nums$ 进行排序,然后扫描所有大小为 $k$ 的窗口,直接找到答案,而无须使用「二分」。
代码(二分答案代码见 $P2$):
###Java
class Solution {
public int minimumDifference(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length, ans = nums[k - 1] - nums[0];
for (int i = k; i < n; i++) {
ans = Math.min(ans, nums[i] - nums[i - k + 1]);
}
return ans;
}
}
###Java
class Solution {
int[] nums; int k;
public int minimumDifference(int[] _nums, int _k) {
nums = _nums; k = _k;
Arrays.sort(nums);
int l = 0, r = 100010;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return r;
}
boolean check(int x) {
int n = nums.length, ans = nums[k - 1] - nums[0];
for (int i = k; i < n && ans > x; i++) {
ans = Math.min(ans, nums[i] - nums[i - k + 1]);
}
return ans <= x;
}
}
- 时间复杂度:排序复杂度为 $O(n\log{n})$;遍历得到答案复杂度为 $O(n)$。整体复杂度为 $O(n\log{n})$
- 空间复杂度:$O(\log{n})$
其他「滑动窗口」内容
题太简单?来看一道 更贴合笔试/面试的滑动窗口综合题 🎉 🎉
或是加练其他「滑动窗口」内容 🍭🍭🍭
| 题目 | 题解 | 难度 | 推荐指数 |
|---|---|---|---|
| 3. 无重复字符的最长子串 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩🤩 |
| 30. 串联所有单词的子串 | LeetCode 题解链接 | 困难 | 🤩🤩 |
| 187. 重复的DNA序列 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩 |
| 219. 存在重复元素 II | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩 |
| 424. 替换后的最长重复字符 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩 |
| 438. 找到字符串中所有字母异位词 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩 |
| 480. 滑动窗口中位数 | LeetCode 题解链接 | 困难 | 🤩🤩🤩🤩 |
| 567. 字符串的排列 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
| 594. 最长和谐子序列 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩 |
| 643. 子数组最大平均数 I | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
| 992. K 个不同整数的子数组 | LeetCode 题解链接 | 困难 | 🤩🤩🤩🤩 |
| 1004. 最大连续1的个数 III | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
| 1052. 爱生气的书店老板 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
| 1208. 尽可能使字符串相等 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
| 1423. 可获得的最大点数 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩 |
| 1438. 绝对差不超过限制的最长连续子数组 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
| 1610. 可见点的最大数目 | LeetCode 题解链接 | 困难 | 🤩🤩🤩🤩 |
| 1838. 最高频元素的频数 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
| 注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。 |
最后
如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/
也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
所有题解已经加入 刷题指南,欢迎 star 哦 ~
[Python/Java/JavaScript/Go] 排序+双指针滑窗
解题思路
排序后我们要选k个数达到最大最小的差尽可能小,必然是连续的长度为k的子数组的选法,而差值就是最右边的元素减去最左边的元素。
遍历返回其中的最小值即可。
代码
###Python3
class Solution:
def minimumDifference(self, nums: List[int], k: int) -> int:
return min(s[i + k - 1] - s[i] for i in range(len(s) - k + 1)) if k > 1 and (s:=sorted(nums)) else 0
###Java
class Solution {
public int minimumDifference(int[] nums, int k) {
if(k == 1)
return 0;
Arrays.sort(nums);
int ans = 100005;
for(int i = 0; i <= nums.length - k; i++)
ans = Math.min(ans, nums[i + k - 1] - nums[i]);
return ans;
}
}
###JavaScript
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var minimumDifference = function(nums, k) {
if(k == 1)
return 0
nums.sort((a,b)=>a-b)
let ans = 100005
for(let i = 0; i <= nums.length - k; i++)
ans = Math.min(ans, nums[i + k - 1] - nums[i])
return ans
};
###Go
func minimumDifference(nums []int, k int) int {
if k == 1 {
return 0
}
sort.Ints(nums)
ans := 100005
for i := 0; i <= len(nums) - k; i++ {
ans = min(ans, nums[i + k - 1] - nums[i])
}
return ans
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
学生分数的最小差值
方法一:排序
思路与算法
要想最小化选择的 $k$ 名学生中最高分和最低分的差值,我们一定是在排好序后的数组中连续地进行选择。这是因为在选择时,如果跳过了某个下标 $i$,那么在选择完毕后,将其中的最高分替换成 $\textit{nums}[i]$,最高分一定不会变大,与最低分的差值同样也不会变大。因此,一定存在有一种最优的选择方案,是连续选择了有序数组中的 $k$ 个连续的元素。
这样一来,我们首先对数组 $\textit{nums}$ 进行升序排序,随后使用一个大小固定为 $k$ 的滑动窗口在 $\textit{nums}$ 上进行遍历。记滑动窗口的左边界为 $i$,那么右边界即为 $i+k-1$,窗口中的 $k$ 名学生最高分和最低分的差值即为 $\textit{nums}[i+k-1] - \textit{nums}[i]$。
最终的答案即为所有 $\textit{nums}[i+k-1] - \textit{nums}[i]$ 中的最小值。
代码
###C++
class Solution {
public:
int minimumDifference(vector<int>& nums, int k) {
int n = nums.size();
sort(nums.begin(), nums.end());
int ans = INT_MAX;
for (int i = 0; i + k - 1 < n; ++i) {
ans = min(ans, nums[i + k - 1] - nums[i]);
}
return ans;
}
};
###Java
class Solution {
public int minimumDifference(int[] nums, int k) {
int n = nums.length;
Arrays.sort(nums);
int ans = Integer.MAX_VALUE;
for (int i = 0; i + k - 1 < n; ++i) {
ans = Math.min(ans, nums[i + k - 1] - nums[i]);
}
return ans;
}
}
###C#
public class Solution {
public int MinimumDifference(int[] nums, int k) {
int n = nums.Length;
Array.Sort(nums);
int ans = int.MaxValue;
for (int i = 0; i + k - 1 < n; ++i) {
ans = Math.Min(ans, nums[i + k - 1] - nums[i]);
}
return ans;
}
}
###Python
class Solution:
def minimumDifference(self, nums: List[int], k: int) -> int:
nums.sort()
return min(nums[i + k - 1] - nums[i] for i in range(len(nums) - k + 1))
###C
#define MIN(a, b) ((a) < (b) ? (a) : (b))
int cmp(const void * pa, const void *pb) {
return *(int *)pa - *(int *)pb;
}
int minimumDifference(int* nums, int numsSize, int k){
qsort(nums, numsSize, sizeof(int), cmp);
int ans = INT_MAX;
for (int i = 0; i + k - 1 < numsSize; ++i) {
ans = MIN(ans, nums[i + k - 1] - nums[i]);
}
return ans;
}
###JavaScript
var minimumDifference = function(nums, k) {
const n = nums.length;
nums.sort((a, b) => a - b);
let ans = Number.MAX_SAFE_INTEGER;
for (let i = 0; i < n - k + 1; i++) {
ans = Math.min(ans, nums[i + k - 1] - nums[i]);
}
return ans;
};
###go
func minimumDifference(nums []int, k int) int {
sort.Ints(nums)
ans := math.MaxInt32
for i, num := range nums[:len(nums)-k+1] {
ans = min(ans, nums[i+k-1]-num)
}
return ans
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
复杂度分析
-
时间复杂度:$O(n \log n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。排序需要的时间为 $O(n \log n)$,后续遍历需要的时间为 $O(n)$。
-
空间复杂度:$O(\log n)$,即为排序需要使用的栈空间。
Next.js 页面导航深度解析:Link 组件的全面指南
1.8GB 内存也能跑大模型!Ollama Docker 部署完整指南
想在服务器上部署私有 AI 模型,但内存不够用?本文教你用 Docker + Swap 优化,让低配服务器也能流畅运行 Ollama 大模型。
背景
为什么选择 Docker 部署?
因为直接使用命令会报错,无法运行ollama。
1. 简介
1.1 为什么使用 Docker 部署?
| 优势 | 说明 |
|---|---|
| 环境隔离 | 不污染宿主机环境,依赖问题少 |
| 一键部署 | 容器化部署,跨平台一致性好 |
| 易于管理 | 重启、更新、迁移方便 |
| 资源控制 | 可限制内存、CPU 使用 |
| 适合生产 | 稳定可靠,推荐生产环境使用 |
1.2 硬件要求
| 模型规模 | 内存要求 | 推荐配置 |
|---|---|---|
| 0.5B-3B | 2-4GB | 最低 2GB 可用内存 |
| 7B-14B | 8-16GB | 最低 8GB 可用内存 |
| 30B+ | 32GB+ | 最低 32GB 可用内存 |
1.3 低配服务器(<2GB 内存)
如果你的服务器内存不足(如 1GB-2GB),运行大模型会遇到以下错误:
Error: 500 Internal Server Error: llama runner process has terminated: signal: killed
什么是 Swap?
Swap 是 Linux 系统中的一块硬盘空间,当作"备用内存"使用。当物理内存(RAM)不够用时,系统会把暂时不用的数据从内存搬到 Swap 中,腾出物理内存给需要运行的程序。
┌─────────────────────────────────────────────────┐
│ 物理内存 (RAM) = 你的办公桌(快速但小) │
│ Swap (虚拟内存) = 旁边的储物柜(慢但大) │
│ │
│ 当办公桌放满东西时: │
│ 把不常用的文件 → 放到储物柜 (Swap) │
│ 腾出空间 → 放置正在处理的文件 │
└─────────────────────────────────────────────────┘
Swap 的作用
| 作用 | 说明 |
|---|---|
| 防止系统崩溃 | 内存不足时,用 Swap 补充,避免进程被杀死 |
| 运行大程序 | 允许运行超出物理内存的程序(如大语言模型) |
| 内存回收 | 把不活跃的内存页面移到 Swap,释放物理内存 |
为什么需要 Swap?
你的服务器配置:
- 物理内存:1.8GB
- 想运行:3b 模型(需要 ~4GB 内存)
没有 Swap:
1.8GB < 4GB → 程序被杀死 ❌
有 5GB Swap:
1.8GB + 5GB = 6.8GB > 4GB → 可以运行 ✅
注意:使用 Swap 会牺牲性能(硬盘速度约为内存的 1/100),但总比程序崩溃好。
添加 Swap 虚拟内存
# 创建 4GB swap 文件
dd if=/dev/zero of=/swapfile bs=1M count=4096
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# 永久生效
echo '/swapfile none swap sw 0 0' >> /etc/fstab
# 验证
free -h
不同内存配置的模型推荐
| 服务器内存 | 推荐模型 | Swap 需求 |
|---|---|---|
| 1GB | qwen2.5-coder:0.5b | 建议 2GB |
| 2GB | qwen2.5-coder:0.5b / 1.5b | 建议 3GB |
| 4GB | qwen2.5-coder:3b | 不需要 |
| 8GB+ | qwen2.5-coder:7b | 不需要 |
Swap 性能判断
| Swap 使用量 | 状态 | 建议 |
|---|---|---|
| 0-500MB | 正常 | 无需处理 |
| 500MB-1GB | 一般 | 注意性能 |
| 1GB-2GB | 较慢 | 考虑换小模型 |
| >2GB | 很慢 | 必须换小模型 |
内存监控命令
# 查看当前内存和 Swap 状态
free -h
# 实时监控内存(每 1 秒刷新)
watch -n 1 free -h
# 查看 Docker 容器资源使用
docker stats ollama
# 查看容器内存限制
docker inspect ollama | grep -i memory
# 查看系统内存配置
cat /proc/sys/vm/overcommit_memory
# 0 = 启发式过度分配(默认)
# 1 = 始终允许过度分配
# 2 = 严格控制,不允许过度分配
运行模型时实时监控
开启两个终端窗口:
终端 1:运行模型
docker exec -it ollama ollama run qwen2.5-coder:0.5b
终端 2:实时监控
watch -n 1 'free -h && echo "---" && docker stats ollama --no-stream'
常见问题排查
问题:模型运行时被杀死
# 1. 检查容器内存限制
docker inspect ollama | grep -i memory
# 2. 如果有内存限制,重新创建容器
docker rm -f ollama
docker run -d \
-p 11434:11434 \
--name ollama \
--restart always \
--memory-swap=-1 \
ollama/ollama:latest
# 3. 启用内存过度分配
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
echo 'vm.overcommit_memory = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# 4. 重启容器
docker restart ollama
问题:Swap 使用过高导致卡顿
# 查看当前 Swap 使用
free -h
# 如果 Swap 使用 > 1GB,建议切换到更小的模型
docker exec -it ollama ollama run qwen2.5-coder:0.5b
2. 安装 Docker
2.1 Ubuntu/Debian
# 一键安装 Docker
curl -fsSL https://get.docker.com | sh
# 将当前用户加入 docker 组(免 sudo)
sudo usermod -aG docker $USER
# 重新登录或执行以下命令使组权限生效
newgrp docker
# 验证安装
docker --version
2.2 CentOS/RHEL
# 安装 Docker
sudo yum install -y docker
# 启动 Docker 服务
sudo systemctl start docker
sudo systemctl enable docker
# 将当前用户加入 docker 组
sudo usermod -aG docker $user
# 验证安装
docker --version
2.3 验证 Docker 安装
# 运行测试容器
docker run hello-world
# 查看 Docker 版本
docker --version
docker info
3. 部署 Ollama 容器
3.1 拉取镜像
# 拉取最新版 Ollama 镜像
docker pull ollama/ollama:latest
# 或指定版本
docker pull ollama/ollama:0.5.7
3.2 启动容器
CPU 模式(默认):
docker run -d \
-p 11434:11434 \
--name ollama \
--restart always \
ollama/ollama:latest
GPU 模式(需要 NVIDIA GPU):
# 首先安装 NVIDIA Container Toolkit
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker
# 启动带 GPU 的容器
docker run -d \
--gpus all \
-p 11434:11434 \
--name ollama \
--restart always \
ollama/ollama:latest
3.3 验证容器运行
# 查看容器状态
docker ps
# 查看容器日志
docker logs -f ollama
# 测试 API
curl http://localhost:11434/api/tags
4. 模型管理
4.1 拉取模型
# 拉取 qwen2.5-coder:3b
docker exec -it ollama ollama pull qwen2.5-coder:3b
# 拉取其他模型
docker exec -it ollama ollama pull qwen2.5:7b
docker exec -it ollama ollama pull deepseek-r1:7b
4.2 查看已安装模型
docker exec -it ollama ollama list
4.3 运行模型(交互式)
docker exec -it ollama ollama run qwen2.5-coder:3b
4.4 删除模型
docker exec -it ollama ollama rm qwen2.5-coder:3b
4.5 推荐模型
| 模型 | 用途 | 内存需求 |
|---|---|---|
qwen2.5-coder:0.5b |
代码生成(轻量) | ~1GB |
qwen2.5-coder:3b |
代码生成(推荐) | ~4GB |
qwen2.5-coder:7b |
代码生成(专业) | ~8GB |
qwen2.5:3b |
通用对话 | ~4GB |
qwen2.5:7b |
通用对话(推荐) | ~8GB |
5. API 调用
5.1 基础调用格式
# 生成文本
curl http://localhost:11434/api/generate -d '{
"model": "qwen2.5-coder:3b",
"prompt": "用python写一个快速排序",
"stream": false
}'
# 对话模式
curl http://localhost:11434/api/chat -d '{
"model": "qwen2.5-coder:3b",
"messages": [
{"role": "user", "content": "你好"}
],
"stream": false
}'
5.2 参数说明
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| model | string | 模型名称 | - |
| prompt | string | 输入文本 | - |
| stream | boolean | 是否流式输出 | true |
| temperature | number | 温度(0-1),越高越随机 | 0.8 |
| num_ctx | number | 上下文长度 | 2048 |
5.3 Python 调用示例
import requests
API_URL = "http://localhost:11434/api/generate"
def call_ollama(prompt: str, model: str = "qwen2.5-coder:3b"):
response = requests.post(API_URL, json={
"model": model,
"prompt": prompt,
"stream": False
})
return response.json()["response"]
# 使用
result = call_ollama("用python写一个快速排序")
print(result)
5.4 JavaScript 调用示例
浏览器环境(原生 Fetch)
// 非流式响应
async function callOllama(prompt) {
const response = await fetch("http://localhost:11434/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "qwen2.5-coder:3b",
prompt: prompt,
stream: false
})
});
const data = await response.json();
return data.response;
}
// 使用
callOllama("用python写一个快速排序").then(console.log);
流式响应(浏览器)
async function chatWithOllama(prompt) {
const response = await fetch("http://localhost:11434/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "qwen2.5-coder:3b",
messages: [{ role: "user", content: prompt }],
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n").filter(line => line.trim());
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") continue;
try {
const json = JSON.parse(data);
const content = json.choices?.[0]?.delta?.content;
if (content) {
result += content;
console.log(content); // 实时输出
}
} catch (e) {
// 忽略解析错误
}
}
}
}
return result;
}
// 使用
chatWithOllama("用python写一个快速排序");
Node.js 环境
const axios = require("axios");
async function callOllama(prompt) {
const response = await axios.post(
"http://localhost:11434/api/generate",
{
model: "qwen2.5-coder:3b",
prompt: prompt,
stream: false
}
);
return response.data.response;
}
// 使用
callOllama("用python写一个快速排序").then(console.log);
带认证的调用
// 如果设置了 API 密钥
async function callOllamaWithAuth(prompt) {
const response = await fetch("http://localhost:11434/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your_api_key_here"
},
body: JSON.stringify({
model: "qwen2.5-coder:3b",
prompt: prompt,
stream: false
})
});
const data = await response.json();
return data.response;
}
5.5 OpenAI 兼容格式(JavaScript)
// 使用 OpenAI SDK 调用 Ollama
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: "http://localhost:11434/v1",
apiKey: "ollama" // 不需要真实 key
});
async function chat(prompt) {
const response = await client.chat.completions.create({
model: "qwen2.5-coder:3b",
messages: [{ role: "user", content: prompt }]
});
return response.choices[0].message.content;
}
// 使用
chat("用python写一个快速排序").then(console.log);
5.6 外网调用示例
// 如果配置了外网访问(需要 HTTPS + API Key)
async function callOllamaRemote(prompt) {
const response = await fetch("https://your-domain.com/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your_secure_password"
},
body: JSON.stringify({
model: "qwen2.5-coder:3b",
prompt: prompt,
stream: false
})
});
const data = await response.json();
return data.response;
}
6. 容器管理
6.1 查看容器状态
# 查看运行中的容器
docker ps
# 查看所有容器(包括停止的)
docker ps -a
# 查看容器详细信息
docker inspect ollama
6.2 日志管理
# 查看实时日志
docker logs -f ollama
# 查看最近 100 行日志
docker logs --tail 100 ollama
# 查看带时间戳的日志
docker logs -t ollama
6.3 启停重启
# 停止容器
docker stop ollama
# 启动容器
docker start ollama
# 重启容器
docker restart ollama
# 删除容器(需先停止)
docker rm -f ollama
6.4 进入容器
# 进入容器 shell
docker exec -it ollama bash
# 在容器中执行命令
docker exec -it ollama ollama list
7. 进阶配置
7.1 持久化模型存储
默认情况下,模型存储在容器内部,删除容器后模型会丢失。使用挂载卷持久化:
# 删除旧容器
docker rm -f ollama
# 重新创建,挂载本地目录
docker run -d \
-p 11434:11434 \
-v ollama_data:/root/.ollama \
--name ollama \
--restart always \
ollama/ollama:latest
7.2 资源限制
# 限制内存使用为 4GB
docker run -d \
-p 11434:11434 \
--memory=4g \
--name ollama \
--restart always \
ollama/ollama:latest
# 限制 CPU 使用
docker run -d \
-p 11434:11434 \
--cpus=2.0 \
--name ollama \
--restart always \
ollama/ollama:latest
7.3 环境变量配置
docker run -d \
-p 11434:11434 \
-e OLLAMA_HOST=0.0.0.0:11434 \
-e OLLAMA_NUM_PARALLEL=4 \
-e OLLAMA_DEBUG=0 \
-v ollama_data:/root/.ollama \
--name ollama \
--restart always \
ollama/ollama:latest
7.4 使用 Docker Compose
创建 docker-compose.yml:
version: '3.8'
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
environment:
- OLLAMA_HOST=0.0.0.0:11434
- OLLAMA_NUM_PARALLEL=4
restart: always
# GPU 配置(需要 nvidia-docker)
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
volumes:
ollama_data:
启动:
docker-compose up -d
7.5 国内镜像加速
# 使用国内镜像源
docker pull registry.cn-hangzhou.aliyuncs.com/ollama/ollama:latest
# 或使用代理
docker pull ollama/ollama:latest
8. 故障排查
8.1 容器启动失败
# 查看容器日志
docker logs ollama
# 常见错误:GPU 配置问题
# 解决方案:删除容器,使用 CPU 模式重新创建
docker rm -f ollama
docker run -d -p 11434:11434 --name ollama --restart always ollama/ollama:latest
8.2 无法访问 API
# 检查容器是否运行
docker ps
# 检查端口是否正确映射
docker port ollama
# 测试容器内部 API
docker exec ollama curl http://localhost:11434/api/tags
# 检查防火墙
sudo ufw status # Ubuntu
sudo firewall-cmd --list-all # CentOS
8.3 模型加载慢
# 查看资源使用情况
docker stats ollama
# 检查磁盘 IO
docker exec ollama df -h
8.4 内存不足
# 查看容器资源使用
docker stats --no-stream
# 使用更小的模型
docker exec -it ollama ollama pull qwen2.5-coder:0.5b
# 或限制容器内存
docker update --memory=4g ollama
9. 生产部署建议
9.1 安全配置
# 绑定到本地地址
docker run -d \
-p 127.0.0.1:11434:11434 \
--name ollama \
ollama/ollama:latest
# 使用反向代理(Nginx)配置 HTTPS
9.2 监控配置
# 使用 Prometheus + Grafana 监控
docker run -d \
--name prometheus \
-p 9090:9090 \
prom/prometheus
# 配置 cAdvisor 监控容器
docker run -d \
--name cadvisor \
-p 8080:8080 \
google/cadvisor:latest
9.3 高可用配置
# 使用负载均衡
# 部署多个 Ollama 实例,通过 Nginx 负载均衡
# 使用健康检查
docker run -d \
--name ollama \
--health-cmd="curl -f http://localhost:11434/api/tags || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-retries=3 \
ollama/ollama:latest
10. 常用命令速查
# 拉取模型
docker exec -it ollama ollama pull qwen2.5-coder:3b
# 查看模型列表
docker exec -it ollama ollama list
# 运行模型
docker exec -it ollama ollama run qwen2.5-coder:3b
# 查看日志
docker logs -f ollama
# 重启容器
docker restart ollama
# 进入容器
docker exec -it ollama bash
# 删除容器
docker rm -f ollama
# 测试 API
curl http://localhost:11434/api/tags