tensorflow http调用_《TensorFlow 内核剖析》笔记——系统架构

bc12fef65ed7c416abc6d43195e7e768.png

3 系统架构

  1. 系统整体组成:Tensorflow的系统结构以C API为界,将整个系统分为前端和后端两个子系统:
  • 前端构造计算图
  • 后端执行计算图,可再细分为:
    • 运行时:提供本地模式和分布式模式
    • 计算层:由kernal函数组成
    • 通信层:基于gRPC实现组件间的数据交换,并能够在支持IB网络的节点间实现RDMA通信
    • 设备层:计算设备是OP执行的主要载体,TensorFlow支持多种异构的计算设备类型

d4127517b9308640112a3caa99be0fab.png

2. 从图操作的角度看,TensorFlow执行步骤包括计算图的构造、编排、及其运行:

    • 表达图:构造计算图,但不执行(前端)
    • 编排图:将计算图的节点以最佳的执行方案部署在集群中各个计算设备上(运行时)
    • 运行图:按照拓扑排序执行图中的节点,并启动每个OP的Kernel计算(计算层、通信层、设备层)

3. 系统中重要部分(Client、Master、Worker)

    • Client:前端系统的主要组成部分,Client基于TensorFlow的编程接口,负责构造计算图。
    • Master负责的流程(接收并处理图):
      • Client执行Session.run时,传递整个计算图(Full Graph)给后端的Master
      • Master通过Session.run中的fetches、feeds参数根据依赖关系将Full Graph剪枝为小的依赖子图(Client Graph)
      • Master根据任务名称将Client Graph分为多个Graph Partition(SplitByTask),每个Graph Partition被注册到相应的Worker上(任务和Worker一一对应)
      • Master通知所有Worker启动相应Graph Partition并发执行
    • Worker负责的流程(再次处理图并执行图):
      • 处理来自Master的请求(图执行命令)
      • 对注册的Graph Partition根据本地设备集二次分裂(SplitByDevice),其中每个计算设备对应一个Graph Partition(是注册的Graph Partition中更小的Partition),并通知各个计算设备并发执行这个更小的Graph Partition(计算根据图中节点之间的依赖关系执行拓扑排序算法)
      • 按照拓扑排序算法在某个计算设备上执行本地子图,并调度OP的Kernel实现
      • 协同任务之间的数据通信(交换OP运算的结果)
        • 设备间Send/Recv(主要用于本地的数据交换):
          • 本地CPU与GPU之间,使用cudaMemcpyAsync实现异步拷贝
          • 本地GPU之间,使用端到端的DMA操作,避免主机端CPU的拷贝
        • 任务间通信(分布式运行时,Send/Recv节点通过GrpcRemoteRendezvous完成数据交换):
          • gRPC over TCP
          • RDMA over Converged Ethernet
    • Kernal:Kernel是OP在某种硬件设备的特定实现,它负责执行OP的具体运算,大多数Kernel基于Eigen::Tensor实现。Eigen::Tensor是一个使用C++模板技术

4. 图控制(实例解释3.3中的内容)

    • 组建集群
      • 假设一个分布式环境:1PS+1Worker,将其划分为两个任务:
        • ps0:使用/job:ps/task:0标记,负责模型参数的存储和更新
        • worker0:/job:worker/task:0标记,负责模型的训练

9d1604f1572cbda7768757daa85e343f.png
    • 图构造:Client构建了一个简单的计算图

d3f043d760c3f062b00d9bcb03cd304d.png
    • 图执行:Client创建Session实例并调用Session.run将计算图传递给Master。Master在执行图计算之前会实施一系列优化技术,例如公共表达式消除,常量折叠等。最后,Master负责任务之间的协同,执行优化后的计算图。

23b6829808de4aae2f3ce3c6814efc47.png
    • 图分裂:Master将模型参数相关的OP划分为一组,并放置在ps0任务上;其他OP划分为另外一组,放置在worker0任务上执行

4167a98406972a47f41d37a43ddf7b0d.png
    • 子图注册:在图分裂过程中,如果计算图的边跨越节点或设备,Master将该边实施分裂,在两个节点或设备之间插入Send和Recv节点(Send和Recv节点是特殊OP,仅用于数据的通信,没有数据计算逻辑),最后Master通过调用RegisterGraph接口,将子图注册给相应的Worker上,并由相应的Worker负责执行运算。

328b5e61803b824f95398f250a83e934.png
    • 子图运算:Master通过调用RunGraph接口,通知所有Worker执行子图运算。其中,Worker之间可以通过调用RecvTensor接口,完成数据的交换

131cd2e654424e9e4f39223dc26dac6e.png

5. 会话管理(保障Client与Master之间的消息传递,执行图控制的操作)

    • 创建会话
      • Client首次执行tf.Session.run时,会将整个图序列化后,通过gRPC发送CreateSessionRequest消息,将图传递给Master
      • Master创建一个MasterSession实例,并用全局唯一的handle标识,最终通过CreateSessionResponse返回给Client

7ba77337867a31231d7d31b2810edfab.png
    • 迭代运行
      • Clent->Master。Client会启动迭代执行的过程,并称每次迭代为一次Step。此时,Client发送RunStepRequest消息给Master,消息携带handle标识,用于Master索引相应的MasterSession实例
      • Master图分裂。Master收到RunStepRequest消息后,Full Graph->Client Graph->Partition Graph->Master向Worker发送RegisterGraphRequest消息将Partition Graph注册到各个Worker节点上
      • Worker图分裂。当Worker收到RegisterGraphRequest消息后,再次实施分裂操作,最终按照设备将图划分为多个子图片段
      • 注册子图。当Worker完成子图注册后,通过返回RegisterGraphReponse消息,并携带graph_handle标识。这是因为Worker 可以并发注册并运行多个子图,每个子图使用graph_handle唯一标识。Master完成子图注册后通过发送RunGraphRequest消息给Worker并发执行所有子图,消息中携带(session_handle,graph_handle,step_id)三元组的标识信息,用于Worker索引相应的子图
      • 子图节点拓扑排序。Worker收到消息RunGraphRequest消息后,Worker根据graph_handle索引相应的子图。每个子图放置在单独的Executor中执行,Executor将按照拓扑排序算法完成子图片段的计算。

7539f9e714ebb6e1cd94c3fb3af6a99b.png
    • 数据交换
      • 设备间通信通过Send/Recv节点
      • Worker间通信涉及进程间通信,此时,需要通过接收端主动发送RecvTensorRequest消息到发送方,再从发送方的信箱取出对应的Tensor,并通过RecvTensorResponse返回

ace30b9b846e95c78e4af0fde8707022.png
    • 关闭会话:计算完成后,Client向Master发送CloseSessionReq消息。Master收到消息后,开始释放MasterSession所持有的所有资源

db2295e09f16e27bbb6b14d63de0e23d.png

4 C API:分水岭(前后端之间的通道的实现)

  1. 科普
  • Bazel:高级构建语言Bazel使用一种抽象的、人易于理解的、语义级别的高级语言来描述项目的构建属性。免于将单个调用编写到编译器和链接器等的复杂性
  • Swig:SWIG是一种简化脚本语言与C/C++接口的开发工具。简而言之,SWIG是一个通过包装和编译C语言程序来达到与脚本语言通讯目的的工具

2. Swig

    • TensorFlow使用Bazel的构建工具,在系统编译之前启动Swig的代码生成过程,通过tensorflow.i自动生成了两个适配(Wrapper) 文件:
      • pywrap_tensorflow_internal.py——负责对接上层Python调用:该模块首次被导入时,自动地加载_pywrap_tensorflow_internal.so的动态链接库;其中,_pywrap_tensorflow_internal.so包含了整个TensorFlow运行时的所有符号
      • http://pywrap_tensorflow_internal.cc——负责对接下层C API调用:该模块实现时,静态注册了一个函数符号表,实现了Python函数名到C函数名的二元关系。在运行时,按照Python的函数名称,匹配找到对应的C函数实现,最终实现Python到c_api.c具体实现的调用关系

433e5e12ac935178045686c3e8a9b7b9.png
    • Bazel生成规则定义于//tensorflow/python:pywrap_tensorflow_internal,如下图所示

6b9aca16336551e94f14cd29fb4f6e6f.png

3. 会话生命周期——包括会话的创建,创建计算图,扩展计算图,执行计算图,关闭会话,销毁会话六个过程,在前后端表现为两套相兼容的接口实现

    • Python前端的Session生命周期,下图为生命周期
      • 创建Session,tf.Session(target)
      • 迭代执行sess.run(fetchs,feed_dict)
        • sess._extend_graph(graph)
        • sess.TF_Run(feeds,fetches,targets)
      • 关闭Session,sess.close()
      • 销毁Session,sess.__del__

ff45df63d9473f6df7f9ad7cf190c4f1.png
    • C++后端的Session生命周期,下图为生命周期和本地模型的DirectSession实例
      • 根据target多态创建Session
      • Session.Create(graph):有且仅有一次
      • Session.Extend(graph):零次或多次
      • 迭代执行Session.Run(inputs,outputs,targets)
      • 关闭Session.Close
      • 销毁Session对象

7d1aaf1b9dd2685e1a5f8be40f39994f.png

56a8496d700fc58fc9d6c4de5f45de54.png

4. 会话周期各部分内容展开(展开4.3中的内容,从代码层面分析)

    • 创建会话
      • 过程:从Python前端为起点,通过Swig自动生成的Python-C++的包装器,并以此为媒介,实现了Python到TensorFlow的C API的调用

798f14834cee034151e9f2fad3b5de4f.png
      • 编程接口(Python调用)
        • 创建了一个Session实例,调用父类BaseSession的构造函数,进而调用BaseSession的构造函数中pywrap_tensorflow模块中的函数(Python代码)

6d384b346116dfd1ed805e79defcfc23.png

949012675a588c9b508ce291561c66a4.png

f83166f46d91d450e1c8657e19654f7b.png
        • Session类构造函数中graph参数的定义如下图。ScopedTFGraph是对TF_Graph的包装器,完成类似于C++的RAII的工作机制。而TF_Graph持有 ternsorflow::Graph实例。其中,self._graph._c_graph返回一个TF_Graph实例,后者通过C API创建的图实例(Python代码)

7c45b2aa7fe359425455b42544766f2e.png

c3588a2b4d83170ddbb4a24491528bda.png
        • 图实例传递如下(上面代码对应图中Python部分):

004d9b96f346438ef57470ae9d3537dc.png
      • Python包装器:在pywrap_tensorflow模块中,通过_pywrap_tensorflow_internal的转发实现从Python到动态连接库_pywrap_tensorflow_internal.so的函数调用(对接上层Python调用,包含了整个TensorFlow运行时的所有符号)

0f2d68974c5a69c870fb90c430c87027.png
      • C++包装器:在http://pywrap_tensorflow_internal.cc实现中,通过函数调用的符号表实现Python到C++的映射。_wrap_TF_NewSession/wrap_TF_NewDeprecatedSession将分别调用c_api.h对其开放的API接口:TF_NewSession/TF_NewDeprecatedSession。也就是说,自动生成的http://pywrap_tensorflow_internal.cc仅仅负责Python函数到C/C++函数调用的转发,最终将调用底层C系统向上提供的API接口

05f775429120d13889a5bdd052940d08.png

ac410090f097cbd88de5a3b399832f01.png
      • C API:c_api.h是TensorFlow的后端执行系统面向前端开放的公共API接口,实现采用了引用计数的技术,实现图实例在多个Session实例中共享

77f0dcff3debbfde68a5dd684eaad3da.png
      • 后端系统(C API的底层C++实现):NewSession将根据前端传递的target,使用SessionFactory多态创建不同类型的tensorflow::Session实例(工厂方法)
        • SessionOptions中target为空字符串 (默认的),则创建DirectSession实例
        • SessionOptions中target以grpc://开头,则创建GrpcSession实例,启动基于RPC的分布式运行模式

df2a91b14b8c0b73f8b7204bf32168e3.png

1d535ca8c305efd2fe6a773f6f4ffce5.png
    • 创建/扩展图
      • 过程:在既有的接口实现中,需要将图构造期构造好的图序列化,并传递给后端C++系统。而在新的接口实现中,无需实现图的创建或扩展
        • 新的接口:创建OP时,节点实时添加至后端C++系统的图实例中,而不像既有接口每次调用sess.run后在原先图实例的基础上再添加节点
        • 既有接口:Python前端将迭代调用Session.run接口,将构造好的计算图,以GraphDef的形式发送给C++后端。其中,前端每次调用Session.run接口时,都会试图将新增节点的计算图发送给后端系统,以便将新增节点的计算图Extend到原来的计算图中。特殊地,在首次调用Session.run时,将发送整个计算图给后端系统。后端系统首次调用Session.Extend时,转调Session.Create。以后,后端系统每次调用Session.Extend时将真正执行Extend的语义,将新增的计算图的节点追加至原来的计算图中

0c03e73a3bb12cc6c1f078f8d4d62744.png
      • 编程接口
        • 在既有的接口实现中,通过_extend_graph实现图实例的扩展

528a90e43a9dcc4a969f015e34e9142a.png
        • 在首次调用self._extend_graph时,或者有新的节点被添加至计算图中时,对计算图GraphDef实施序列化操作,最终触发tf_session.TF_ExtendGraph的调用

3b11dad1fee8ab4eba63f7761140f388.png
      • Python包装器

11b14e195cb8b5f05c30685217cd8695.png
      • C++包装器

a66c976e966017e1d14eb2dee41fe887.png
      • C API:TF_ExtendGraph是C API对接上层编程环境的接口。首先,它完成计算图GraphDef的反序列化,最终调用tensorflow::Session的Extend接口

c1de0877261efe5277dd9e14941d9da2.png
      • 后端系统:Create表示在当前的tensorflow::Session实例上注册计算图,如果要注册新的计算图,需要关闭该tensorflow::Session对象。Extend表示在tensorflow::Session实例上已注册的计算图上追加节点。Extend 首次执行时,等价于Create的语义。实现如首次扩展图GrpcSession所示:若引用Master的handle不为空则执行Extend,否则执行Create,建立与Master的连接并持有MasterSession的handle

f0183b770623d09536f5d55489761b0f.png

a5dc06de553e25b891ea911d08675646.png
    • 迭代运行
      • 过程:Python前端Session.run实现将fetches,feed_dict传递给后端系统,后端系统调用Session.Run接口

0fc881168bbd35d3ea84a99755ca6a2b.png
      • 编程接口:当Client调用Session.run时,最终会调用pywrap_tensorflow_internal模块中的函数

7d436d841a1c449a29542951a19d2ddb.png
      • Python包装器

f4c82f70db69eb9fba9965de6cb561fd.png
      • C++包装器

fbbe692ca14a163165c54326ad6f4759.png
      • C API:在既有的接口中,TF_Run是C API对接上层编程环境的接口。首先,它完成输入数据从C到C++的格式转换,并启动后台的tensorflow::Session的执行过程。当执行完成后,再将outputs的输出数据从C++到C的格式转换。TF_SessionRun类似

2f52f0760294d10b67550ce4be429992.png
      • 后端系统
        • 输入包括:
          • options:Session的运行配置参数
          • inputs:输入Tensor的名字列表
          • output_names:输出Tensor的名字列表
          • targets:无输出,待执行的OP的名字列表
        • 输出包括
          • outputs:输出的Tensor列表,outputs列表与输入的output_names一一对应
          • run_metadata:运行时元数据的收集器
    • 关闭会话
      • 过程:当计算图执行完毕后,需要关闭tf.Session,以便释放后端的系统资源,包括队列,IO等

f492fa4a83a7e130069fb0aceb4dc527.png
      • 编程接口:当Client调用Session.close时,最终会调用pywrap_tensorflow模块中的函数:TF_CloseDeprecatedSession

9a2fe2bb484d98a42daaebfb21d579da.png

8d21460fbf6cca42d0cc19b1e89eccf3.png
      • Python包装器

92c4977f9c007aea6ec52477ac52c96c.png
      • C++包装器

78a30a6e828dfb9644d15d4e903a1bc7.png
      • C API:TF_CloseSession/TF_CloseDeprecatedSession直接完成tensorflow::Session的关闭操作

b7a670028740db0f34e7f8757ce26581.png

aac773c0d0a640d984d9a9d83665e795.png
      • 后端系统:Session(C++)在运行时其动态类型,将多态地调用相应的子类实现

840c5d55bdeb2660257231f5d926d8f9.png
    • 销毁会话
      • 过程:当tf.Session不在被使用,由Python的GC释放。Client调用Session.__del__后,将启动后台tensorflow::Session对象的析构过程

9ca2b787990e288475477f28bdadeb57.png
      • 编程接口:Client调用Session.__del__时,先调用Session.close,再调用pywrap_tensorflow模块中的TF_DeleteSession/TF_DeleteDeprecatedSession

ec6a3e29b0455dcb0db85860c021ddc3.png

9939cc0f8312639f1ac3747ffb21fc47.png
      • Python包装器

624cff9a355d17e3c3d9e0780d35dab8.png
      • C++包装器

61e1383d5cbfa64e138f656a97205047.png
      • C API:TF_DeleteDeprecatedSession直接完成tensorflow::Session对象的释放。而新的接口TF_DeleteSession实现中,当需要删除tensorflow::Session实例时,相应的图实例的计数器减1。当计数器为0时,则删除该图实例;否则,不删除该图实例

cfa45562fceabe809e7af16345109eb0.png
      • 后端系统:tensorflow::Session在运行时其动态类型,多态地调用相应子类实现的析构函数

11f5007ec24ec303e7a9e55cea28707b.png

5. 性能调优(针对新实现的接口)

    • 共享图实例:一个Session只能运行一个图实例,如果一个Session要运行其他的图实例,必须先关掉Session,然后再将新的图实例注册到此Session中。但反过来,一个计算图可以运行在多个Session实例上。如果在Graph实例上维持Session的引用计数器,在Session创建时,在该图实例上增加1;在Session销毁时(不是关闭Session),在该图实例上减少1;当计数器为0时,则自动删除图实例(即:新接口加入了引用计数器)

70c43fc8566632d316023d444e0c00a0.png
    • 消除序列化
      • 遗留的接口实现中,前端构造图并将其序列化后,通过Session::Create或Session::Extend传递给后端。这本质是图实例的拷贝,具有很大的时延开销

cc4a26e496c3344b4993732c4835bed6.png
      • 在新的接口实现中,可以去Create/Extend语义。在图的构造器,前端Python在构造每个OP时,直接通过C API将其追加至后端C++的图实例中,从而避免了图实例在前后端的序列化和反序列化的开销

e211aecd70b372f361a643d3d19eabd7.png

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

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

相关文章

(转)数据流图

转自:http://jingyan.baidu.com/article/4f34706eefdb04e387b56deb.html 数据流图4种图元 数据流图的实例 转载于:https://www.cnblogs.com/wrencai/p/5852389.html

MySQL中文乱码问题

项目中用到MySQL数据库时中文出现乱码问题(中文字符都变成了?)解决: 1、统一项目与数据库的编码,项目中用的是UTF-8因此我的把数据库的编码统一成UTF-8 修改方式:修改 MySQL根目录中的 my.ini 文件替换d…

C++实例讲解Binder通信

binder是android里面的通信机制,这就不说它如何如何好了,Goog已经说过了,这里不多说。binder是一个面向对象的编程方法,大量使用虚函数类。最近研究binder看到一网友写的,就借鉴一下。这个例子很好的解释里binder通信关…

回收对象以提高性能

总览 在上一篇文章中,我说过对象反序列化更快的原因是由于使用了回收对象。 由于两个原因,这可能令人惊讶:1)相信如今创建对象是如此之快,无关紧要或与回收自己一样快,2)默认情况下,…

oj系统格式错误_论文查重会不会检查格式?【paperpp吧】

高等学校一般都会要求大学生在毕业时需要写作毕业论文,并且会提前发出关于毕业论文的通知,在通知上一般会说明论文写作的相关要求,其中就会规定论文的相关格式。当然,学校也会在通知中说明论文查重的相关事宜,那么论文…

usb大容量存储设备驱动_usb无法识别怎么办 如何解决usb识别故障【详细步骤】...

usb无法识别怎么办? 随着计算机硬件飞速发展,外围设备日益增多,键盘、鼠标等早已为人所共知,数码相机、MP3随身听接踵而至,这么多的设备,如何接入个人计算机?USB就是基于这个目的产生的。USB是一个使计算机周边设备连…

Java EE过去,现在和云7

最近的JavaOne 2011的一个突出主题是下一个主要的Java EE 7版本。 正如主题发言中所述,有关工作正在进行中。 它将包含我们已经从先行者那里知道的28个规范以及一些新规范。 没人可以告诉您确切的号码,因为EE 7仅在“及时”完成时才会接受新的规范。 这意…

程序员都用什么来记录知识_1年前的小五都用 Python 来做什么?

↑ 点击上方 “凹凸数据” 关注 星标 ~ 每天更新,干货不断 (多图预警)注:这是小五一年前在知乎的回答,当时还只有凹凸数读一个公众号,所以很多图片都会带有数读或者知乎的水印。作为一个菜鸟数据分析师,只会sqlpytho…

SQL-行转列(PIVOT)实例1

--未旋转之前的查询结果 select s.Name ShiftName,h.BusinessEntityID,d.Name as DpartmentName from HumanResources.EmployeeDepartmentHistory h inner join HumanResources.Department d on h.DepartmentIDd.DepartmentIDinner join HumanResources.Shift s on s.ShiftIDh…

webservice接口_webservice服务器端发票识别接口

关键词:发票识别 私有云发票识别 发票识别API接口 webservice发票识别平台发票,一个再也熟悉不过的财务往来凭证,录入发票,一项让多少财会人员头疼的工作。过去录入一张发票需要一个财会人员5分钟的时间,那么这个人在工…

一个简单的socket通信小demo

写了一个socket的程序,可以和本地的服务器进行通信,要先和服务器建立链接,然后发送登录信息,验证成功,就可以和服务器通信了 1 页面截图 2 点击链接服务器,可以链接服务器,服务器的ip地址为&…

汉仪尚巍手书可以商用吗_【商用车维修】夏天修空调可以撑起全年修车收入的一半,你会了吗?...

更多精彩,请点击上方蓝字关注我们!车载空调是炎热的季节必不可少的利器,但用得多,毛病也多了起来,今天和大家分享一些空调系统的相关知识,助力修车师傅们来应对空调系统的相关故障问题。如何判断制冷系统的…

Git 简单使用

1.Git是什么 简介:Git是 Linux 之父 Linus Trovalds,为管理 Linux 内核代码而建立的,被认为是分布式版本控制工具中的顶级水准。智能、友好、强健、高效。 作用:新建一个分支,把服务器上最新版的代码fetch下来&#x…

Vaadin附加组件和Maven

介绍 我喜欢Vaadin的 (众多)一件事是它对Vaadin框架的“附加组件”社区-他们称之为Vaadin目录 。 “附加组件”是框架中社区贡献的附加组件,可以是任何东西,例如从新的客户端小部件到数据表的延迟加载容器。 我肯定会为Activiti看…

八皇后时间复杂度_【算法打卡】N皇后

难度:困难题目:n 皇后问题研究的是如何将 n 个皇后放置在 nn 的棋盘上,并且使皇后彼此之间不能相互攻击。上图为 8 皇后问题的一种解法。给定一个整数 n,返回 n 皇后不同的解决方案的数量。提示:皇后,是国际…

Android-Binder 简析

前言 对于Android来说,Binder的重要性怎么说都不为过。不管是我们的四大组件Activity、Service、BroadcastReceiver、ContentProvider,还是经常在应用中使用到的各种ServiceManager,其背后都是Binder在支撑。然而Binder机制又不是三言两语能够…

tplink 703刷固件

1.软件下载: ImageBuilder链接 如果是全新刷机的话,使用:http://downloads.openwrt.org/snapshots/trunk/ar71xx/generic/openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-factory.bin 如果是系统升级的话,使用:http://downloads.openwrt.org/snapshots/trunk/ar71xx/generic/op…

编程反模式

您是否曾经进行过代码审查,记录了非常高的WTF / m? 您是否想知道所有这些错误代码的原因是什么? 在大多数情况下,导致原因1的主要原因是使用设计和编码反模式。 如果您喜欢定义,请参见以下内容:AntiPatter…

sql 插入text字段包含特殊字符_Kettle(PDI)转换中输出之插入/更新详解

概述Insert / update(插入 / 更新)此步骤首先使用一个或多个查询关键字查找表中的一行。如果找不到该行,则插入该行。如果可以找到它,并且要更新的字段相同,则不执行任何操作。如果它们不完全相同,则更新表中的行。注意&#xff1…

一张图让你看清Java集合类(Java集合类的总结)

如今关于Java集合类的文章非常多,可是我近期看到一个非常有意思图片,基本上把Java集合的整体框架都给展现出来了。非常直观。 假设发现图片看不清楚。点此处看大图 在这里,集合类分为了Map和Collection两个大的类别。 处于图片左上角的那一块…