当前位置: 首页 > article >正文

自己手写tomcat项目

一:Servlet的原理

在Servlet(接口中)有:

1.init():初始化servlet

2.getServletConfig():获取当前servlet的配置信息

3.service():服务器(在HttpServlet中实现,目的是为了更好的匹配http的请求方式)

4.getServletInfo():获取servlet当前运行过程中的信息

5.destroy():销毁,回收内存

在I/O中应包含:1.请求头(f12+网络) 2.请求方式(get/post) 3.请求内容

get请求:将请求的内容放在url中(相对不安全),url长度有限(导致发送的内容不能太长),get请求做查询

post请求:请求内容放在请求体当中,无法看到(相对安全),一般用来做文件上传、下载,post请求做增删改操作

在ServletRequest中应该有:1.method:请求方式 2.编码方式 3.parmater 4.url 5.cookie

在ServletResponse中应该有:1.状态码 2.编码方式 3.字符集 4.data数据

每一个和外界进行通讯的进程(端口号0~65535)

端口号区分当前的进程

serverSocket.accept():阻塞监听(停在这里等到程序的到来)

二:tomcat原理

下面用一个图给大家展示

注解:给程序看的(@webservlet等等)

注释:给人看得(//)

实现自定义注解:需要使用jdk提供元注解(主要使用前两个)

1.@Target注解(用来描述注解的使用范围)

2.@Retention注解(表示这个注解在什么时候还有效,用于描述注解的生命周期)

3.@Documented注解

4.@Inherited注解 这四个帮助实现自定义注解

前两个的使用方法

1.

2.

利用上面两个就能够实现自定义注解(下面就是实现)

三、手写tomcat

首先先创建idea项目(名称和位置可以随意)

在创建好项目之后会有一个.java文件(可删可不删)

之后在创建下面几个软件包和MyTomcat文件(MyTomcat和刚开始有的那个java文件不一样)

之后在每个软件包中创建好java文件和注解文件、接口文件(其中zj软件包中的注解文件可用自己的姓名起,因为是自己手写的tomcat文件)

创建完成之后,首先在HttpServletRequest和HttpServletResponse中分别写

package com.qcby.request;public class HttpServletRequest {private String method;private String url;public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}
}
package com.qcby.response;import com.qcby.request.HttpServletRequest;import java.io.IOException;
import java.io.OutputStream;public class HttpServletResponse {private OutputStream outputStream;public HttpServletResponse(OutputStream outputStream){this.outputStream = outputStream;}public  void writeServlet(String context) throws IOException {outputStream.write(context.getBytes());}
}

写好之后就可以在写servlet软件包中的内容了

这是接口

package com.qcby.servlet;import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;import java.io.IOException;public interface servlet {public void service(HttpServletRequest request, HttpServletResponse response) throws IOException;
}

这是java

package com.qcby.servlet;import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;import java.io.IOException;public abstract class HttpServlet implements servlet{public void doPost(HttpServletRequest request, HttpServletResponse response) {}public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {}@Overridepublic void service(HttpServletRequest request, HttpServletResponse response) throws IOException {if(request.getMethod().equals("GET")){doGet(request,response);}else if(request.getMethod().equals("POST")){doPost(request,response);}}
}

写关于自己的注解

package com.qcby.zj;import java.lang.annotation.*;@Target(ElementType.TYPE)//该注解用在类上面
@Retention(RetentionPolicy.RUNTIME)//在运行期间表达
public @interface LRHServlet {String url();
}

之后写myweb中的

package com.qcby.myweb;import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import com.qcby.servlet.HttpServlet;
import com.qcby.util.ResponseUtil;
import com.qcby.zj.LRHServlet;import java.io.IOException;@LRHServlet(url="/myservlet")
public class MyFirstServlet extends HttpServlet {@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {System.out.println("hello word");response.writeServlet(ResponseUtil.getResponseHeader200("First hello word"));}@Overridepublic void doPost(HttpServletRequest request,HttpServletResponse response){}
}
package com.qcby.myweb;import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import com.qcby.servlet.HttpServlet;
import com.qcby.zj.LRHServlet;@LRHServlet(url="/insert")
public class insertServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response){System.out.println("I am insert");}@Overridepublic void doPost(HttpServletRequest request,HttpServletResponse response){}
}

util中放的是工具类,可直接复制

package com.qcby.util;import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;/*** 扫描指定包,获取该包下所有的类的全路径信息*/
public class SearchClassUtil {/*** 扫描指定包路径下的所有类* @param basePack 需要扫描的包名* @return 类的全路径列表*/public static List<String> searchClass(String basePack) {//创建存储类路径的列表List<String> classPaths = new ArrayList<>();try {// 获取类加载器(用于查找类路径资源)ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 将包名转换为路径格式(如 com.qcby.test)转换为路径格式(com/qcby/test)String path = basePack.replace('.', '/');// 获取资源枚举通过类加载器获取该路径下的所有资源(可能来自文件系统或 JAR 包)Enumeration<URL> resources = classLoader.getResources(path);//resources 是通过类加载器获取的资源枚举(包含文件系统和 JAR 包中的资源)while (resources.hasMoreElements()) {//每次循环处理一个资源 URLURL resource = resources.nextElement();// 判断资源类型(文件系统或JAR包)if (resource.getProtocol().equalsIgnoreCase("file")) {// 处理文件系统中的类//获取 URL 中的路径部分String filePath = resource.getPath();//解码特殊字符filePath = java.net.URLDecoder.decode(filePath, "UTF-8");//调用 findAndAddClassesInPackageByFile() 递归扫描目录findAndAddClassesInPackageByFile(basePack, filePath, classPaths);} else if (resource.getProtocol().equalsIgnoreCase("jar")) {// 处理JAR包中的类//从 JAR 资源 URL 中提取实际的 JAR 文件路径。跳过前缀 jar:file:(长度为 5)。//indexOf("!"):找到 ! 的位置,截取到此前的部分,得到 JAR 文件的路径String jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!"));//解码 URL 编码的特殊字符jarPath = java.net.URLDecoder.decode(jarPath, "UTF-8");//打开 JAR 文件,扫描指定包下的所有类文件。findAndAddClassesInPackageByJar(basePack, jarPath, classPaths);}}} catch (Exception e) {e.printStackTrace();}return classPaths;}/*** 从文件系统中查找类*/private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, List<String> classPaths) {//创建文件对象将传入的路径字符串转换为 File 对象,用于文件系统操作。File dir = new File(packagePath);//确保当前路径是一个存在且有效的目录。if (!dir.exists() || !dir.isDirectory()) {return;}// 获取目录下的所有文件和子目录,并过滤出以下两类File[] dirfiles = dir.listFiles(file ->// 文件夹或.class文件//使用 Lambda 表达式作为 listFiles 的过滤器,简洁地指定筛选条件。file.isDirectory() || file.getName().endsWith(".class"));//目录无法访问(如权限不足)或为空时,listFiles 可能返回 null,此处直接跳过。if (dirfiles == null) {return;}// 遍历所有文件for (File file : dirfiles) {if (file.isDirectory()) {// 递归处理子目录findAndAddClassesInPackageByFile(packageName + "." + file.getName(), //包名file.getAbsolutePath(),   //文件或目录的绝对路径classPaths    //类路径集合(用于储存结果));} else {// 处理class文件String className = file.getName().substring(0, file.getName().length() - 6);classPaths.add(packageName + '.' + className);}}}/*** 从JAR包中查找类*/private static void findAndAddClassesInPackageByJar(String packageName, String jarPath, List<String> classPaths) {//尝试打开 JAR 文件(资源自动关闭)try (JarFile jar = new JarFile(jarPath)) {//获取 JAR 文件中所有条目的枚举Enumeration<JarEntry> entries = jar.entries();//逐个处理 JAR 中的每个条目。while (entries.hasMoreElements()) {JarEntry entry = entries.nextElement();String name = entry.getName();// 判断是否是类文件并且在指定包路径下//确保只提取指定包及其子包下的类文件。if (name.endsWith(".class") && name.startsWith(packageName.replace('.', '/'))) {//提取类全限定名去除 .class 后缀:String className = name.substring(0, name.length() - 6).replace('/', '.');//添加类名到结果列表classPaths.add(className);}}} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {// 测试示例//测试扫描指定包List<String> classes = searchClass("com.qcby.myweb");//遍历并打印结果for (String className : classes) {System.out.println(className);}}public static List<String> searchClass() {return null;}
}
package com.qcby.util;public class ResponseUtil {public  static  final String responseHeader200 = "HTTP/1.1 200 \r\n"+"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n";public static String getResponseHeader404(){return "HTTP/1.1 404 \r\n"+"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + "404";}public static String getResponseHeader200(String context){return "HTTP/1.1 200 \r\n"+"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + context;}
}

在config中写入

package com.qcby.config;import com.qcby.servlet.HttpServlet;
import com.qcby.util.SearchClassUtil;
import com.qcby.zj.LRHServlet;import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;/*** tomcat路由*/
public class TomcatRoute {//HashMap<String, HttpServlet> HttpServlet向上转型(使用多态),因为myweb中的父类全是HttpServlet//将public static HashMap<String, HttpServlet> routes = new HashMap<>();写入static{}中会导致无法调用public static HashMap<String, HttpServlet> routes = new HashMap<>();static{// 传入要扫描的包名List<String> paths = SearchClassUtil.searchClass("com.qcby.myweb");for(String path : paths){try{//urlClass clazz = Class.forName(path);LRHServlet webServlet = (LRHServlet) clazz.getDeclaredAnnotation(LRHServlet.class);routes.put(webServlet.url(), (HttpServlet) clazz.getDeclaredConstructor().newInstance());System.out.println(webServlet.url());
//                if (webServlet != null) {
//                    System.out.println(webServlet.url());
//                }//对象clazz.getDeclaredConstructor().newInstance();}catch(ClassNotFoundException e){e.printStackTrace();} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}
}

最后就是直接的MyTomcat

package com.qcby;import com.qcby.config.TomcatRoute;
import com.qcby.request.HttpServletRequest;
import com.qcby.response.HttpServletResponse;
import com.qcby.servlet.HttpServlet;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;/*** tomcat主启动类*/
public class MyTomcat {static HashMap<String, HttpServlet> routes = TomcatRoute.routes;static HttpServletRequest request = new HttpServletRequest();/*** 分发器*/public static void dispatch(HttpServletResponse response) throws IOException {HttpServlet servlet = routes.get(request.getUrl());System.out.println(servlet);if (servlet != null) {servlet.service(request, response);}}public static void start() throws IOException {System.out.append("服务器端启动...");//1.定义ServerSocket对象进行服务器端的端口注册ServerSocket serverSocket = new ServerSocket(8080);while (true) {//2.监听客户端的Socket链接程序Socket socket = serverSocket.accept(); // 阻塞监听//3.打开输入流,解析客户端发来的内容InputStream inputStream = socket.getInputStream();//输入流BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));//将字节流转换成字符流String str = reader.readLine();request.setMethod(str.split("\\s")[0]);request.setUrl(str.split("\\s")[1]);//4.打开输出流OutputStream outputStream = socket.getOutputStream();HttpServletResponse response = new HttpServletResponse(outputStream);dispatch(response);}}public static void main(String[] args) throws IOException {start();}
}

这样就完成三分之二了

之后运行MyTomcat程序后,回显示com.qcby.myweb下的所有软件包名,和服务器端启动的字样

在浏览器的地址栏里输入(localhost:8080/myservlet)这里的8080要根据你实际写的端口号是多少写多少(myservlet要写自己定义的url)

在浏览器中就能够看到下面写在MyFirstServlet中的内容

在控制台能够看到MyFirstservlet中打印的内容

这样一个简单的tomcat就写好了

相关文章:

自己手写tomcat项目

一&#xff1a;Servlet的原理 在Servlet(接口中)有&#xff1a; 1.init():初始化servlet 2.getServletConfig()&#xff1a;获取当前servlet的配置信息 3.service():服务器&#xff08;在HttpServlet中实现&#xff0c;目的是为了更好的匹配http的请求方式&#xff09; 4.g…...

2025年渗透测试面试题总结-安恒[实习]安全工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 安恒[实习]安全工程师 一面 1. 自我介绍 2. 前两段实习做了些什么 3. 中等难度的算法题 4. Java的C…...

生成对抗网络(Generative Adversarial Networks ,GAN)

生成对抗网络是深度学习领域最具革命性的生成模型之一。 一 GAN框架 1.1组成 构造生成器&#xff08;G&#xff09;与判别器&#xff08;D&#xff09;进行动态对抗&#xff0c;实现数据的无监督生成。 G&#xff08;造假者&#xff09;&#xff1a;接收噪声 ​&#xff0c…...

六、磁盘划分与磁盘配额

目录 1、磁盘划分1.1、什么是磁盘1.2、机械硬盘的结构与关键概念1.3、思考:为什么新买一个1T硬盘,使用时发现可使用容量低于1T1.4、Linux中inode和block1.5、查看超级快信息1.6、磁盘分区与挂载1.6.1、分区工具fdisk与格式化1.6.2、分区工具gdisk与格式化1.7、查看磁盘使用情…...

在WSL中的Ubuntu发行版上安装Anaconda、CUDA、CUDNN和TensorRT

在Windows 11的WSL&#xff08;Windows Subsystem for Linux&#xff09;环境中安装Anaconda、CUDA、CUDNN和TensorRT的详细步骤整理&#xff1a; 本文是用cuda12.4与CuDNN 8.9.7 和 TensorRT 9.1.0 及以上对应 一、前言&#xff08;准备&#xff09; 确保电脑上有NVIDIA GPU…...

小刚说C语言刷题—1230蝴蝶结

1.题目描述 请输出 n 行的蝴蝶结的形状&#xff0c;n 一定是一个奇数&#xff01; 输入 一个整数 n &#xff0c;代表图形的行数&#xff01; 输出 n 行的图形。 样例 输入 9 输出 ***** **** *** ** * ** *** **** ***** 2.参考代码&#xff08;C语言版&#xff09…...

代码随想录算法训练营第60期第三十九天打卡

大家好&#xff0c;我们今天继续讲解我们的动态规划章节&#xff0c;昨天我们讲到了动态规划章节的背包问题&#xff0c;昨天讲解的主要是0-1背包问题&#xff0c;那么今天我们可能就会涉及到完全背包问题&#xff0c;昨天的题目有一道叫做分割等和子集&#xff0c;今天应该会有…...

计算机网络体系结构深度解析:从理论到实践的全面梳理

计算机网络体系结构深度解析&#xff1a;从理论到实践的全面梳理 本系列博客源自作者在大二期末复习计算机网络时所记录笔记&#xff0c;看的视频资料是B站湖科大教书匠的计算机网络微课堂&#xff0c;祝愿大家期末都能考一个好成绩&#xff01; 一、常见计算机网络体系结构 …...

Qwen2.5-VL模型sft微调和使用vllm部署

本文的server.py和req.py代码参见&#xff1a;https://github.com/zysNLP/quickllm 配套课程《AIGC大模型理论与工业落地实战》&#xff1b;Deepseek相关课程更新中 1. 安装相关docker镜像&#xff1a;nvcr.io/nvidia/pytorch:25.02-py3 docker pull nvcr.io/nvidia/pytorch:…...

python打卡DAY22

##注入所需库 import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import random import numpy as np import time import shap # from sklearn.svm import SVC #支持向量机分类器 # # from sklearn.neighbors import KNeighborsClassifier …...

【教程】Docker更换存储位置

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 背景说明 更换教程 1. 停止 Docker 服务 2. 创建新的存储目录 3. 编辑 Docker 配置文件 4. 迁移已有数据到新位置 5. 启动 Docker 服务 6…...

鸿蒙Next API17学习新特性之组件可见区域变化事件新增支持设置事件的回调参数,限制它的执行间隔

概述 鸿蒙开发文档更新的非常快&#xff0c;对应我们开发者的学习能力也要求非常高&#xff0c;今天这篇文章给大家分享一下鸿蒙API17中更新的新特性学习。 鸿蒙 Next 的组件可见区域变化事件在最新的 API Version 17 中得到了增强&#xff0c;新增了支持设置事件的回调参数的…...

AI大模型从0到1记录学习 mysql day23

第 1 章 MySQL概述 1.1 基本概念 1.1.1 数据库是什么&#xff1f; 数据库&#xff08;DB&#xff1a;Database&#xff09;&#xff1a;存储数据的地方。 1.1.2 为什么要用数据库&#xff1f; 应用程序产生的数据是在内存中的&#xff0c;如果程序退出或者是断电了&#xff0c;…...

spring -MVC-02

SpringMVC-11 - 响应 在 SpringMVC 中&#xff0c;响应是服务器对客户端请求的反馈&#xff0c;它可以以多种形式呈现&#xff0c;包括视图名称、ModelAndView 对象、JSON 数据以及重定向等。以下是对 SpringMVC 中不同响应类型的详细介绍&#xff1a; 1. 视图名称 通过返回…...

深入解析 React 的 useEffect:从入门到实战

文章目录 前言一、为什么需要 useEffect&#xff1f;核心作用&#xff1a; 二、useEffect 的基础用法1. 基本语法2. 依赖项数组的作用 三、依赖项数组演示1. 空数组 []&#xff1a;2.无依赖项&#xff08;空&#xff09;3.有依赖项 四、清理副作用函数实战案例演示1. 清除定时器…...

通过Ollama读取模型

通过Ollama读取模型 前言一、查看本地Ollama上有哪些模型二、调用bge-m3模型1、调用模型2、使用bge-m3进行相似度比较 三、调用大模型 前言 手动下载和加载大模型通常需要复杂的环境配置&#xff0c;而使用Ollama可以避免这一问题。本文将介绍如何调用Ollama上的模型。 一、查…...

C#控制流

&#x1f9e9; 一、控制流概述 C# 中的控制流语句用于根据条件或循环执行代码块。它们是程序逻辑的核心部分。 ✅ 二、1. if、else if、else int score 85;if (score > 90) {Console.WriteLine("优秀"); } else if (score > 60) {Console.WriteLine("及…...

永久免费,特殊版本!

随着大家审美的不断提升&#xff0c;无论是社交平台的日常分享还是特定场景的图像展示&#xff0c;人们对图像质量的要求都日益严苛。为了呈现更完美的视觉效果&#xff0c;许多小伙伴都会对原始图像进行精细化的后期处理&#xff0c;其中复杂背景抠图、光影调整、色彩校正等专…...

Canva 推出自有应用生成器以与 Bolt 和 Lovable 竞争

AI 目前是一个巨大的市场,每个人都想从中分一杯羹。 即使是 Canva,这个以拖放图形设计而闻名的流行设计平台,也在其 Canva Create 2025 活动中发布了自己版本的代码生成器,加入了 AI 竞赛。 但为什么一个以设计为先的平台会提供代码生成工具呢? 乍看之下,这似乎有些不…...

Matrix-Game:键鼠实时控制、实时生成的游戏生成模型(论文代码详细解读)

1.简介 本文介绍了一种名为Matrix-Game的交互式世界基础模型&#xff0c;专门用于可控的游戏世界生成。 Matrix-Game通过一个两阶段的训练流程来实现&#xff1a;首先进行大规模无标签预训练以理解环境&#xff0c;然后进行动作标记训练以生成交互式视频。为此&#xff0c;研…...

MySQL 5.7在CentOS 7.9系统下的安装(下)——给MySQL设置密码

新下载下来的MySQL&#xff0c;由于没有root密码&#xff0c;&#xff08;1&#xff09;所以如果我们希望登陆mysql&#xff0c;得给mysql的root账户设置密码&#xff0c;或者另一方面来说&#xff0c;&#xff08;2&#xff09;未来如果你忘记root密码了&#xff0c;也能通过这…...

机器学习笔记2

5 TfidfVectorizer TF-IDF文本特征词的重要程度特征提取 (1) 算法 词频(Term Frequency, TF), 表示一个词在当前篇文章中的重要性 逆文档频率(Inverse Document Frequency, IDF), 反映了词在整个文档集合中的稀有程度 (2) API sklearn.feature_extraction.text.TfidfVector…...

AgentCPM-GUI,清华联合面壁智能开源的端侧GUI智能体模型

AgentCPM-GUI是什么 AgentCPM-GUI 是由清华大学与面壁智能团队联合开发的一款开源端侧图形用户界面&#xff08;GUI&#xff09;代理&#xff0c;专为中文应用进行优化。基于 MiniCPM-V 模型&#xff08;80 亿参数&#xff09;&#xff0c;该系统能够接收智能手机的屏幕截图&a…...

Go语言实现链式调用

在 Go 语言中实现链式调用&#xff08;Method Chaining&#xff09;&#xff0c;可以通过让每个方法返回对象本身&#xff08;或对象的指针&#xff09;来实现。这样每次方法调用后可以继续调用其他方法。 示例&#xff1a;实现字符串的链式操作 假设你想对一个字符串连续执行…...

重排序模型解读 mxbai-rerank-base-v2 强大的重排序模型

mxbai-rerank-base-v2 强大的重排序模型 模型介绍benchmark综合评价安装 模型介绍 mxbai-rerank-base-v2 是 Mixedbread 提供的一个强大的重排序模型&#xff0c;旨在提高搜索相关性。该模型支持多语言&#xff0c;特别是在英语和中文方面表现出色。它还支持代码和 SQL 排序&a…...

期望是什么:(无数次的均值,结合概率)21/6=3.5

https://seeing-theory.brown.edu/basic-probability/cn.html 期望是什么:(无数次的均值,结合概率)21/6=3.5 一、期望(数学概念) 在概率论和统计学中,**期望(Expectation)**是一个核心概念,用于描述随机变量的长期平均取值,反映随机变量取值的集中趋势。 (一…...

uniapp-vue3项目中引入高德地图的天气展示

前言&#xff1a; uniapp-vue3项目中引入高德地图的天气展示 效果&#xff1a; 操作步骤&#xff1a; 1、页面上用定义我们的 当前天气信息&#xff1a;<view></view> 2、引入我们的map文件 <script setup>import amapFile from ../../libs/amap-wx.js …...

容器化-k8s-介绍及下载安装教程

一、K8s 概念 官网地址: https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/ 1、含义 Kubernetes 是一个开源的容器编排引擎,用于自动化部署、扩展和管理容器化应用程序。它可以将多个容器组合成一个逻辑单元,实现对容器的集中管理和调度,从而简化复杂应用的部…...

lc42接雨水

1.原题 42. 接雨水 - 力扣&#xff08;LeetCode&#xff09; 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 2.题目解析 这一题是经常被考到的一道算法题&#xff0c;其中最简单最好用的方法就是双指…...

通义千问-langchain使用构建(三)

目录 序言docker 部署xinference1WSL环境docker安装2拉取镜像运行容器3使用的界面 本地跑chatchat1rag踩坑2使用的界面2.1配置个前置条件然后对话2.2rag对话 结论 序言 在前两天的基础上&#xff0c;将xinference调整为wsl环境&#xff0c;docker部署。 然后langchain chatcha…...