前言

在上一篇文章 Unity3D热更新之LuaFramework篇[06]--Lua中是怎么实现脚本生命周期的 中,我分析了由LuaBehaviour来实现lua脚本生命周期的方法。

但在实际使用中发现,只有一个这样的脚本还不够。

LuaBehaviour驱动XxxPanel.lua脚本的方法,只适用于界面相对简洁的情况(界面上只有少量的Image、Text和其它UI组件),一但遇到稍微复杂一点的情况,就有点捉襟见肘了,比如一个包含多个子项的排行榜页面。

现以一个排行榜的示例来说明。

一、创建一个排行榜页面

1、创建一个大厅场景,相机及Canvas设置与之前的main场景相同,然后创建一个HallPanel面板。

同时创建HallPanel.lua和HallCtrl.lua脚本并做相应注册(添加到CtrlNames和PanelNames里并做Require)。

面板上放两个按钮(排行榜、商城),且这个面板不做成由PanelMgr加载的预制体,就这么挂在Canvas下好了。

2、创建一个排行榜RankingPanel,其结构主要是几个垂直排序的RankItem,如下图所示。

同时创建RankingPanel.lua和RankingCtrl.lua并做相应注册。

这个面板也不做成由PanelMgr加载的那种,就放在Canvas下,通过SetActive来控制显示与隐藏(开发中这种使用方式应该也很常见)。

3、功能需求:

  1) 点击HallPanel上的排行榜按钮,弹出排行榜面板;

  2)点击排行榜上的子项,弹出各自的名字及顺序;

  难点分析:

  难点1,怎么实现HallPanel的点击事件

    假如不是用的Lua,而是c#,实现这个功能也太简单了,刚入门Unity的新手也知道怎么做。

       假如HallPanel是一个动态加载的,那实现排行榜按钮的点击事件也好做,因为有LuaBehaviour以及之前我们自己实现的UIEventEx。 由于这个是非预制体加载的,所以这条路也走不通。

       思路:手动给这个HallPanel挂载LuaBehaviour.cs脚本试试?不行就自己写个差不多的脚本。

 难点2,怎么让RankItem独自产生行为

前言中有提到过LuaBehavoiur并不适用所有情况,这个就是一种。在一个设计良好的架构中,XxxPanel.lua最好只处理浅层布局的元素,对于复杂的嵌套的UI或者元素较多的UI,最好让它们自行处理自己的行为。

   这个需求放在这里就是,不在RankingCtrl.lua和RankingPanel.lua中处理RankItem的逻辑,而是交由RankItem自行处理。

    思路:创建一个RankItem.lua脚本(拥有事件处理功能以及其它生命周期能力),与RankItem对象绑定。

  这两个难点,其实反映的是一个问题,我有一个unity对象,又创建了一 个lua脚本,怎么让它们产生绑定关系?

  下面来尝试解决问题。

二、处理HallPanel的UI事件

方法1:使用LuaBehaviour脚本

1、直接给HallPall对象添加LuaBehaviour脚本;

  2、在Game.lua中把初始自动加载Panel的语句注释掉。

    CtrlManager.Init();
local ctrl = CtrlManager.GetCtrl(CtrlNames.Login);
if ctrl ~= nil and AppConst.ExampleMode == then
-- ctrl:Awake(); --就是这一句决定首先加载什么面板
end

3、给HallPanel的InitPanel方法添加查找按钮控件的语句,并在HallCtrl中添加按钮事件,具体修改见代码:

local transform;
local gameObject; HallPanel = {};
local this = HallPanel; --启动事件--
function HallPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; this.InitPanel();
logWarn("Awake lua--->>"..gameObject.name);
end --初始化面板--
function HallPanel.InitPanel()
logWarn("我是HallPanel,我被加载了."); --排行榜按钮
HallPanel.rankingBtn = transform:FindChild("BtnRanking").gameObject; --调用Ctrl中panel创建完成时的方法
HallCtrl.OnCreate(gameObject);
end function HallPanel.OnDestroy()
logWarn("OnDestroy---->>>");
end

HallPanel.lua

HallCtrl = {};
local this = HallCtrl; local behaviour;
local transform;
local gameObject; --构建函数--
function HallCtrl.New()
logWarn("HallCtrl.New--->>");
return this;
end function HallCtrl.Awake()
logWarn("HallCtrl.Awake--->>"); logWarn("我是HallCtrl,我被加载了.");
end --启动事件--
function HallCtrl.OnCreate(obj)
gameObject = obj;
transform = obj.transform; UIEventEx.AddButtonClick(HallPanel.rankingBtn, function ()
log("你点击了排行榜按钮");
end); end --单击事件--
function HallCtrl.OnClick(go)
destroy(gameObject);
end --关闭事件--
function HallCtrl.Close()
panelMgr:ClosePanel(CtrlNames.Hall);
end

HallCtrl.lua

有一点需要注意的是,之前UI事件处理的方法是在XxxCtrl中的OnCreate方法里处理,这个方法在XxxPanel预制体加载后被回调。

现在HallPanel没有预制体加载的过程,所以要在InitPanel方法的末尾手动加一句对HallCtrl.OnCreate方法的调用。

4、运行游戏

点击运行后,发现,InitPanel方法中的日志语句没有输出,点击按钮也没有响应。

经跟踪调试发现,在处理HallPanel面板时,其身上的LuaBehaviour脚本中Awake方法的执行时,Lua虚拟机的初始化还没完成,甚至是在执行Start方法时其初始化也没初始化完成。

所以,从LuaBehaviour的Awake中调用HallPanel.lua脚本的Awake是不可能成功的(Lua虚拟机没初始化完成,所有Lua脚本也没被加载)。

LuaBehaviour脚本本身没问题,这个问题的出现,是因为我们想绕过LuaFramework的加载流程引起的。

5、解决问题

想解决这个问题,就需要修改 Awake方法的调用时机。

为了不破坏原有的LuaBehaviour脚本,我们复制一个LuaBehaviour脚本并重命名为"CustomBehaviour"。

并在CustomBehaviour的Awake的0.1秒之后,再调用HallPanel.lua的Awake方法,见下图:

重新给HallPanel对象挂载CustomBehaviour脚本后,再运行游戏,

能看到InitPanel方法被正确执行了,按钮事件也生效了。

说明:用延时的方法去执行Awake,虽然让Lua中的方法执行了,但也破坏了Awake的原本执行顺序。如果对框架了解不深或游戏逻辑处理不够严谨,则会引起问题。

这只是一个临时方法,完善的解决方案可以看看PanelMgr的加载流程,应该能找到答案。

三、显示RankingPanel面板并处理RankItem子项

1、显示RankingPanel面板

在HallPanel.lua中引用RankingPanel面板,并在HallCtrl.lua中添加点击事件,见下图:

如此,当点击排行榜按钮时,就会显示排行榜面板了(运行前要把RankingPanel禁掉)。

完整的HallPanel.lua

local transform;
local gameObject; HallPanel = {};
local this = HallPanel; --启动事件--
function HallPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; this.InitPanel();
logWarn("Awake lua--->>"..gameObject.name);
end --初始化面板--
function HallPanel.InitPanel()
logWarn("我是HallPanel,我被加载了."); --排行榜按钮
HallPanel.rankingBtn = transform:FindChild("BtnRanking").gameObject; --排行榜面板
HallPanel.rankingPanel = transform.parent:Find("RankingPanel"); --调用Ctrl中panel创建完成时的方法
HallCtrl.OnCreate(gameObject);
end function HallPanel.OnDestroy()
logWarn("OnDestroy---->>>");
end

完整的HallCtrl.lua

HallCtrl = {};
local this = HallCtrl; local behaviour;
local transform;
local gameObject; --构建函数--
function HallCtrl.New()
logWarn("HallCtrl.New--->>");
return this;
end function HallCtrl.Awake()
logWarn("HallCtrl.Awake--->>"); logWarn("我是HallCtrl,我被加载了.");
end --启动事件--
function HallCtrl.OnCreate(obj)
gameObject = obj;
transform = obj.transform; UIEventEx.AddButtonClick(HallPanel.rankingBtn, function ()
log("你点击了排行榜按钮"); HallPanel.rankingPanel.gameObject:SetActive (true);
end); end --单击事件--
function HallCtrl.OnClick(go)
destroy(gameObject);
end --关闭事件--
function HallCtrl.Close()
panelMgr:ClosePanel(CtrlNames.Hall);
end

2、处理RankItem

思路: 我们的目标是让RankItem具有独立处理逻辑的能力(包括生命周期函数的执行),想到的第一个办法就是继续使用上边讲到的CustomBehaviour脚本。

CustomBehaviour适用于面板加载,且每个面板要对应一个XxxPanel.lua和XxxCtrl.lua,并且还要注册,用起来有点不方便。所在决定重新创建一个C#脚本,以处理各种Item类型的Unity对象(如RankItem,ShopItem等)与Lua的绑定关系。

考虑到RankItem可能是动态创建的,所以这个脚本应该有绑定unity对象与Lua脚本对象的能力。

步骤:

1)创建一个LuaComponent脚本

将这个脚本放在 “Assets\LuaFramework\Scripts\Utility”下,这个脚本包含将GameObjet与LuaTable进行绑定的Add方法以及调用Lua脚本生命周期函数的方法。见下图

LuaCompnent.cs的完整代码:

/*
* 让Lua脚本也能挂载到游戏物体上的组件
*
* LuaComponent主要有Get和Add两个静态方法,其中Get相当于UnityEngine中的GetComponent方法,Add相当于AddComponent方法,
* 只不过这里添加的是lua组件不是c#组件。每个LuaComponent拥有一个LuaTable(lua表)类型的变量table,它既引用上述的Component表。
* Add方法使用AddComponent添加LuaComponent,调用参数中lua表的New方法,将其返回的表赋予table。
* Get方法使用GetComponents获取游戏对象上的所有LuaComponent(一个游戏对象可能包含多个lua组件,由参数table决定需要获取哪一个),
* 通过元表地址找到对应的LuaComponent,返回lua表
*
* Add by TYQ
*/ using UnityEngine;
using System.Collections;
using LuaInterface;
using LuaFramework; public class LuaComponent : MonoBehaviour
{
//Lua表
public LuaTable table; //添加LUA组件 public static LuaTable Add(GameObject go, LuaTable tableClass)
{ LuaFunction fun = tableClass.GetLuaFunction("New"); if (fun == null) return null; /*object[] rets = fun.Call(tableClass);
if (rets.Length != 1) return null; LuaComponent cmp = go.AddComponent(); cmp.table = (LuaTable)rets[0];
*/ //lua升级后不,Call方法不再返回对象,因此改为Invoke方法实现
object rets = fun.Invoke<LuaTable, object>(tableClass);
if (rets == null)
{
return null;
}
LuaComponent cmp = go.AddComponent<LuaComponent>();
cmp.table = (LuaTable)rets; cmp.CallAwake();
return cmp.table;
} //添加LUA组件,允许携带额外一个参数(args)
public static LuaTable Add(GameObject go, LuaTable tableClass, LuaTable args)
{
LuaFunction fun = tableClass.GetLuaFunction("New");
if (fun == null)
return null; object rets = fun.Invoke<LuaTable, object>(tableClass);
if (rets == null)
{
return null;
}
LuaComponent cmp = go.AddComponent<LuaComponent>();
cmp.table = (LuaTable)rets; cmp.CallAwake(args);
return cmp.table;
} //添加LUA组件
// isAllowOneComponent为true时,表示只添加一次组件,如果已存在,就不再添加
public static LuaTable Add(GameObject go, LuaTable tableClass, bool isAllowOneComponent)
{
//如果已存在,则不再添加
LuaComponent luaComponent = go.GetComponent<LuaComponent>();
if (luaComponent != null)
{
return null;
} LuaFunction fun = tableClass.GetLuaFunction("New"); if (fun == null)
return null; object rets = fun.Invoke<LuaTable, object>(tableClass);
if (rets == null)
{
return null;
}
LuaComponent cmp = go.AddComponent<LuaComponent>();
cmp.table = (LuaTable)rets; cmp.CallAwake();
return cmp.table;
} //获取lua组件 public static LuaTable Get(GameObject go, LuaTable table) {
/*
LuaComponent[] cmps = go.GetComponents();
foreach (LuaComponent cmp in cmps)
{
string mat1 = table.ToString();
string mat2 = cmp.table.GetMetaTable().ToString();
if (mat1 == mat2)
{
return cmp.table;
}
}
*/ LuaComponent cmp = go.GetComponent<LuaComponent>();
string mat1 = table.ToString();
string mat2 = cmp.table.GetMetaTable().ToString();
if (mat1 == mat2)
{
return cmp.table;
} return null; } //删除LUA组件的方法略,调用Destory()即可 //调用lua表的Awake方法
void CallAwake()
{ LuaFunction fun = table.GetLuaFunction("Awake"); if (fun != null)
fun.Call(table, gameObject);
} //调用lua表的Awake方法(携带一个参数)
void CallAwake(LuaTable args)
{ LuaFunction fun = table.GetLuaFunction("Awake");
if (fun != null)
fun.Call(table, gameObject, args);
} private void OnEnable()
{
// Debug.Log("================================================================================");
//Debug.Log(table); if (table == null)
{
//Debug.LogWarning("Table is Null---------------------");
return;
} LuaFunction fun = table.GetLuaFunction("OnEnable"); if (fun != null)
{
fun.Call(table, gameObject);
}
} void Start() {
LuaFunction fun = table.GetLuaFunction("Start"); if (fun != null) fun.Call(table, gameObject);
} void Update()
{
//效率问题有待测试和优化 //可在lua中调用UpdateBeat替代 LuaFunction fun = table.GetLuaFunction("Update"); if (fun != null) fun.Call(table, gameObject);
} private void FixedUpdate()
{
LuaFunction fun = table.GetLuaFunction("FixedUpdate"); if (fun != null) fun.Call(table, gameObject);
} private void LateUpdate()
{
LuaFunction fun = table.GetLuaFunction("LateUpdate"); if (fun != null) fun.Call(table, gameObject);
} void OnCollisionEnter(Collision collisionInfo) { //略 } //更多函数略 private void OnDisable()
{
if (table != null) {
LuaFunction fun = table.GetLuaFunction("OnDisable"); if (fun != null)
{
fun.Call(table, gameObject);
}
}
} private void OnDestroy()
{
if (table != null)
{
LuaFunction fun = table.GetLuaFunction("OnDestroy"); if (fun != null)
{
fun.Call(table, gameObject);
}
}
} }

这个脚本的写法参考了知乎上 罗培羽 大佬的一篇文章 :Unity3D热更新LuaFramework入门实战(4)——Lua组件

该文章里有详细的原理阐述,我这里就不多解释了。

LuaComponent.cs脚本创建完毕后,需要添加到CustomSetting.cs文件中并进行导出操作(Generate All)。

2)创建一个RankItem.Lua的脚本,并放在Controller/Hall目录下。

RankItem的主要功能是在其Start方法中查找子组件并赋值 以及 添加按钮点击事件,见代码:

function RankItem:Start()
-- 这里的id, name, score来源于绑定时的赋值,见RankingPanel的 InitPanel方法
-- 设置Id
self.obj.transform:Find("TextOrder"):GetComponent("Text").text = self.id;
-- 设置name
self.obj.transform:Find("TextName"):GetComponent("Text").text = self.name;
-- 设置score
self.obj.transform:Find("TextScore"):GetComponent("Text").text = self.score; UIEventEx.AddButtonClick(self.obj, function ()
log("你点击了RankItem " .. self.name);
end);
end

RankItem.lua的完整代码在这里:

RankItem = {
--里面可以放一些属性
name = "RankItem",
index = -, --索引
obj = nil --脚本关联的对象
} function RankItem:Awake()
--print("RankItem Awake name = "..self.name );
end function RankItem:Start() -- 设置Id
self.obj.transform:Find("TextOrder"):GetComponent("Text").text = self.id;
-- 设置name
self.obj.transform:Find("TextName"):GetComponent("Text").text = self.name;
-- 设置score
self.obj.transform:Find("TextScore"):GetComponent("Text").text = self.score; UIEventEx.AddButtonClick(self.obj, function ()
log("你点击了RankItem " .. self.name);
end);
end --Item点击事件
function RankItem.OnItemClick (go, selfData) end function RankItem:Update() end --创建对象
function RankItem:New(obj)
local o = {}
setmetatable(o, self)
self.__index = self
return o
end

3)在RankingPanel.lua中查找RankItem的引用,并进行绑定操作

a.声明rankitemData变量,这里存放的是将要显示在RankItem上的数据。

b.查找rankItem子组件并用LuaComponent.Add方法执行绑定操作,代码如下:

--排行榜项数据
local rankItemData = {
{id = , name = "张三1", score = },
{id = , name = "张三2", score = },
{id = , name = "张三3", score = },
{id = , name = "张三4", score = }
} --初始化面板--
function RankingPanel.InitPanel() local rankList = transform:FindChild("RankList");
for i = , rankList.childCount do local go = rankList:GetChild(i - ).gameObject;
log(go.name); local item = LuaComponent.Add(go, RankItem);
item.name = rankItemData[i].name;
item.index = i;
item.obj = go; item.id = rankItemData[i].id;
item.score = rankItemData[i].score;
end RankingCtrl.OnCreate(gameObject);
end

完整的RankingPanel.lua代码在这里:

local transform;
local gameObject; require("Controller/Hall/RankItem") RankingPanel = {};
local this = RankingPanel; --启动事件--
function RankingPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; this.InitPanel();
logWarn("=========Awake lua--->>"..gameObject.name);
end --排行榜项数据
local rankItemData = {
{id = , name = "张三1", score = },
{id = , name = "张三2", score = },
{id = , name = "张三3", score = },
{id = , name = "张三4", score = }
} --初始化面板--
function RankingPanel.InitPanel() local rankList = transform:FindChild("RankList");
for i = , rankList.childCount do local go = rankList:GetChild(i - ).gameObject;
log(go.name); local item = LuaComponent.Add(go, RankItem);
item.name = rankItemData[i].name;
item.index = i;
item.obj = go; item.id = rankItemData[i].id;
item.score = rankItemData[i].score;
end RankingCtrl.OnCreate(gameObject);
end --单击事件--
function RankingPanel.OnDestroy()
logWarn("OnDestroy---->>>");
end

4)运行

运行Hall场景,点出排行榜面板。

能看到在lua脚本给定的值(rankItemData )已经被正确显示到RankItem上了。点击相应项,输出的内容也符合预期。

总结

要用Lua做逻辑开发,怎么让unity对象绑定lua脚本,是一个绕不过去的问题。由于网上相关资料比较少,这一篇讲的都是自己摸出来的一点门道,不知道写得是否对,但勉强还能用,仅供参考。

补充一个在LuaFramework中实现Update的简单方法

要在XxxPane中实现Update等方法,直接在其Awake函数中写 UpdateBeat:Add(Update, self) 就行,见代码

function XxxPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; UpdateBeat:Add(Update, self);
FixedUpdateBeat:Add(FixedUpdate, self);
LateUpdateBeat:Add(LateUpdate, self);
end

Add函数的第一个参数是一个function, 是这个脚本中定义的函数。这个UpdaateBeat应该是框架实现的全局函数。

2019-07-28更新 :

已找到新的启动HallPanel的方式,放弃使用CustomBehaviour并延迟调用Awake的方法,操作如下:

a)移除HallPanel身上的CustomBehaviour;

b)在Game.lua的OnInitOK方法末尾添加如下语句

    --查找HallPanel对象,并发起对HallPanel.Awake的调用
local objHallPanel = UnityEngine.GameObject.Find("Canvas").transform:GetChild().gameObject;
HallPanel.Awake(objHallPanel);

代码位置见下图:

c)重新运行unity,点击排行榜按钮,效果如前。

至于RankItem.lua和LuaComponent.cs,不存在问题,依然用之前介绍的使用方式。

Unity3D热更新之LuaFramework篇[07]--怎么让unity对象绑定Lua脚本的更多相关文章

  1. Unity3D热更新之LuaFramework篇[10]--总结篇

    背景 19年年初的时候,进到一家新单位,公司正准备将现有的游戏做成支持热更的版本.于是寻找热更方案的任务就落在了我头上. 经过搜索了解,能做Unity热更的方案是有好几种,但是要么不够成熟,要么不支持 ...

  2. Unity3D热更新之LuaFramework篇[03]--prefab加载和Button事件

    在上一篇文章 Unity3D热更新之LuaFramework篇[02]--用Lua创建自己的面板 中,我介绍了LuaFramework加载面板的方法,但这个方法并不适用于其它Prefab资源,在这套框 ...

  3. Unity3D热更新之LuaFramework篇[02]--用Lua创建自己的面板

    在上篇文章 Unity3D热更新之LuaFramework篇[01]--从零开始 中,我们了解了怎么获得一个可用的LuaFramework框架. 本篇将我会先介绍一下如何配置Lua开发环境,然后分析在 ...

  4. Unity3D热更新之LuaFramework篇[09]--资源热更新与代码热更新的具体实现

    前言 在上一篇文章 Unity3D热更新之LuaFramework篇[08]--热更新原理及热更服务器搭建 中,我介绍了热更新的基本原理,并且着手搭建一台服务器. 本篇就做一个实战练习,真正的来实现热 ...

  5. Unity3D热更新之LuaFramework篇[05]--Lua脚本调用c#以及如何在Lua中使用Dotween

    在上一篇文章 Unity3D热更新之LuaFramework篇[04]--自定义UI监听方法 中,我对LuaBehaviour脚本进行了扩展,添加了两个新的UI监听方法,也提到最好能单写一个脚本处理此 ...

  6. Unity3D热更新之LuaFramework篇[04]--自定义UI监听方法

    时隔一个多月我又回来啦! 坚持真的是很难的一件事,其它事情稍忙,就很容易说服自己把写博客的计划给推迟了. 好在终于克服了自己的惰性,今天又开始了. 本篇继续我的Luaframework学习之路. 一. ...

  7. Unity3D热更新之LuaFramework篇[06]--Lua中是怎么实现脚本生命周期的

    前言 用c#开发的时候,新建的脚本都默认继承自Monobehaviour, 因此脚本才有了自己的生命周期函数,如Awake,Start, Update, OnDestroy等. 在相应的方法中实现游戏 ...

  8. Unity3D热更新之LuaFramework篇[08]--热更新原理及热更服务器搭建

    前言 前面铺垫了这么久,终于要开始写热更新了. Unity游戏热更新包含两个方面,一个是资源的更新,一个是脚本的更新. 资源更新是Unity本来就支持的,在各大平台也都能用.而脚本的热更新在iOS平台 ...

  9. Unity3D热更新之LuaFramework篇[01]--从零开始

    前言 因工作关系,需要对手头的项目进行热更新支持.了解后发现,Lua的几个变种:XLua.ToLua(原uLua)和Slua都可以做Unity热更,而ToLua更是提供了一个简易的热更框架--LuaF ...

随机推荐

  1. 安装使用Cloudera Impala

    安装与使用Cloudera Impala Cloudera Impala提供快速的.交互式的SQL查询方式,直接基于Apache Hadoop存储在HDFS或HBase中的数据进行查询.除了使用与Ap ...

  2. react中使用高德地图的原生API

    干货,无话 1.react-create-app,创建新react项目 2.npm install react-amap,引入高德地图的封装 3.编写组件index.js import React f ...

  3. 基于jsp技术的校园二手交易网站

    [项目介绍]基于jsp的校园二手商品交易网站系统使用jsp技术进行开发,项目主要实现了一整套的校园二手交易逻辑, 主要功能如下(包括但不限于,只列出主要功能): 管理员模块    |-----用户管理 ...

  4. Windows下OSGEarth的编译过程

    目录 1. 依赖 1) OpenSceneGraph 2) GDAL 3) CURL 4) GEOS 5) 其他 2. 编译 1) 设置参数 2) 配置路径 3) 生成编译 3. 参考文献 1. 依赖 ...

  5. vue项目实现表格导出excel表格

    第一:安装依赖 npm install -S file-saver xlsx npm install -D script-loader 第二:在目录里新建excel文件夹 在excel文件夹里新建两个 ...

  6. Spring Framework 组件注册 之 FactoryBean

    Spring Framework 组件注册 之 FactoryBean 前言 前两篇文章介绍了如何使用@Component,@Import注解来向spring容器中注册组件(javaBean),本文将 ...

  7. CentOS 操作防火墙

    1:查看防火状态 systemctl status firewalld 2:暂时关闭防火墙 systemctl stop firewalld 3:永久关闭防火墙 systemctl disable f ...

  8. 从同步原语看非阻塞同步以及Java中的应用

    非阻塞同步:基于冲突检测的乐观并发策略,通俗讲就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了,如果争用数据有冲突那就采用其他的补偿措施(最常见的就是不断重试直到成功),这种乐观的并发策略 ...

  9. Python之Pandas库学习(一):简介

    官方文档 1. 安装Pandas windos下cmd:pip install pandas 导入pandas包:import pandas as pd 2. Series对象 带索引的一维数组 创建 ...

  10. Java连接MYSQL进行操作(增,删,改)

    连接数据库,并用表格输出数据 创建insex.jsp <table border="1" width="80%" align='center'> & ...