创新实训2024.04.24日志:RAG技术初探

1. 什么是RAG技术

RAG is short for Retrieval Augmented Generation。结合了检索模型和生成模型的能力,以提高文本生成任务的性能。具体来说,RAG技术允许大型语言模型(Large Language Model, LLM)在生成回答时,不仅依赖于其内部知识,还能检索并利用外部数据源中的信息。

对于这个概念,我自己的理解是,大模型相当于是一个人,而RAG技术检索并利用的外部数据源就是书本、或者电子/数据资料。而RAG就是人检索并根据书本或者电子资料生成任务的能力。

比如一个人一目十行,理解能力强,可以快速地汲取知识并加以理解从而输出,就代表这个人的学习能力强,就相当于RAG技术性能优越。而另一个人阅读能力差,不容易理解新知识,就相当于RAG技术没做好,性能不行。

在这张图中,我把人类智能比作RAG技术,人类比作AI,外部知识来源比作向量数据库(一般与RAG一起使用)。RAG的实现越好,那么相当于越智能,则AI的能力越强。

2. RAG技术的Working Pipeline

首先我们要搜集插入到向量数据库 中,也即实体的文档、结构化知识、手册,读取文本内容,进行文本分割,进行向量嵌入后插入向量数据库中。

当用户请求大模型时,首先将查询向量化,随后检索向量库得到相似度高的知识,作为背景注入到prompt,随后大模型再生成回答。

3. RAG的实现

在github上,有一个RAG实现的Web应用的Demo。Langchain-Chatchat

我们同样打算以Web应用的模式构建一个能够被请求用来检索知识的向量数据库。因此先学习阅读一下这个项目的代码。

3.1. Web应用的入口:挂载Web应用路径

这一部分其实和RAG本身关系不大了,属于是网络通信方面的部分。但因为它是整个应用的入口,所以有必要探索一下。

首先在这个项目的README文件中,我们发现了这个Web应用还有个在线的接口文档。

从这个接口文档中,可以看到对于知识库(Knowledge Base) 的接口,这一部分就涉及了向量数据库。

我们可以通过在IDE中全局搜索这些接口,来找到暴露这些应用路径的地方。

可以看到,server/api.py下挂载了这些接口,我们来到这个文件一探究竟。其中不乏这样的函数:

    app.post("/knowledge_base/create_knowledge_base",tags=["Knowledge Base Management"],response_model=BaseResponse,summary="创建知识库")(create_kb)app.post("/knowledge_base/delete_knowledge_base",tags=["Knowledge Base Management"],response_model=BaseResponse,summary="删除知识库")(delete_kb)app.get("/knowledge_base/list_files",tags=["Knowledge Base Management"],response_model=ListResponse,summary="获取知识库内的文件列表")(list_files)app.post("/knowledge_base/search_docs",tags=["Knowledge Base Management"],response_model=List[DocumentWithVSId],summary="搜索知识库")(search_docs)

 我们点到每个函数中的参数,即create_kb这样的参数,来到了一个名叫kb_api.py的文件,其中暴露了这个函数(create_kb)。

此时我们就通过挂载Web应用路径的入口,找到了与向量数据库交互的模块。

 3.2. 与向量数据库交互

现在来看看这些与向量数据库交互的函数。

通过交互函数看知识库工程架构

首先我们关注到create_kb中的这样一部分代码:

    kb = KBServiceFactory.get_service(knowledge_base_name, vector_store_type, embed_model)try:kb.create_kb()

 光看这个名字,我们就能知道,这是一个工厂方法的设计模式。获取知识库的方式并不是直接拿到知识库的操作柄,而是先通过提供知识库服务的工厂拿到一项知识库的服务。

对于get_service函数,如下:

    @staticmethoddef get_service(kb_name: str,vector_store_type: Union[str, SupportedVSType],embed_model: str = EMBEDDING_MODEL,) -> KBService:if isinstance(vector_store_type, str):vector_store_type = getattr(SupportedVSType, vector_store_type.upper())if SupportedVSType.FAISS == vector_store_type:from server.knowledge_base.kb_service.faiss_kb_service import FaissKBServicereturn FaissKBService(kb_name, embed_model=embed_model)elif SupportedVSType.PG == vector_store_type:from server.knowledge_base.kb_service.pg_kb_service import PGKBServicereturn PGKBService(kb_name, embed_model=embed_model)elif SupportedVSType.MILVUS == vector_store_type:from server.knowledge_base.kb_service.milvus_kb_service import MilvusKBServicereturn MilvusKBService(kb_name,embed_model=embed_model)elif SupportedVSType.ZILLIZ == vector_store_type:from server.knowledge_base.kb_service.zilliz_kb_service import ZillizKBServicereturn ZillizKBService(kb_name, embed_model=embed_model)elif SupportedVSType.DEFAULT == vector_store_type:from server.knowledge_base.kb_service.milvus_kb_service import MilvusKBServicereturn MilvusKBService(kb_name,embed_model=embed_model)  # other milvus parameters are set in model_config.kbs_configelif SupportedVSType.ES == vector_store_type:from server.knowledge_base.kb_service.es_kb_service import ESKBServicereturn ESKBService(kb_name, embed_model=embed_model)elif SupportedVSType.CHROMADB == vector_store_type:from server.knowledge_base.kb_service.chromadb_kb_service import ChromaKBServicereturn ChromaKBService(kb_name, embed_model=embed_model)elif SupportedVSType.DEFAULT == vector_store_type:  # kb_exists of default kbservice is False, to make validation easier.from server.knowledge_base.kb_service.default_kb_service import DefaultKBServicereturn DefaultKBService(kb_name)

那么这个是在干什么?显然,他根据向量嵌入的方式,确定要创建的数据库服务是基于哪个向量数据库的,可能是chroma,也可能是Faiss,等等。

总之,它返回了一个KBService子类的实例。而这里KBService并非是一个可实例化的类,因为它是抽象类。

在server/knowledge_base/kb_service中,我们可以看到Class Definition。

    @abstractmethoddef do_create_kb(self):"""创建知识库子类实自己逻辑"""pass

在类定义中,出现了@abstractmethod注解,说明这是个抽象类。

那么其实现都在哪里呢?经过一番翻阅,在server/knowledge_base/kb_service下,包括了大量的基于不同数据库的实现类。

在翻阅代码时,我关注到了项目默认的向量数据库是faiss,因此我们可以来到faiss_kb_service中查看。

class FaissKBService(KBService):vs_path: strkb_path: strvector_name: str = None

 类定义中,对于KBService的继承赫然在目。

再回到通过KBServiceFactory创建KBService处:

    kb = KBServiceFactory.get_service(knowledge_base_name, vector_store_type, embed_model)try:kb.create_kb()

 我们溯源create_kb,可以发现:

    def create_kb(self):"""创建知识库"""if not os.path.exists(self.doc_path):os.makedirs(self.doc_path)self.do_create_kb()status = add_kb_to_db(self.kb_name, self.kb_info, self.vs_type(), self.embed_model)return status

可以看到,create_kb调用了self(实例自身)的do_create_kb()。而这就是刚才提到的抽象方法,也就是它会根据不同类对其的覆写,执行不同的逻辑。

    def do_create_kb(self):if not os.path.exists(self.vs_path):os.makedirs(self.vs_path)self.load_vector_store()def load_vector_store(self) -> ThreadSafeFaiss:return kb_faiss_pool.load_vector_store(kb_name=self.kb_name,vector_name=self.vector_name,embed_model=self.embed_model)

例如faiss就有自己独特的创建数据库的方式。

因此这个设计架构就明确了,是一个四层的Web-静态工厂-抽象类-实体类的架构。如下图所示:

Mapping from Abstract Working Pipeline to Code 

现在我们知道了如何获取一个向量数据库的服务。但在哪里使用它,如何使用它呢?正如先前RAG的Working Pipeline中所说,用户在请求大模型进行任务时,先通过检索向量数据库获取相似知识优化Prompt,再进行提问。那么这样一套流程,是如何映射到代码中的,我们是如何使用向量数据库提供的检索功能的?

找到RAG流程的入口

为了找到这个接口的入口,我还是先翻看了server/api.py文件,其中包括了:

    app.post("/chat/chat",tags=["Chat"],summary="与llm模型对话(通过LLMChain)",)(chat)app.post("/chat/search_engine_chat",tags=["Chat"],summary="与搜索引擎对话",)(search_engine_chat)app.post("/chat/feedback",tags=["Chat"],summary="返回llm模型对话评分",)(chat_feedback)app.post("/chat/knowledge_base_chat",tags=["Chat"],summary="与知识库对话")(knowledge_base_chat)app.post("/chat/file_chat",tags=["Knowledge Base Management"],summary="文件对话")(file_chat)app.post("/chat/agent_chat",tags=["Chat"],summary="与agent对话")(agent_chat)

 一开始我以为/chat/chat这个接口是包括了RAG流程的接口,但后来我翻了翻代码,发觉并没有检索向量数据库。

随后经过一些翻阅,我找到了/chat/knowledge_base_chat这个一接口:

async def knowledge_base_chat(query: str = Body(..., description="用户输入", examples=["你好"]),knowledge_base_name: str = Body(..., description="知识库名称", examples=["samples"]),top_k: int = Body(VECTOR_SEARCH_TOP_K, description="匹配向量数"),score_threshold: float = Body(SCORE_THRESHOLD,description="知识库匹配相关度阈值,取值范围在0-1之间,SCORE越小,相关度越高,取到1相当于不筛选,建议设置在0.5左右",ge=0,le=2),history: List[History] = Body([],description="历史对话",examples=[[{"role": "user","content": "我们来玩成语接龙,我先来,生龙活虎"},{"role": "assistant","content": "虎头虎脑"}]]),stream: bool = Body(False, description="流式输出"),model_name: str = Body(LLM_MODELS[0], description="LLM 模型名称。"),temperature: float = Body(TEMPERATURE, description="LLM 采样温度", ge=0.0, le=1.0),max_tokens: Optional[int] = Body(None,description="限制LLM生成Token数量,默认None代表模型最大值"),prompt_name: str = Body("default",description="使用的prompt模板名称(在configs/prompt_config.py中配置)"),request: Request = None,):kb = KBServiceFactory.get_service_by_name(knowledge_base_name)if kb is None:return BaseResponse(code=404, msg=f"未找到知识库 {knowledge_base_name}")history = [History.from_data(h) for h in history]async def knowledge_base_chat_iterator(query: str,top_k: int,history: Optional[List[History]],model_name: str = model_name,prompt_name: str = prompt_name,) -> AsyncIterable[str]:nonlocal max_tokenscallback = AsyncIteratorCallbackHandler()if isinstance(max_tokens, int) and max_tokens <= 0:max_tokens = Nonemodel = get_ChatOpenAI(model_name=model_name,temperature=temperature,max_tokens=max_tokens,callbacks=[callback],)docs = await run_in_threadpool(search_docs,query=query,knowledge_base_name=knowledge_base_name,top_k=top_k,score_threshold=score_threshold)# 加入rerankerif USE_RERANKER:reranker_model_path = get_model_path(RERANKER_MODEL)reranker_model = LangchainReranker(top_n=top_k,device=embedding_device(),max_length=RERANKER_MAX_LENGTH,model_name_or_path=reranker_model_path)print("-------------before rerank-----------------")print(docs)docs = reranker_model.compress_documents(documents=docs,query=query)print("------------after rerank------------------")print(docs)context = "\n".join([doc.page_content for doc in docs])if len(docs) == 0:  # 如果没有找到相关文档,使用empty模板prompt_template = get_prompt_template("knowledge_base_chat", "empty")else:prompt_template = get_prompt_template("knowledge_base_chat", prompt_name)input_msg = History(role="user", content=prompt_template).to_msg_template(False)chat_prompt = ChatPromptTemplate.from_messages([i.to_msg_template() for i in history] + [input_msg])chain = LLMChain(prompt=chat_prompt, llm=model)# Begin a task that runs in the background.task = asyncio.create_task(wrap_done(chain.acall({"context": context, "question": query}),callback.done),)source_documents = []for inum, doc in enumerate(docs):filename = doc.metadata.get("source")parameters = urlencode({"knowledge_base_name": knowledge_base_name, "file_name": filename})base_url = request.base_urlurl = f"{base_url}knowledge_base/download_doc?" + parameterstext = f"""出处 [{inum + 1}] [{filename}]({url}) \n\n{doc.page_content}\n\n"""source_documents.append(text)if len(source_documents) == 0:  # 没有找到相关文档source_documents.append(f"<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答!</span>")if stream:async for token in callback.aiter():# Use server-sent-events to stream the responseyield json.dumps({"answer": token}, ensure_ascii=False)yield json.dumps({"docs": source_documents}, ensure_ascii=False)else:answer = ""async for token in callback.aiter():answer += tokenyield json.dumps({"answer": answer,"docs": source_documents},ensure_ascii=False)await taskreturn EventSourceResponse(knowledge_base_chat_iterator(query, top_k, history,model_name,prompt_name))

他这个函数签名非常长,一堆参数,但实际有用的其实主要还是集中在query,也即用户查询上,其他的都是要调用langchain的库或者与向量数据库交互的必要参数。top k个相关向量是RAG技术的一部分,也是必要的参数。

源码解读

首先,先获取了数据库服务。(当然也可能数据库不存在)

    kb = KBServiceFactory.get_service_by_name(knowledge_base_name)if kb is None:return BaseResponse(code=404, msg=f"未找到知识库 {knowledge_base_name}")

随后选择LLM模型实例:

        model = get_ChatOpenAI(model_name=model_name,temperature=temperature,max_tokens=max_tokens,callbacks=[callback],)

再在对应的向量数据库中检索相关文档(top k个)

        docs = await run_in_threadpool(search_docs,query=query,knowledge_base_name=knowledge_base_name,top_k=top_k,score_threshold=score_threshold)

这个异步调用中的search_docs暴露自server/knowledge_basekb_doc_api.py,如下:

def search_docs(query: str = Body("", description="用户输入", examples=["你好"]),knowledge_base_name: str = Body(..., description="知识库名称", examples=["samples"]),top_k: int = Body(VECTOR_SEARCH_TOP_K, description="匹配向量数"),score_threshold: float = Body(SCORE_THRESHOLD,description="知识库匹配相关度阈值,取值范围在0-1之间,""SCORE越小,相关度越高,""取到1相当于不筛选,建议设置在0.5左右",ge=0, le=1),file_name: str = Body("", description="文件名称,支持 sql 通配符"),metadata: dict = Body({}, description="根据 metadata 进行过滤,仅支持一级键"),
) -> List[DocumentWithVSId]:kb = KBServiceFactory.get_service_by_name(knowledge_base_name)data = []if kb is not None:if query:docs = kb.search_docs(query, top_k, score_threshold)data = [DocumentWithVSId(**x[0].dict(), score=x[1], id=x[0].metadata.get("id")) for x in docs]elif file_name or metadata:data = kb.list_docs(file_name=file_name, metadata=metadata)for d in data:if "vector" in d.metadata:del d.metadata["vector"]return data

首先还是获取数据库服务,随后调用服务类暴露的search_docs函数(这个很显然,对于不同向量数据库来说,肯定是具体实现不一样), 随后返回相似度在阈值内的top_k个结果。

        if len(docs) == 0:  # 如果没有找到相关文档,使用empty模板prompt_template = get_prompt_template("knowledge_base_chat", "empty")else:prompt_template = get_prompt_template("knowledge_base_chat", prompt_name)input_msg = History(role="user", content=prompt_template).to_msg_template(False)chat_prompt = ChatPromptTemplate.from_messages([i.to_msg_template() for i in history] + [input_msg])chain = LLMChain(prompt=chat_prompt, llm=model)

 随后,建立prompt模板。然后根据历史会话信息建立当前对话的prompt。

之后通过LangChain提供的LLMChain,获取能够进行用户任务的中间件。

        # Begin a task that runs in the background.task = asyncio.create_task(wrap_done(chain.acall({"context": context, "question": query}),callback.done),)

随后启动一个后台的异步任务,将向量数据库中检索到的文档作为知识背景,用户的输入作为问题。

        source_documents = []for inum, doc in enumerate(docs):filename = doc.metadata.get("source")parameters = urlencode({"knowledge_base_name": knowledge_base_name, "file_name": filename})base_url = request.base_urlurl = f"{base_url}knowledge_base/download_doc?" + parameterstext = f"""出处 [{inum + 1}] [{filename}]({url}) \n\n{doc.page_content}\n\n"""source_documents.append(text)if len(source_documents) == 0:  # 没有找到相关文档source_documents.append(f"<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答!</span>")

一般LLM回答问题,会把自己参考的文献放出来(比如说Kimi),这一部分做的就是拼接参考文献字符串。

return EventSourceResponse(knowledge_base_chat_iterator(query, top_k, history,model_name,prompt_name))

 最后返回大模型的回答。

这个过程就是RAG的Working Pipeline在代码部分中的映射。

将知识嵌入到知识库

这一部分相对而言比较直接。在server/api.py中,有这么一段:

    app.post("/knowledge_base/upload_docs",tags=["Knowledge Base Management"],response_model=BaseResponse,summary="上传文件到知识库,并/或进行向量化")(upload_docs)

 找到对应的upload_docs,在server/knowledge_basekb_doc_api.py中。

def upload_docs(files: List[UploadFile] = File(..., description="上传文件,支持多文件"),knowledge_base_name: str = Form(..., description="知识库名称", examples=["samples"]),override: bool = Form(False, description="覆盖已有文件"),to_vector_store: bool = Form(True, description="上传文件后是否进行向量化"),chunk_size: int = Form(CHUNK_SIZE, description="知识库中单段文本最大长度"),chunk_overlap: int = Form(OVERLAP_SIZE, description="知识库中相邻文本重合长度"),zh_title_enhance: bool = Form(ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),docs: Json = Form({}, description="自定义的docs,需要转为json字符串",examples=[{"test.txt": [Document(page_content="custom doc")]}]),not_refresh_vs_cache: bool = Form(False, description="暂不保存向量库(用于FAISS)"),
) -> BaseResponse:"""API接口:上传文件,并/或向量化"""if not validate_kb_name(knowledge_base_name):return BaseResponse(code=403, msg="Don't attack me")kb = KBServiceFactory.get_service_by_name(knowledge_base_name)if kb is None:return BaseResponse(code=404, msg=f"未找到知识库 {knowledge_base_name}")failed_files = {}file_names = list(docs.keys())# 先将上传的文件保存到磁盘for result in _save_files_in_thread(files, knowledge_base_name=knowledge_base_name, override=override):filename = result["data"]["file_name"]if result["code"] != 200:failed_files[filename] = result["msg"]if filename not in file_names:file_names.append(filename)# 对保存的文件进行向量化if to_vector_store:result = update_docs(knowledge_base_name=knowledge_base_name,file_names=file_names,override_custom_docs=True,chunk_size=chunk_size,chunk_overlap=chunk_overlap,zh_title_enhance=zh_title_enhance,docs=docs,not_refresh_vs_cache=True,)failed_files.update(result.data["failed_files"])if not not_refresh_vs_cache:kb.save_vector_store()return BaseResponse(code=200, msg="文件上传与向量化完成", data={"failed_files": failed_files})

这一部分最重要的还是save_vector_store函数,不过这一部分属于每种数据库自己的实现了。

我们可以看一个faiss的

    def load_vector_store(self) -> ThreadSafeFaiss:return kb_faiss_pool.load_vector_store(kb_name=self.kb_name,vector_name=self.vector_name,embed_model=self.embed_model)def load_vector_store(self,kb_name: str,vector_name: str = None,create: bool = True,embed_model: str = EMBEDDING_MODEL,embed_device: str = embedding_device(),) -> ThreadSafeFaiss:self.atomic.acquire()vector_name = vector_name or embed_modelcache = self.get((kb_name, vector_name)) # 用元组比拼接字符串好一些if cache is None:item = ThreadSafeFaiss((kb_name, vector_name), pool=self)self.set((kb_name, vector_name), item)with item.acquire(msg="初始化"):self.atomic.release()logger.info(f"loading vector store in '{kb_name}/vector_store/{vector_name}' from disk.")vs_path = get_vs_path(kb_name, vector_name)if os.path.isfile(os.path.join(vs_path, "index.faiss")):embeddings = self.load_kb_embeddings(kb_name=kb_name, embed_device=embed_device, default_embed_model=embed_model)vector_store = FAISS.load_local(vs_path, embeddings, normalize_L2=True,distance_strategy="METRIC_INNER_PRODUCT")elif create:# create an empty vector storeif not os.path.exists(vs_path):os.makedirs(vs_path)vector_store = self.new_vector_store(embed_model=embed_model, embed_device=embed_device)vector_store.save_local(vs_path)else:raise RuntimeError(f"knowledge base {kb_name} not exist.")item.obj = vector_storeitem.finish_loading()else:self.atomic.release()return self.get((kb_name, vector_name))

其实这个模块是个缓存机制,也就是说每次检索都会查看是否已经有这个向量数据库的操作柄了。如果有直接返回,如果没有则加载一遍,这个加载的过程集中在:

    def get(self, key: str) -> ThreadSafeObject:if cache := self._cache.get(key):cache.wait_for_loading()return cache

那么他返回的是什么呢?是一个对应数据库的操作柄,定义如下:

class ThreadSafeFaiss(ThreadSafeObject):def __repr__(self) -> str:cls = type(self).__name__return f"<{cls}: key: {self.key}, obj: {self._obj}, docs_count: {self.docs_count()}>"def docs_count(self) -> int:return len(self._obj.docstore._dict)def save(self, path: str, create_path: bool = True):with self.acquire():if not os.path.isdir(path) and create_path:os.makedirs(path)ret = self._obj.save_local(path)logger.info(f"已将向量库 {self.key} 保存到磁盘")return retdef clear(self):ret = []with self.acquire():ids = list(self._obj.docstore._dict.keys())if ids:ret = self._obj.delete(ids)assert len(self._obj.docstore._dict) == 0logger.info(f"已将向量库 {self.key} 清空")return ret

本质上是存储向量化文档的一个对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/3267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

13. Spring AOP(一)思想及使用

1. 什么是Spring AOP AOP的全称是Aspect Oriented Programming&#xff0c;也就是面向切面编程&#xff0c;是一种思想。它是针对OOP(面向对象编程)的一种补充&#xff0c;是对某一类事情的集中处理。比如一个博客网站的登陆验证功能&#xff0c;在用户进行新增、编辑、删除博…

算法设计优化——有序向量二分查找算法与Fibonacci查找算法

文章目录 0.概述1.语义定义2. 二分查找&#xff08;版本A&#xff09;2.1 原理2.2 实现2.3 复杂度2.4 查找长度 3.Fibonacci查找3.1 改进思路3.2 黄金分割3.3 实现3.4 复杂度分析3.5 平均查找长度 4. 二分查找&#xff08;版本B&#xff09;4.1 改进思路4.2 实现4.3 性能4.4 进…

YOLOv8常见水果识别检测系统(yolov8模型,从图像、视频和摄像头三种路径识别检测)

1.效果视频&#xff08;常见水果识别&#xff08;yolov8模型&#xff0c;从图像、视频和摄像头三种路径识别检测&#xff09;_哔哩哔哩_bilibili&#xff09; 资源包含可视化的水果识别检测系统&#xff0c;可识别图片和视频当中出现的六类常见的水果&#xff0c;包括&#xf…

【redis】非关系型数据库——Redis介绍与安装(windows环境)

目录 数据库架构的演化单体架构缓存(Memcached)MySQL集群缓存(Memcached可以)MySQL集群垂直拆分&#xff08;主从复制&#xff0c;读写分离&#xff09;缓存(Redis)MySQL集群垂直拆分分库分表 NoSQLNoSQL产生的背景性能需求MySQL的扩展性瓶颈方面什么是NoSQLNoSQL的特点主流的N…

下级平台级联EasyCVR视频汇聚安防监控平台后,设备显示层级并存在重复的原因排查和解决

视频汇聚平台/视频监控系统/国标GB28181协议EasyCVR安防平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;平台支持7*24小时实时高清视频监控&#xff0c;能同时…

C语言进阶|单链表的实现

✈链表的概念和结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针链接次序实现的。 链表的结构跟火车车厢相似&#xff0c;淡季时车次的车厢会相应减少&#xff0c;旺季时车次的车厢会额外增加几节。…

BSV区块链协会上线首个版本的ARC交易处理器

​​发表时间&#xff1a;2024年3月28日 BSV区块链协会近期上线了首个版本的ARC交易处理器。ARC是一项区块链交易处理服务&#xff0c;能在通过P2P网络广播交易之前验证并存储相关的交易。一旦新区块被挖出&#xff0c;一条与该交易相关的Merkle路径将被发回给交易发起者作为确…

长效静态代理IP如何改变你的SEO和网络营销策略?

长效静态代理IP为SEO和网络营销专家提供了一个强大的工具&#xff0c;通过这种技术&#xff0c;可以突破传统的限制&#xff0c;以全新的视角和方法优化其在线策略。这不仅增强了企业的市场竞争力&#xff0c;也为实现更高效、更精准的营销目标提供了可能。 一、长效静态代理IP…

Vue面试经验

Vue编译时声明周期的执行顺序 Vue中父子组件渲染顺序&#xff08;同步引入子组件&#xff1a;import Son from ‘/components/son’ &#xff09; 父子组件编译时的生命周期执行顺序 这里修改data数据时也修改了dom&#xff0c;如过知识通过按钮对数据进行操作&#xff0c;那…

kafka大数据采集技术实验(未完待续)

Kafka环境搭建 下载地址&#xff1a;https://link.zhihu.com/?targethttps%3A//kafka.apache.org/downloads解压启动zookeeper bin/zookeeper-server-start.sh config/zookeeper.properties需要注意的是 : " c o n f i g / z o o k e e p e r . p r o p e r t i e s &q…

解密Java线程池源码

一、线程池中的保活和回收源码分析 1、线程池中线程的创建时机 1、核心线程创建时机 在研究线程池的源码前首先想一个问题 public class Main {public static void main(String[] args) {ThreadPoolExecutor executor new ThreadPoolExecutor(10, 20, 0l, TimeUnit.MILLIS…

从Linux角度具体理解程序翻译过程-----预处理、编译、汇编、链接

目录 前言&#xff1a; 翻译过程 1.预处理 2.编译 3.汇编 4.链接 Linux下对其理解&#xff1a; 1.预处理 拓展&#xff1a; Linux下文件信息&#xff1a; 文件类型&#xff1a; 硬链接数&#xff1a; 文件拥有者&#xff1a; 文件所属组&#xff1a; other&#x…

区块链安全应用-------压力测试

基于已有的链进行测试&#xff08;build_chain默认建的链 四个节 点&#xff09;&#xff1a; 第一步&#xff1a;搭链 1. 安装依赖 在ubuntu操作系统中&#xff0c;操作步骤如下&#xff1a; sudo apt install -y openssl curl 2. 创建操作目录, 下载安装脚本 ## 创建操作…

3个比较不错的Linux云音乐应用程序整理

在现代音乐流媒体时代&#xff0c;基于云的音乐应用程序因其便利性和可访问性而变得非常流行。Linux 用户尤其寻求可靠且功能丰富的音乐播放器来无缝地享受他们喜爱的音乐。 在这里&#xff0c;我们探讨了三个最好的基于云的音乐应用程序&#xff0c;每个应用程序都提供专为 L…

Java Web 网页设计(1)

不要让追求之舟停泊在幻想的港湾 而应扬起奋斗的风帆 驶向现实生活的大海 网页设计 1.首先 添加框架支持 找到目录右键添加 找到Web Application选中 点击OK 然后 编辑设置 找到Tomcat--local 选中 点击OK 名称可以自己设置 找到对应文件夹路径 把Tomcat添加到项目里面 因为…

【Hadoop】-HDFS的Shell操作[3]

目录 前言 一、HDFS集群启停命令 1.一键启停脚本可用 2.独立进程启停可用 二、文件系统操作命令 1、创建文件夹 2、查看指定目录下内容 3、上传文件到HDFS指定目录下 4、查看HDFS文件内容 5、下载HDFS文件 6、拷贝HDFS文件 7、追加数据到HDFS文件中 8、HDFS数据移…

哪吒汽车把最后的翻身筹码,全压在了这辆新车上

正如比亚迪王传福所说&#xff0c;新能源车市场已进入惨烈淘汰赛环节。 近几年国内新能源车销量增长势头迅猛&#xff0c;仅过去的 2023 年产销便分别达 958.7 万辆和 949.5 万辆&#xff0c;同比增长 35.8% 和 37.9%。 销量高速增长背后自然也带来了越来越激烈的竞争。 过去…

Footprint Analytics 与 GalaChain 达成战略合作

​ Footprint Analytics 宣布与 GalaChain 达成战略合作。GalaChain 是 Gala 旗下的 Layer 1 区块链。此次合作标志着双方在游戏&#xff08;包括 Gala Games) 、娱乐和金融等多个行业的区块链生态系统革新方面迈出了重要的一步。 GalaChain 致力于满足企业级项目的广泛需求&…

算法-栈操作

1047. 删除字符串中的所有相邻重复项 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:string removeDuplicates(string s) {string stack;for(char& ch:s){if(stack.size()>0&&chstack.back()){stack.pop_back();}else{stack.push_back(ch);}…

AI大模型实现软件智能化落地实践

1、什么是大模型 大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff1b;Large Language Models&#xff0c;LLMs)。 大语言模型是一种深度学习模型&#xff0c;特别是属于自然语言处理&#xff08;NLP&#xff09;的领域&#xff0c;一般是指包含数干亿&…