你问这谁会啊?ThreadLocal 父子线程之间该如何传递数据?


忘记之前是哪个公司面试的时候问到的,并不是一个常见的问题,我当时也没回答正确,就按照线程通信那一套比如什么 synchronized、Locks、volatile 啥的 XJB 说的,面试完找了些资料今天整理了下分享给大家~

ThreadLocal 的具体原理这篇文章就不解释了,能干啥大伙儿都倒背如流,其实就两点:

  1. 链路透传(通俗来说就是方便做参数传递,不用在调用方法时携带一堆请求参数)
  2. 线程隔离

每个线程都有自己的一个 ThreadLocalMap,ThreadLocal 持有的数据就是存在这个 Map 里的(Thread.ThreadLocalMap threadLocals),所以能够实现线程隔离,毕竟每个线程的 ThreadLocalMap 都是不一样的

如果子线程想要拿到父线程的中的 ThreadLocal 值怎么办呢

比如会有以下的这种代码的实现。在子线程中调用 get 时,我们拿到的 Thread 对象是当前子线程对象,对吧,每个线程都有自己独立的 ThreadLocal,那么当前子线程的 ThreadLocalMap 是 null 的(而父线程,也就是 main 线程中的 ThreadLocalMap 是有数据的),所以我们得到的 value 也是 null

public class ThreadLocalTest {
 private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args) throws  Exception{
        threadLocal.set("飞天小牛肉");
        System.out.println("父线程的值:"+ threadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程的值:"+ threadLocal.get());
            }
        }).start();

        Thread.sleep(2000);
    }
}

结果输出如下:

父线程的值:飞天小牛肉
子线程的值:null

要如何解决这个问题呢?

我们先来从 Thread 类中找找思路:

你会发现,在 ThreadLocalMap threadLocals 的下方,还有一个 ThreadLocalMap 变量 inherittableThreadLocals,inherit 翻译为继承

先看下这个变量的注释:InheritableThreadLocal values pertaining to this thread. This map is maintained by the InheritableThreadLocal class.

oho,这里出现了一个渣渣辉都从未体验过的传新类:InheritableThreadLocal

翻译一下注释,大概就是,如果你使用 InheritableThreadLocal,那么保存的数据都已经不在原来的 ThreadLocal.ThreadLocalMap threadLocals 里面了,而是在一个新的 ThreadLocal.ThreadLocalMap inheritableThreadLocals 变量中了。

所以,如果想让上面那段代码中,子线程能够拿到父线程的 ThreadLocal 值,只需要把 ThreadLocal 声明改为 InheritableThreadLocal 就可以了

下面我们具体来看下 InheritableThreadLocal 是怎么做到父子线程传值的。

首先看下 new Thread 的时候线程都做了些什么 Thread#init()

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
 // 省略部分代码
 Thread parent = currentThread();
     
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        // copy父线程的 map,创建一个新的 map 赋值给当前线程的inheritableThreadLocals
  this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       
 // 省略部分代码
   }

核心其实就是上面几句代码,如果你设置了 inheritableThreadLocals 变量,那么 Thread 就会把父线程 ThreadLocal threadLocals 中的所有数据都 copy 到子线程的 InheritableThreadLocal inheritableThreadLocals

而且,copy 调用的 createInheritedMap 方法其实是一个浅拷贝函数,key 和 value 都是原来的引用地址,这里所谓的 copy 其实就是把一个 Map 中的数据复制到另一个 Map 中:

至此,大致的解释了 InheritableThreadLocal 为什么能解决父子线程传递 Threadlcoal 值的问题了,总结下:

  1. 在创建 InheritableThreadLocal 对象的时候赋值给线程的 t.inheritableThreadLocals 变量
  2. 在创建新线程的时候会 check 父线程中 t.inheritableThreadLocals 变量是否为 null,如果不为 null 则 copy 一份数据到子线程的 t.inheritableThreadLocals 成员变量中去
  3. InheritableThreadLocal 重写了 getMap(Thread) 方法,所以 get 的时候,就会从 t.inheritableThreadLocals 中拿到 ThreadLocalMap 对象,从而实现了可以拿到父线程 ThreadLocal 中的值

‍‍‍‍

本文转载于网络 如有侵权请联系删除

相关文章

  • 关于Delay函数的思考

    大家好,又见面了,我是你们的朋友全栈君。这几天一直在忙的一个项目中有一小部分是对机械按键的操作,在准备些BSP的时候突然想起来以前在大学 常用的处理方法就是按键消抖然后识别,待消抖最后弹起,并且所有的消抖程序段都是依靠延时程序实现。 可能很多人对该函数的使用并不排斥,但是我个人觉得这是非常不符合软件的本质的,并且也并不合理。 软件的本质是将现实中的各种行为抽象。以现实中人的活动为例,人在同一时刻是可以实时响应很多事情的, 而Delay函数的出现相当于将CPU进行软件暂停而对实时的任务拒之门外(中断除外),这在很多对任务的 执行时间有着严格要求的场合是难以忍受的。并且糟糕的是,系统任务越多,Delay函数的影响越大。那难 倒就没有了别的解决办法了吗?其实答案就在μ/COSii里。 在实时操作系统里有一个概念叫信号量,用来处理不同事件状态的查询或者对不同任务队同一资源的请求。 标志,该时间标志位在50微秒(暂定)的定时器中断中递增,当达到计时时间要求后就传递给响应的需要延 时的任务,然后该变量清零。 我们以按键的识别为例,在实际按键按下以后,需要等待按键可靠弹起,一般来说在一定时间内如果按

  • 连开28个黄网被抓了!网友:就这点钱?还是找个班上吧...

    速领:神作《凤凰架构:构建可靠的大型分布式系统》电子版据金山网报道,镇江市京口区检察院以涉嫌传播淫秽物品牟利罪,对男子郭某批准逮捕。据报道,2020年7月起,郭某通过贴吧等渠道出售淫秽视频网盘链接。2021年初,郭某开设了28个黄色网站,先后上架3000余部淫秽视频。2022年3月21日,郭某被公安机关抓获,共计非法获利70余万元。经审查,京口区检察院于对涉嫌传播淫秽物品牟利的郭某批准逮捕。目前,该案正在进一步办理中。IT之家了解到,根据我国《刑法》规定,以牟利为目的,制作、复制、出版、贩卖、传播淫秽物品的,即会构成犯罪,情节特别严重的,还会处以十年以上有期徒刑或者无期徒刑。有网友评论:28个(黄网)一年才70w,你也别干这了,找个班上吧。 我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!推荐阅读SpringBoot2.7.0发布,2.5.x将停止维护,这节奏你还更得上吗? Java8的Stream操作不好调试?试试这个方法吧! 速

  • [语音识别] kaldi -- aidatatang_200zh脚本解析:三音速详解

    #traintri1[firsttriphonepass] steps/train_deltas.sh--cmd"$train_cmd"\ 250020000data/traindata/langexp/mono_aliexp/tri1||exit1; #steps/train_deltas.sh<num-leaves><tot-gauss><data-dir><lang-dir><alignment-dir><exp-dir>复制num-leaves是叶子节点数目tot-gauss是总高斯数目data-dir是数据文件夹lang-dir是存放语言的文件夹alignment-dir是存放之前单音素对⻬后结果的文件夹exp-dir是存放三音素模型结果的文件夹。#decodetri1 utils/mkgraph.shdata/lang_testexp/tri1exp/tri1/graph||exit1; steps/decode.sh--cmd"$decode_cmd"--c

  • 61 - 进程之间的通信

    用python创建两个进程,在这两个进程之间如何通信呢?frommultiprocessingimportQueue,Process importtime importrandom list1=["java","Python","js"] defwrite(queue): forvalueinlist1: print(f'正在向队列中添加数据-->{value}') queue.put_nowait(value) time.sleep(random.random()) defread(queue): whileTrue: ifnotqueue.empty(): value=queue.get_nowait() print(f'从队列中取到的数据为-->{value}') time.sleep(random.random()) else: break if__name__=='__main__': queue=Queue() write_dat

  • [Skill]程序员零基础速成SQL

    前言严格来说,SQL并不是一门编程语言,只是一个取数工具,与它的原意(结构化查询语言)比较贴切。和很多初学者一样,我学习SQL最大的门槛并非这门语言本身的难易,而是缺乏一个科学有效的学习路径。我尝试过看书(《HeadFirstSQL》,《SQL必知必会》等系统性的书籍),也在一个月内准备并通过了数据库二级、三级的计算机等级考试,更看过形形色色的SQL题目,然而成效甚微。但是在我进入一家互联网公司实习后,每天都需要写大量的SQL且有大牛细心指导,我在短短几天内就能独立对接SQL需求。在没有实习练手机会的情况下,如何在短时间快速上手SQL对于在校学生或者非技术人员都是相当重要的。 章节安排本篇文章的目的主要是帮助初学者在初步知晓SQL语句的情况下在短时间内系统入门SQL,从而解决80%的sql查询问题。 上篇介绍SQL的语法顺序和执行顺序的区别并仔细剖析SQL的执行顺序;中篇详细介绍条件子句、分组查询和排序的细节;下篇会介绍表的连接和其他常用关键字。希望学完这三篇后能助你系统地入门SQL~一个小时上手SQL1.通过一个例子逐步理解SQL语法(单表查询)学生表student结构:学生表先看一

  • JS|JavaScript脚本也可固定位置

    欢迎点击「算法与编程之美」↑关注我们!本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列文章。问题描述“如果不改变<script>标签的位置,如何固定JS的脚本呢”,当我们在网页中写入JavaScript代码时,如果我们每次都必须在其他标签之后嵌入JavaScript代码,不仅会造成代码的冗余,而且也不方便我们检查代码,同时也会增加我们的工作量。我们今天来学习一下怎么将JavaScript脚本位置固定且能让脚本生效吧!解决方案函数是编程语言中很常见的概念,在JavaScript脚本中也不例外。为了让文档生效,我们首先认识JavaScript这门语言当中的一个很重要的知识点‘BOM’。在JavaScript脚本语言中BOM称之为浏览器对象模型。虽然ECMAScript是JavaScript的核心,但如果要在Web中使用JavaScript,则BOM才是真正的核心。我们先来简单的了解一下什么是浏览器对象模型?BOM的核心对象为window,他表示浏览器的一个实例。在浏览器中,window对象有着双重角色,它既是通过JavaScript访问

  • 盘点Hadoop让人讨厌的12件事

    1.Pigvs.Hive你在Pig里用不了HiveUDFS。在Pig中你必须用HCatalog来访问Hive表。你在Hive里用不了PigUDFS。在Hive中无论是多么小的额外功能,我都不会感觉像写一个Pig脚本或者“啊,如果是在Hive里我可以轻易地完成”,尤其是当我写Pig脚本的时候,当我在写其中之一的时候,我经常想,“要是能跳过这堵墙就好了!”。2.被迫存储我所有共享库到HDFS这是Hadoop的复发机制。如果你保存你的Pig脚本到HDFS上,那么它会自动假设所有的JAR文件都会在你那里一样。这种机制在Oozie和别的工具上也出现了。这通常无关紧要,但有时,必须存储一个组织的共享库版本就很痛苦了。还有,大多数时候,你安装在不同客户端的相同JAR,那么为什么要保存两次?这在Pig中被修复了。别的地方呢?3.OozieDebug并不好玩,所以文档里有很多老式的例子。当你遇到错误,可能并不是你做错了什么。可能是配置打印错误或者格式验证错误,统称“协议错误”。很大程度上,Oozie就像Ant或Maven,除了分布式的,不需要工具、有点易错。4.错误信息你在开玩笑,对吧?说到错误信息。我

  • 页面里的父类——BaseUI源代码下载(2009.10.15更新)

    一、介绍和下载名称:页面里的基类(BaseUI)版本:1.0.2上传时间:2009.10.15主要功能:权限验证,URL参数获取、验证,自定义控件的赋值等。下载:http://www.naturefw.com/down/List1.aspx说明:压缩包里面包含“页面里的基类(BaseUI)”和“当前登录人管理(UserManage)”两个项目的源代码。所以您下载了这个包就不需要在去下载“当前登录人管理(UserManage)”。 二、改进记录版本更新日期说明1.0.22009.09.14修改控件ID不一致的bug。当前登录人是否可以访问指定记录的验证实现方式,放到了BaseUserInfo类里面具体实现。1.0.12009.09.11修改PagePermission类,把CheckFunctionID和CheckButtonID两个函数去掉,放到了BaseUserInfo类里面。1.0.02009.09.09第一个版本。三、类图:

  • 【Scikit-Learn 中文文档】广义线性模型 - 监督学习 - 用户指南 | ApacheCN

    中文文档: http://sklearn.apachecn.org/cn/0.19.0/tutorial/basic/tutorial.html英文文档: http://sklearn.apachecn.org/en/0.19.0/tutorial/basic/tutorial.htmlGitHub: https://github.com/apachecn/scikit-learn-doc-zh(觉得不错麻烦给个Star,我们一直在努力)贡献者: https://github.com/apachecn/scikit-learn-doc-zh#贡献者1.1.广义线性模型下面是一组用于回归的方法,其中目标期望值y是输入变量x的线性组合。在数学概念中,如果  是预测值value.在整个模块中,我们定义向量  作为 coef_ 定义  作为 intercept_.如果需要使用广义线性模型进行分类,请参阅 logistic回归 . logistic回归.1.1.1.普通最小二乘法LinearRegression 适合一个带有系数  的线性模型,使得数据集实际观测数据和预测数据(估计值)之间的残差

  • 腾讯云应用合规平台产品概述

    什么是应用合规平台应用合规平台(Applicationcomplianceplatform)是一款提供小程序、移动App应用隐私合规检测的产品,基于相关法律法规、国家标准、行业标准等,对小程序、移动App应用进行静态、动态的技术检测,结合腾讯灵犀隐私合规专家团队专业意见,为企业提供自动化隐私合规检测服务,帮助企业识别应用的隐私合规问题,助力企业安全合规。产品功能小程序隐私合规-基础版小程序隐私合规基础版,主要为微信小程序提供隐私合规检测基础版本服务,基于自动化检测引擎对小程序进行技术检测,协助微信小程序开发者识别小程序当前隐私合规层面的相关基础问题,开发者可自行根据报告进行相关修改。App隐私合规-自动化版通过静态结合动态的智能分析技术,对移动App提供在线自动化合规技术检测服务,帮助企业识别App的数据隐私合规问题。

  • 记 js 上传下载细节

    上传   <inputtype="file"ref="inputFile"accept=".xls,.xlsx"@change="readFile($event)"style="display:none"/>复制 js //上传 asyncreadFile(e){ constfiles=e.target.files if(files.length<=0){ return }elseif(!/(xls|xlsx)$/.test(files[0].name.toLowerCase())){ this.$message.warning('上传格式不正确,请上传xls、xlsx格式') } constform=newFormData() form.append('risenUpload',files[0]) letresult=awaitdemoUpload(form) if(result.success){ this.$message.success('上传成功') }else{ this.$message.error(result.actionErrors

  • MAMP 运行项目

    首先装一个mamp,其次新建一个项目。下面开始配置。 第一步:首先要在MAMP这个软件目录下找到apache的配置文件httpd.conf,路径:/Applications/MAMP/conf/apache/httpd.conf 找到以下这段代码 #Include/Applications/MAMP/conf/apache/extra/httpd-vhosts.conf  然后把前面的#去掉;   第二步:在MAMP这个软件的目录下找到httpd-vhosts.conf这个文件,路径:/Applications/MAMP/conf/apache/extra/httpd-vhosts.conf   加上以下这段代码: DocumentRoot站点配置的项目的路径(绝对路径) ServerName站点的名称,浏览器输入可以直接访问到项目的域名,可以不带www ServerAlias 站点绑定的别名,带www或者其他的域名 最后一步: 找到系统的hosts的文件进行修改,终端输入:open/etc/hosts这个命令。也可以通过

  • 《西安游记》

    西安游记 时间 10.9-10.11三天 交通 北京往返高铁 为什么选择高铁呢?因为高铁站有免费的核酸 关于48小时核酸 兵马俑华清池西安博物馆都需要48小时内的核酸检测 会有专门的人员查时间是否在48h内 准备工作 核酸报告+西安一码通+羽绒服+雨伞(西安贼冷),其余就是旅行必备的了 路线攻略 笔者路线:钟鼓楼-回民街-大唐芙蓉园-兵马俑-华清池-西安事变旧址-大唐不夜城-洒金桥-西安博物院-西安城墙(没上去)-书院门 西安博物院,十二生肖陶俑&彩陶人&玉雕时刻展示汉唐风采很漂亮~书院门书香气太浓了到处都是艺术家们写字作画。带了个小葫芦是街边手艺人现刻的黄铜饰品~西安城墙很保存确实完整~ 陕西历史博物馆比较遗憾没能打卡,骊山封山了不让上去~个人最喜欢兵马俑和看的两场演出(复活军团&西安事变舞台剧,小贵但是很震撼)~~大唐芙蓉园建议白天晚上各刷一次感受不同。华清池不太推荐,4个水泥坑即使导游讲了也不太能脑补画面~~西安城墙有时间的可以去骑一圈~~回民街和洒金桥的美食个人感觉中规中矩没有很惊艳(不喜欢酸辣味道)~~大唐不夜城晚上确实很热闹一条商业街物价偏贵~~钟鼓

  • gem安装cocoapods

    1.升级Ruby环境 sudogemupdate--system复制 如果Ruby没有安装,请参考 如何在MacOSX上安装Ruby运行环境 2.安装CocoaPods时我们要访问cocoapods.org,用淘宝的RubyGems镜像来代替官方版本,执行以下命令: $gemsources--removehttps://rubygems.org/ //等有反应之后再敲入以下命令 $gemsources-ahttps://ruby.taobao.org/复制 为了验证你的Ruby镜像是并且仅是taobao,可以用以下命令查看: $gemsources-l复制 只有在终端中出现下面文字才表明你上面的命令是成功的: ***CURRENTSOURCES*** http://ruby.taobao.org/复制 上面的命令,有时试了会没有效,请参考淘宝网的https://ruby.taobao.org $gemsources--addhttps://ruby.taobao.org/--removehttps://rubygems.org/ $gemsources

  • 通过漫画轻松掌握HDFS工作原理(转)

    hadoop文件分布式系统(hdfs)client:人们坐在我面前,请求我去读写数据namenode:我只有一个,我来指挥这里所有的事情datanode:我们存储数据,我们很多人,有时候会有几千人 在HDFS集群中写入操作①用户请求:“用户:‘让我们写一些数据吧,client先生,请给我写入200M的数据’;client:‘这是我的荣幸,但是。。。’。”②块和复制:“client:‘你没有忘记什么东西吧?’;用户:‘噢,对了。。请帮我把数据分为128M一个包,每个包复制到三个地方;”③一个优秀的客户端总是知道这两件事情:blocksize:大的文件分为许多的包(通常包的大小为64或者128Mb)replicationfactor:每一个包被复制存储在多个地方(一般是三个) ①分解文件为包:client:首先我会把这个大的文件分解为许多包②请求namenode:client:首先让我们为第一个包开始工作吧,namenode先生,请帮我写一个128Mb的包复制三份③namenode分配datanodes:namenode:复制三份,hmm。。。需要找三个datanode给这个client,

  • ac自动机

    给出一些字符串,求对于每个字符串,在这些字符串里有多少子串 利用ac自动机+树链的并可以求解 //#pragmaGCCoptimize("Ofast,no-stack-protector,unroll-loops,fast-math") //#pragmaGCCtarget("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native") //#include<immintrin.h> //#include<emmintrin.h> #include<bits/stdc++.h> usingnamespacestd; #definerep(i,h,t)for(inti=h;i<=t;i++) #definedep(i,t,h)for(inti=t;i>=h;i--) #definelllonglong #defineme(x)memset(x,0,sizeof(x)) #defineILinline #definerintregisterint inlinellrd()

  • IDEA中的JUNIT测试

    安装插件 Ctrl+Alt+s→Plugins→junitgeneratorv2.0   Alt+insert选中JUnittest中JUnit4 packagetest.com.demo.controller; importcom.baomidou.mybatisplus.plugins.Page; importcom.demo.DemoApplication; importcom.demo.entity.User; importcom.demo.error.ErrorCode; importcom.demo.exceptionHander.BusinessException; importcom.demo.service.IUserService; importorg.junit.After; importorg.junit.Before; importorg.junit.Test; importorg.junit.runner.RunWith; importorg.springframework.beans.factory.annotation.Autowired

  • MySQL索引的基本使用

    转自:https://www.cnblogs.com/whgk/p/6179612.html 一、什么是索引?为什么要建立索引? 索引用于快速找出在某一列中有一特定值的行。如果不使用索引,MySQL必须从第一条记录开始读完整个表,直到找出相关的行;表越大,查询数据所花费的时间就越多。如果表中查询的列有一个索引,MySQL能够快速到达一个位置去搜索数据文件,而不必查看所有数据,那么将会节省很大一部分时间。 例如:有一张person表,其中有2W条记录。有一个Phone的字段记录每个人的电话号码,现在想要查询出电话号码为xxxx的人的信息。  如果没有索引,那么将从表中第一条记录一条条往下遍历,直到找到该条信息为止。如果有了索引且将Phone字段设置为索引,那么会将该Phone字段通过一定的方法进行存储,以便查询该字段上的信息时能够快速找到对应的数据,而不必在遍历2W条数据了。 其中MySQL中的索引的存储类型有两种:BTREE、HASH。也就是用树或者Hash值来存储该字段。 二、MySQL中索引的优点和缺点和使用原则 优点:1、所有的MySql列类型(字段类型)都可以被索引,

  • Ubuntu下安装gsoap

    昨天在ubuntu下进行安装gSOAP,费了很多时间,没成功,今天又来找了大量教程资料,终于一次成功,这里写下自己的安装步骤和方法,供大家参考。 首先下载gsoap,我下载的是gsoap-2.8.1.zip 用unzipgsoap-2.8.1.zip命令解压缩,会解压生成gsoap-2.8文件夹。 cdgsoap-2.8 在安装之前需要先安装一些编译工具。 安装编译工具:   $sudoapt-getinstallbuild-essential   为了成功编译gSOAP,您需要安装GTK+的开发文件和GLib库(libraries)。   $sudoapt-getinstalllibgtk2.0-devlibglib2.0-dev   安装Checkinstall以便管理您系统中直接由源代码编译安装的软件。   $sudoapt-getinstallcheckinstall     安装YACC,YACC是Unix/Linux上一个用来生成编译器的编译器(编译器代码生成器)。   &

  • Java如何生成随机数 - Random、ThreadLocalRandom、SecureRandom

    Java7的Random伪随机数和线程安全的ThreadLocalRandom 一、Random伪随机数: Random类专门用于生成一个伪随机数,它有两个构造器:一个构造器使用默认的种子(以当前时间作为种子),另个构造器需要程序员显式传入一个long整数的种子. 当使用默认的种子或传入相同的种子构造Random对象时,它们属于同一个种子,只要两个Random对象的种子相同,而且方法的调用顺序也相同,它们就会产生相同的数字序列也就是说,Random产生的数字并不是真正随机的,而是一种伪随机。 ![](https://img2020.cnblogs.com/blog/1438655/202102/1438655-20210208160430455-983724190.png) 常用解决方案: 为了避免两个Random对象产生相同的数字序列,通常推荐使用当前时间作为Random对象的种子: Randomrand=newRandom(System.currentTimeMi11is()); 二、ThreadLocalRandom ThreadLocalRandom类是Java

  • PHP的常用函数 持续更新

    PHP的常用函数 前言: 由于害怕遗忘,故在此记录下常用的php函数,以便复习 1define函数 作用:定义常量注意,只能定义常量,不能定义变量。 用法 <?php define('a',100); ?> 复制 2intval函数 作用:向下取整 用法 <?php echointval(1.1); //输出结果是1; ?> 复制 3strpos函数 作用:获得某一字符或一子字符串在一字符串中的位置 用法 <?php $str="string"; echostrpos($str,'s'); ?> 复制 底层似乎是用kmp实现的?不是很确定 4substr函数 作用:截取字符串 用法 <?php $str="123"; echosubstr($str,1,2); ?> 复制 输出:23; 如果不设置终点,将截取到末尾 返回值是字符串 5str_split函数 作用:分割字符串 将字符串分割为字符串数组 用法 <?php $str="123"; $res=str_split($str); print_r($res); ?>

相关推荐

推荐阅读