hdfs源码解析之DFSClient
1、DFSClient类简介
DFSClient
是 Hadoop 分布式文件系统(HDFS)中的一个核心类,用于客户端与 HDFS 之间的交互。它提供了一组方法,使客户端应用程序可以方便地与 HDFS 进行通信,包括文件的读取、写入、创建、删除、重命名等操作。DFSClient
封装了与 NameNode 和 DataNode 的通信细节,使得客户端开发者可以通过高级 API 进行文件系统操作,而不必关心底层的实现细节。
2、DFSClient主要功能
2.1、文件读取和写入
- 提供方法用于读取和写入 HDFS 上的文件。
- 例如,
open
方法用于打开文件以读取,create
方法用于创建新文件以写入。
2.2、文件操作
- 支持文件的创建、删除、重命名、追加等操作。
- 例如,
delete
方法用于删除文件或目录,rename
方法用于重命名文件或目录。
2.3、目录操作
- 支持创建、删除和列出目录。
- 例如,
mkdirs
方法用于创建目录,listPaths
方法用于列出目录内容。
2.4、获取文件和目录信息
- 提供方法获取文件和目录的元数据信息。
- 例如,
getFileInfo
方法用于获取文件或目录的详细信息,getLocatedBlocks
方法用于获取文件的块位置。
2.5、与NN、DN通信
- 管理与 NameNode 的通信,用于获取文件的元数据和块位置信息。
- 管理与 DataNode 的通信,用于读取和写入实际的数据块。
3、DFSClient核心源码
DFSClient源码主要包括:创建客户端连接(配置获取、令牌处理、连接地址解析)
3.1、构造方法
3.1.1、代码概述
该构造函数已废弃,接受一个Configuration对象,并调用另一个构造函数获取NameNode地址
@Deprecatedpublic DFSClient(Configuration conf) throws IOException {this(DFSUtilClient.getNNAddress(conf), conf);}
该构造函数接受一个InetSocketAddress对象和一个Configuration对象,并将InetSocketAddress 转换为URI然后调用另一个基于URI的构造函数
public DFSClient(InetSocketAddress address, Configuration conf)throws IOException {this(DFSUtilClient.getNNUri(address), conf);}
该构造函数接受一个URI对象和一个Configuration对象,并将FileSystem.Statistics参数设置为 null,然后调用另一个更完整的构造函数
public DFSClient(URI nameNodeUri, Configuration conf) throws IOException {this(nameNodeUri, conf, null);}
该构造函数接受一个URI对象、一个Configuration对象和一个FileSystem.Statistics对象,然后调用最完整的构造函数
public DFSClient(URI nameNodeUri, Configuration conf,FileSystem.Statistics stats) throws IOException {this(nameNodeUri, null, conf, stats);}
最底层构造函数,该方法不建议直接调用。
@VisibleForTestingpublic DFSClient(URI nameNodeUri, ClientProtocol rpcNamenode,Configuration conf, FileSystem.Statistics stats) throws IOException {// Copy only the required DFSClient configurationthis.tracer = FsTracer.get(conf);this.dfsClientConf = new DfsClientConf(conf);this.conf = conf;this.stats = stats;this.socketFactory = NetUtils.getSocketFactory(conf, ClientProtocol.class);this.dtpReplaceDatanodeOnFailure = ReplaceDatanodeOnFailure.get(conf);this.dtpReplaceDatanodeOnFailureReplication = (short) conf.getInt(HdfsClientConfigKeys.BlockWrite.ReplaceDatanodeOnFailure.MIN_REPLICATION,HdfsClientConfigKeys.BlockWrite.ReplaceDatanodeOnFailure.MIN_REPLICATION_DEFAULT);LOG.debug("Sets {} to {}",HdfsClientConfigKeys.BlockWrite.ReplaceDatanodeOnFailure.MIN_REPLICATION, dtpReplaceDatanodeOnFailureReplication);this.ugi = UserGroupInformation.getCurrentUser();this.namenodeUri = nameNodeUri;this.clientName = "DFSClient_" + dfsClientConf.getTaskId() + "_" +ThreadLocalRandom.current().nextInt() + "_" +Thread.currentThread().getId();int numResponseToDrop = conf.getInt(DFS_CLIENT_TEST_DROP_NAMENODE_RESPONSE_NUM_KEY,DFS_CLIENT_TEST_DROP_NAMENODE_RESPONSE_NUM_DEFAULT);ProxyAndInfo<ClientProtocol> proxyInfo = null;AtomicBoolean nnFallbackToSimpleAuth = new AtomicBoolean(false);if (numResponseToDrop > 0) {// This case is used for testing.LOG.warn("{} is set to {} , this hacked client will proactively drop responses",DFS_CLIENT_TEST_DROP_NAMENODE_RESPONSE_NUM_KEY, numResponseToDrop);proxyInfo = NameNodeProxiesClient.createProxyWithLossyRetryHandler(conf,nameNodeUri, ClientProtocol.class, numResponseToDrop,nnFallbackToSimpleAuth);}if (proxyInfo != null) {this.dtService = proxyInfo.getDelegationTokenService();this.namenode = proxyInfo.getProxy();} else if (rpcNamenode != null) {// This case is used for testing.Preconditions.checkArgument(nameNodeUri == null);this.namenode = rpcNamenode;dtService = null;} else {Preconditions.checkArgument(nameNodeUri != null,"null URI");proxyInfo = NameNodeProxiesClient.createProxyWithClientProtocol(conf,nameNodeUri, nnFallbackToSimpleAuth);this.dtService = proxyInfo.getDelegationTokenService();this.namenode = proxyInfo.getProxy();}String localInterfaces[] =conf.getTrimmedStrings(DFS_CLIENT_LOCAL_INTERFACES);localInterfaceAddrs = getLocalInterfaceAddrs(localInterfaces);if (LOG.isDebugEnabled() && 0 != localInterfaces.length) {LOG.debug("Using local interfaces [{}] with addresses [{}]",Joiner.on(',').join(localInterfaces),Joiner.on(',').join(localInterfaceAddrs));}Boolean readDropBehind =(conf.get(DFS_CLIENT_CACHE_DROP_BEHIND_READS) == null) ?null : conf.getBoolean(DFS_CLIENT_CACHE_DROP_BEHIND_READS, false);Long readahead = (conf.get(DFS_CLIENT_CACHE_READAHEAD) == null) ?null : conf.getLongBytes(DFS_CLIENT_CACHE_READAHEAD, 0);this.serverDefaultsValidityPeriod = conf.getTimeDuration(DFS_CLIENT_SERVER_DEFAULTS_VALIDITY_PERIOD_MS_KEY,DFS_CLIENT_SERVER_DEFAULTS_VALIDITY_PERIOD_MS_DEFAULT,TimeUnit.MILLISECONDS);Boolean writeDropBehind =(conf.get(DFS_CLIENT_CACHE_DROP_BEHIND_WRITES) == null) ?null : conf.getBoolean(DFS_CLIENT_CACHE_DROP_BEHIND_WRITES, false);this.defaultReadCachingStrategy =new CachingStrategy(readDropBehind, readahead);this.defaultWriteCachingStrategy =new CachingStrategy(writeDropBehind, readahead);this.clientContext = ClientContext.get(conf.get(DFS_CLIENT_CONTEXT, DFS_CLIENT_CONTEXT_DEFAULT),dfsClientConf, conf);if (dfsClientConf.getHedgedReadThreadpoolSize() > 0) {this.initThreadsNumForHedgedReads(dfsClientConf.getHedgedReadThreadpoolSize());}this.initThreadsNumForStripedReads(dfsClientConf.getStripedReadThreadpoolSize());this.saslClient = new SaslDataTransferClient(conf, DataTransferSaslUtil.getSaslPropertiesResolver(conf),TrustedChannelResolver.getInstance(conf), nnFallbackToSimpleAuth);}
3.1.2、重点剖析
DFSClient的核心构建方式是传入namenode节点对应的URI以及配置信息,也是我们构建DFSClient通常使用的方法
public DFSClient(URI nameNodeUri, Configuration conf) throws IOException {this(nameNodeUri, conf, null); }
3.2、委托令牌处理
这段源码是一个用于续约和取消 HDFS 委托令牌(Delegation Token)的 Renewer 类,它继承自 TokenRenewer 类。主要功能是通过与 NameNode 通信,维护和管理委托令牌的生命周期。
3.2.1、代码概述
3.2.2、重点剖析
- static静态代码块为初始化hdfs配置文件;
- handleKind方法用于判断是否处理指定类型的委托令牌,在当前源码中会默认判定是否为HDFS的委托令牌类型;
renew
方法用于续约委托令牌。它通过getNNProxy
方法获取到与委托令牌对应的 NameNode 代理,然后调用renewDelegationToken
方法进行委托令牌的续约操作;cancel
方法用于取消委托令牌。它也通过getNNProxy
方法获取 NameNode 代理,然后调用cancelDelegationToken
方法执行委托令牌的取消操作;getNNProxy
方法根据委托令牌获取对应的 NameNode 代理。它首先根据委托令牌的信息构建 URI,然后通过NameNodeProxiesClient
类的静态方法创建 NameNode 的代理对象,并返回该代理对象。
3.3、getLocalInterfaceAddrs
3.3.1、代码概述
这个方法的作用是接受一个接口名称的数组,并根据每个接口名称解析成对应的本地地址(可以是 IP 地址、子网或域名)。它首先尝试将接口名称视为一个 IP 地址,如果不是,则检查它是否是一个有效的子网,如果仍然不是,则假定它是一个域名,并通过 DNS 解析。最终,所有解析出的地址都被封装为 InetSocketAddress
对象,并返回一个包含这些地址的数组。
private static SocketAddress[] getLocalInterfaceAddrs(String interfaceNames[]) throws UnknownHostException {List<SocketAddress> localAddrs = new ArrayList<>();for (String interfaceName : interfaceNames) {if (InetAddresses.isInetAddress(interfaceName)) {localAddrs.add(new InetSocketAddress(interfaceName, 0));} else if (NetUtils.isValidSubnet(interfaceName)) {for (InetAddress addr : NetUtils.getIPs(interfaceName, false)) {localAddrs.add(new InetSocketAddress(addr, 0));}} else {for (String ip : DNS.getIPs(interfaceName, false)) {localAddrs.add(new InetSocketAddress(ip, 0));}}}return localAddrs.toArray(new SocketAddress[localAddrs.size()]);}
3.3.2、重点剖析
- 该方法首先检查interfaceName是否是一个有效的IP地址:
- 如果不是IP地址,检查interfaceName是否是一个有效的子网:
- 如果是有效的子网,获取该子网中的所有IP地址,并将每个IP地址封装为InetSocketAddress对象,添加到localAddrs列表中。
- 如果既不是IP地址也不是子网,假定它是一个域名:
- 通过DNS解析获取该域名的所有IP地址,并将每个IP地址封装为InetSocketAddress对象,添加到localAddrs列表中。
3.4、getRandomLocalInterfaceAddr
3.4.1、代码概述
这个方法的作用是从一组预先配置的本地接口地址 (localInterfaceAddrs
数组) 中随机选择一个地址并返回。
SocketAddress getRandomLocalInterfaceAddr() {if (localInterfaceAddrs.length == 0) {return null;}final int idx = r.nextInt(localInterfaceAddrs.length);final SocketAddress addr = localInterfaceAddrs[idx];LOG.debug("Using local interface {}", addr);return addr;}
3.4.2、重点剖析
- 检查
localInterfaceAddrs
数组是否为空,如果为空则返回null
。 - 使用随机数生成器
r
生成一个随机索引idx
。 - 获取并返回
localInterfaceAddrs
数组中对应索引idx
的SocketAddress
对象。 - 在返回之前,记录调试日志以便于跟踪选中的本地接口地址。
3.5、读写超时时间判定
3.5.1、代码概述
这段代码包含两个方法:getDatanodeWriteTimeout
和 getDatanodeReadTimeout
,它们用于计算数据节点写入和读取的超时时间。每个方法都接收一个参数 numNodes
,表示数据节点的数量。
int getDatanodeWriteTimeout(int numNodes) {final int t = dfsClientConf.getDatanodeSocketWriteTimeout();return t > 0? t + HdfsConstants.WRITE_TIMEOUT_EXTENSION*numNodes: 0;
}int getDatanodeReadTimeout(int numNodes) {final int t = dfsClientConf.getSocketTimeout();return t > 0? HdfsConstants.READ_TIMEOUT_EXTENSION*numNodes + t: 0;
}
3.5.2、重点剖析
- 通过dfsclientconf获取写入\读取超时时间
t;
如果t大于0则
返回t
加上一个扩展超时时间,这个扩展超时时间是常量HdfsConstants.WRITE_TIMEOUT_EXTENSION
乘以numNodes
(数据节点数量)- 如果t<=0,则返回0
3.6、租约管理
3.6.1、代码概述
这段代码定义了三个方法:getLeaseRenewer
、beginFileLease
和 endFileLease
,用于管理HDFS中的文件租约。文件租约机制确保文件在写入过程中不会被其他客户端修改或删除。
public LeaseRenewer getLeaseRenewer() {return LeaseRenewer.getInstance(namenodeUri != null ? namenodeUri.getAuthority() : "null", ugi, this);}/** Get a lease and start automatic renewal */private void beginFileLease(final String key, final DFSOutputStream out) {synchronized (filesBeingWritten) {putFileBeingWritten(key, out);LeaseRenewer renewer = getLeaseRenewer();boolean result = renewer.put(this);if (!result) {// Existing LeaseRenewer cannot add another Daemon, so remove existing// and add new one.LeaseRenewer.remove(renewer);renewer = getLeaseRenewer();renewer.put(this);}}}/** Stop renewal of lease for the file. */void endFileLease(final String renewLeaseKey) {synchronized (filesBeingWritten) {removeFileBeingWritten(renewLeaseKey);// remove client from renewer if no files are openif (filesBeingWritten.isEmpty()) {getLeaseRenewer().closeClient(this);}}}
3.6.2、重点剖析
- 获取租约续约器:
getLeaseRenewer
方法返回一个LeaseRenewer
实例,用于管理租约的续约。- 获取租约续约器
- 调用 LeaseRenewer.getInstance 方法获取 LeaseRenewer 实例。
- 如果 namenodeUri 不为空,则使用其权限部分(authority),否则使用 "null"。ugi(用户组信息)和当前 DFSClient 实例(this)作为参数传递给 LeaseRenewer.getInstance
- 开始文件租约:
beginFileLease
方法将文件添加到写入记录中,并确保当前客户端的租约续约器能够处理该文件的续约。- 使用 key 和 out(DFSOutputStream 实例)调用 putFileBeingWritten 方法,记录正在写入的文件;
- 获取 LeaseRenewer 实例;
- 调用 renewer.put(this) 方法将当前客户端添加到租约续约器中;
- 如果返回结果为 false(表示现有的 LeaseRenewer 不能添加新的守护线程),则移除现有的 LeaseRenewer,获取新的 LeaseRenewer 实例,并将当前客户端添加到新的 LeaseRenewer 中;
- 结束文件租约:
endFileLease
方法移除文件写入记录,并在没有文件写入时关闭客户端的租约续约- 使用 renewLeaseKey 调用 removeFileBeingWritten 方法,从记录中移除正在写入的文件
- 如果没有文件在写入(filesBeingWritten 为空),则获取 LeaseRenewer 实例,调用 renewer.closeClient(this) 方法,关闭当前客户端的租约续约。
todo,未完待续
相关文章:

hdfs源码解析之DFSClient
1、DFSClient类简介 DFSClient 是 Hadoop 分布式文件系统(HDFS)中的一个核心类,用于客户端与 HDFS 之间的交互。它提供了一组方法,使客户端应用程序可以方便地与 HDFS 进行通信,包括文件的读取、写入、创建、删除、重命…...
智能化立体仓库的种类有哪些?
在仓储运输系统中,自动化立体仓库可充分利用空间储存货物,故而也被称之为高层货架仓库。在实际应用中,自动化仓库系统是不需人工处理的情况下能自动存储和取出物料的系统。那么,智能化立体仓库的种类有哪些?下面就让小…...
Stable Diffusion 3 如何下载安装使用及性能优化
Stable Diffusion 3 Stable Diffusion 3(SD3),Stability AI最新推出的Stable Diffusion模型系列,现在可以在Hugging Face Hub上使用,并且可以与Diffusers一起使用。 今天发布的模型是Stable Diffusion 3 Medium&…...

c语言操作符详解
操作符详解 正数的原码反码补码相同 负数的原码最高位数是1,正数为0 整数在内存中存储的是补码 负数的左移与右移,移的是补码,打印的是源码 补码-1取反就是原码。 左移有乘2的效果 左移和右移只针对整数。 vs里的右移操作赋采用的是算数右…...

【耐水好】强耐水UV胶水它的粘接强度和普通UV胶水比如何呢
【耐水好】强耐水UV胶水它的粘接强度和普通UV胶水比如何呢 强耐水UV胶水的粘接强度与普通UV胶水相比,具有显著的优势。以下是详细的比较和归纳: 固化方式: 两者都是通过紫外线(UV)照射进行固化,但强耐水UV…...

jumpserver堡垒机集群搭建
1、环境 操作系统:龙蜥os 7.9 firewall-cmd --permanent --zonepublic --remove-servicessh firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address10.90.101.1 port port22 protocoltcp accept firewall-cmd --reload2、安装NFS…...

Termius for Mac/Win:跨平台多协议远程管理利器
Termius for Mac/Win是一款备受瞩目的跨平台多协议远程管理软件,以其卓越的性能、丰富的功能和便捷的操作体验,赢得了广大用户的青睐。无论是在企业IT管理、系统维护,还是个人远程连接、文件传输等方面,Termius都展现出了出色的实…...

Unity OpenCVForUnity 安装和第二个案例详解 <二>
目录 一、前言 二、场景介绍 1.WebCamTextureToMatExample脚本 2.FpsMonitor脚本 三、 结构体Scaler 四、找到相机并使用 1.相机的启用 2.格式转换 a.把webCamTexture转换成Mat b.把Mat转换成Texture2D 五、脚本组合 六、作者的碎碎念 一、前言 第二个案例…...

Lua实现自定义函数面向对象编程
本文目录 1、引言2、原理3、实例4、层析验证 文章对应视频教程: 暂无,可以关注我的B站账号等待更新。 点击图片或链接访问我的B站主页~~~ 1、引言 在现代软件开发中,面向对象编程(OOP)已经成为一种广泛使用的编程范式…...

docker安装消息队列mq中的rabbit服务
在现代化的分布式系统中,消息队列(Message Queue, MQ)已经成为了一种不可或缺的组件。RabbitMQ作为一款高性能、开源的消息队列软件,因其高可用性、可扩展性和易用性而广受欢迎。本文将详细介绍如何在Docker环境中安装RabbitMQ服务…...

OpenAI新模型发布,免费开放GPT-4o!但只开放一点点...
GPT-4o 中的“o”代表“omni”——指的是 GPT-4o 的多模态。 该模型将向免费客户开放,这意味着任何人都可以通过 ChatGPT 访问 OpenAI 最先进的技术。 GPT-4o 是 OpenAI 昨天晚上发布的新旗舰模型,可以实时推理音频、视觉和文本。 据官方介绍࿰…...

idea的右边栏maven不见了(丢了)解决方案以及idea无法识别maven项目
前言 众所周知,idea是java开发中不可缺少的利器,但是由于功能过多,导致奇怪的问题也很多 问题汇总 idea的右边栏maven丢了 idea无法识别maven项目 对应的解决办法 idea的右边栏maven丢了 原因可能是被自己手动移除了 或者 项目没被正确…...

等待 chrome.storage.local.get() 完成
chrome.storage.local.get() 获取存储处理并计数,内部计数正常,外部使用始终为0,百思不得其解。 如何在继续执行之前等待异步chrome.storage.local.get()完成-腾讯云开发者社区-腾讯云 (tencent.com) 原来我忽略了异步问题,最简…...
004 AOP使用
文章目录 基于AspectJ的AOP的使用添加依赖编写目标类和目标方法使用XML实现实现步骤切入点表达式通知类型 使用注解实现实现步骤环绕通知注解配置定义通用切入点 纯注解方式 基于AspectJ的AOP的使用 其实就是指的SpringAspectJ整合,不过Spring已经将AspectJ收录到自…...
Zookeeper 集群广播事务性能如何保证?
Zookeeper 集群广播事务性能如何保证? zookeeper是如何保证广播事务时,从开始到多数节点确认事务这个高效的? 在 Zookeeper 中,确保广播事务从开始到多数节点确认的高效性至关重要。Zookeeper 通过以下几个关键机制 和优化策略来实现这一目标: ZAB 协议(Zookeeper Atom…...
【vue解决el-input组件自动填充用户名密码】
解决el-input组件自动填充用户名密码 发现用autocomplete"off"并不能解决el-input组件自动填充密码的问题。 解决方法 auto-complete"new-password" 在el-input组件添加auto-complete"new-password" 即可...
案例练习:演讲比赛
演讲比赛: 比赛规则: 某市举行一场演讲比赛( speech_contest ),共有 24 个人参加。比赛共三轮,前两轮为淘汰赛,第三轮为决赛。 比赛方式:分组比赛,每组 6 个人;选手每次…...

推荐一个很好用的Latex写代码的软件
软件名称:Axmath 据说是国产软件,好用是真好用(去哪找?比如某地球号的公主号或其他地方)我是推荐付费购买使用 1.通过图形操作,选择要转成Latex代码的符号,按下转换,直接就出现了我…...
windows 程序右键管理员点击无响应
Windows 程序在右键单击以管理员身份运行时没有响应,可能是由于多种原因引起的。下面是一些常见的问题和解决方案: 1. 用户账户控制 (UAC) 设置问题: - 试着降低或提高 UAC 设置,然后再试一次。可以在控制面板的“用户账户”部…...

开发基于Java语言的SaaS(Software-as-a-Service,软件即服务)模式的HIS系统详解 HIS系统源码 支持二开
开发基于Java语言的SaaS(Software-as-a-Service,软件即服务)模式的HIS系统详解 HIS系统源码 支持二开 开发基于Java语言的SaaS(Software-as-a-Service,软件即服务)模式的HIS(Hospital Informat…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...
Python学习(8) ----- Python的类与对象
Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...