开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

如何在Swift中使用AsyncStream创建类似回调的行

发表于 2024-04-27

如何在Swift中使用AsyncStream创建类似回调的行为

hudson 译 原文

毫无疑问,Swift并发彻底改变了我们在Swift中处理异步代码的方式。它的一个强大组件是AsyncStream,这是一种特殊的AsyncSequence形式,非常适合使用async/await 语法实现回调或类似委托的行为。

在Swift 并发之前,开发人员必须依靠闭包来触发回调,并在异步操作期间通知调用者某些事件。然而,随着AsyncStream的引入,这种基于闭包的方法现在可以被更直观、更直接的async/await语法所取代。

在本文中,让我们探索一个简单而说明性的示例,说明如何利用AsyncStream来跟踪下载操作的进度。阅读完成后,您将很好地了解AsyncStream的工作原理,并开始在自己的项目中使用它。

所以,不用多说,让我们开始吧。

示例应用程序

为了展示AsyncStream的力量,让我们创建一个示例应用程序,该应用程序将模拟下载操作,并使用进度条显示下载进度。

在这里插入图片描述

为了模拟通常与文件下载相关的等待期,我创建了一个带有 performDownload() 方法的File结构,该方法将随机睡眠一段时间。

1
2
3
4
5
6
7
8
9
10
11
swift复制代码struct File {

let name: String

func performDownload() async {

// Sleep for a random amount of time to emulate the wait required to download a file
let downloadTime = Double.random(in: 0.03...0.5)
try? await Task.sleep(for: .seconds(downloadTime))
}
}

在现实生活中,这种performDownload() 方法很可能由连接到服务器并等待其响应的代码组成。

有了这个解释,让我们深入研究有趣的部分。

创建异步流

首先,让我们创建一个下载器类(FileDownloader),该类接受File文件对象数组并逐个下载,每次成功下载后,它将通过提供下载文件的文件名来通知调用者。

为了实现这种行为,基于闭包的方法很可能看起来像这样:

1
2
3
4
5
6
swift复制代码static func download(_ files: [File], completion: (String) -> Void) {

// Download each file and trigger completion handler
// ...
// ...
}

然而,如果我们选择async/await语法,我们将需要用AsyncStream替换完成处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
swift复制代码static func download(_ files: [File]) -> AsyncStream<String> {

// Init AsyncStream with element type = `String`
let stream = AsyncStream(String.self) { continuation in

// Perform download operation and yield the downloaded file’s filename
// ...
// ...
}

return stream
}

如上述代码所示,我们可以通过给它一个元素类型和自定义闭包来初始化AsyncStream,该闭包将元素交给AsyncStream。在我们的案例中,我们将元素类型设置为String,因为每次下载成功时,我们的闭包将产生下载文件的文件名。

有了这些,我们可以像这样实现执行下载操作的自定义闭包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
swift复制代码// Init AsyncStream with element type = `String`
let stream = AsyncStream(String.self) { continuation in

Task {
for file in files {

// Download the file
await file.performDownload()

// Yield the element (filename) when download is completed
continuation.yield(file.name)
}

// All files are downloaded
// Call the continuation’s finish() method when there are no further elements to produce
continuation.finish()
}
}

使用AsyncStream时要记住的一个要点是在完成所有操作后调用延续的finish() 方法。这一步骤至关重要,因为如果不这样做,将导致在调用点无限期等待,导致我们应用程序中的意外和非预期的行为。

消费AsyncStream

有了FileDownloader,是时候将其与用户界面集成以显示下载进度了。首先,我们将创建50个File对象并触发下载过程。

1
2
3
4
5
6
7
swift复制代码let totalFile = 50

// Generate file objects
let files = (1...totalFile).map { File(name: “Image_\($0).jpg”) }

// Start download
let downloaderStream = FileDownloader.download(files)

现在,为了在UI上显示下载进度,我们将利用给定的AsyncStream实例(downloaderStream),并利用for-wait-in语法处理每个文件名,文件名是在调用延续的yield()方法时由 AsyncStream生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
swift复制代码Task {
var downloadedFile = 0
for await filename in downloaderStream {

downloadedFile += 1

// Update progress bar
progressBar.progress = Float(downloadedFile) / Float(totalFile)

// Update status label
statusLabel.text = “Downloaded \(filename)”
}

statusLabel.text = “Download completed”
}

如前所述,调用延续的 finish() 方法是一个必不可少的步骤。如果没有此步骤,for循环将无限期等待,状态消息将不会更改为“下载完成”。

如果您想亲自尝试一下,您可以在GitHub上找到完整的示例代码。

感谢您的阅读。👨🏻‍💻

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

可解决传统保险丝缺陷的电子保险丝efuse

发表于 2024-04-27

近年来,电子保险丝/熔断器获得了越来越多的关注,业界对此类解决方案的需求也在不断增加。传统的玻璃管保险丝、片式保险丝和聚合物保险丝很容易受到环境温度和其他使用条件的影响,而且熔断电流的精确度较低。此外,响应速度也很慢。近年来,由于电子系统的小型化和系统性能提高的需要,这些问题的影响显得越来越严重。还有一个需要考虑的重要因素,就是传统的保险丝在熔断之后必须进行维修或更换。此外,现在还要求新一代的保险丝具有更先进的功能,以符合IEC62368-1标准(与ICT和AV设备相关的新安全标准)。

电子保险丝/熔断器是集成电路(IC)。MOSFET用于电压-电流检测电路、控制电路和电流通路的通断。因为它们是集成电路,所以在紧凑的封装内集成了许多高精度和快速响应的功能模块,包括过流、过电压、短路、过热保护等。传统的保险丝基本上只是切断电流通路,而电子保险丝却能提供许多保护和控制功能,从而能更容易地获得IEC62368-1认证。电子保险丝能够克服传统保险丝的相关缺陷。

)

eFuse IC的三大优点

eFuse IC是什么?

eFuse IC的应用有哪些?

eFuse IC的电路示例

)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

大厂面试题:两道来自京东的关于MyBatis执行器的面试题

发表于 2024-04-27

大家好,我是王有志。
今天给大家带来两道来自于京东关于的 MyBatis 面试题:

  • MyBatis 提供了哪些执行器(Executor)?它们有什么区别?
  • Mybatis 中如何指定 Executor 的类型?

MyBatis 提供了哪些执行器(Executor)?它们有什么区别?

MyBatis 提供的 Executor

严格意义上 MyBatis 中提供了 4 种 Executor:

image

其中具有执行 SQL 语句能力的是继承自 BaseExecutor 的 3 种 Executor:

  • SimpleExecutor,最基础的 Executor,每次执行 SQL 语句时都会创建 Statement 实例对象,完成 SQL 语句的执行后关闭 Statement 实例对象,无任何性能上的优化;
  • ReuseExecutor,提供复用 Statement 能力的 Executor,ReuseExecutor 会将 Statement 缓存到 Map<String, Statement> 实例对象中,其中 key 是 String 类型的 SQL 语句,而 value 是 SQL 语句的 Statement 对象,这样避免了频繁创建和销毁 Statement 带来的性能损耗;
  • BatchExecutor,提供了批量处理 Statement 的能力,在执行 update 语句时,将所有的 Statement 对象添加到 BatchExecutor 的 statementList 对象中,等到执行 SqlSession#commit时 统一提交,避免了频繁与数据库交互带来的性能损耗。

以上 3 种 Executor 除了自身的特点外,它们还具备抽象类 BaseExecutor 提供的一级缓存的能力。

CachingExecutor 本身不具备执行 SQL 语句的能力,它提供了对 MyBatis 二级缓存的支持。CachingExecutor 会持有一个继承自 BaseExecutor 的实例对象,CachingExecutor 在执行 SQL 语句时会调用自身持有的 Executor 实例对象来完成 SQL 语句的执行。

MyBatis 默认的 Executor

如果我们刨除不能够独立执行 SQL 语句的 CachingExecutor 的话,MyBatis 默认的 Executor 是 SimpleExecutor。如果把 CachingExecutor 也算在内的话,由于 MyBatis 是默认开启二级缓存的,因此默认的 Executor 就是 CachingExecutor。

这点可以在 MyBatis 的源码中得到印证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java复制代码public class Configuration {

protected boolean cacheEnabled = true;

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
return (Executor) interceptorChain.pluginAll(executor);
}
}

源码的第 514 行中,是根据配置的 ExecutorType 创建继承自 BaseExecutor 的 Executor 实例对象,第 1517 行中,判断了 cacheEnabled 的配置情况,并决定是否要使用 CachingExecutor。

Mybatis 中如何指定 Executor 的类型?

MyBatis 配置 Executor 的方式

MyBatis 中有两种方式指定 Executor 的类型:

  • 通过 MyBatis 的核心配置指定 MyBatis 的 Executor 类型;
  • 创建 SqlSession 时可以指定指定 Executor 的类型。

MyBatis 核心配置文件中配置 Executor

可以在核心配置文件 mybatis-config.xml 中通过 settings 元素来配置 Executor 的类型,例如:

1
2
3
4
5
6
7
8
9
XML复制代码<configuration>
<!-- 省略 -->

<settings>
<setting name="defaultExecutorType" value="REUSE"/>
</settings>

<!-- 省略 -->
</configuration>

这样我们通过 mybatis-config.xml 创建的 SqlSession 中都会使用 ReuseExecutor。

创建 SqlSession 时指定 Executor

除了在核心配置文件 mybatis-config.xml 中配置 Executor 的类型外,还以在获取 MyBatis 的 SqlSession 实例对象时指定 Executor 的类型,如下:

1
2
3
4
5
Java复制代码    Reader mysqlReader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(mysqlReader);

// 指定 Executor的类型
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);

如果同时在核心配置文件 mybatis-coonfig.xml 中配置了 Executor 的类型,且在创建 SqlSession 的实例对象时也指定了 Executor 的类型,此时以创建 SqlSession 的实例对象时指定的 Executor 类型为准。


好了,今天的内容就到这里了,如果本文对你有帮助的话,希望多多点赞支持,如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠王有志,我们下次再见!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【TVM 教程】在树莓派上部署预训练模型

发表于 2024-04-27

此教程介绍如何用 Relay 编译 ResNet 模型,并将其部署到树莓派。

1
2
3
4
5
6
javascript复制代码import tvm
from tvm import te
import tvm.relay as relay
from tvm import rpc
from tvm.contrib import utils, graph_executor as runtime
from tvm.contrib.download import download_testdata

在设备上构建 TVM Runtime

首先在远程设备上构建 TVM runtime。

本节和下一节中的所有指令都应在目标设备(例如树莓派)及 Linux 上执行。

由于我们是在本地机器上进行编译,远程设备仅用于运行生成的代码,因此只需在远程设备上构建 TVM runtime。

1
2
3
4
5
6
7
bash复制代码git clone --recursive https://github.com/apache/tvm tvm
cd tvm
mkdir build
cp cmake/config.cmake build
cd build
cmake ..
make runtime -j4

runtime 构建完成后,在 ~/.bashrc 文件中设置环境变量——用 vi ~/.bashrc 命令来编辑 ~/.bashrc,添加下面这行代码(假设 TVM 目录在 ~/tvm 中):

1
ruby复制代码export PYTHONPATH=$PYTHONPATH:~/tvm/python

执行 source ~/.bashrc 来更新环境变量。

在设备上设置 RPC 服务器

在远程设备(该示例中为树莓派)上运行以下命令,启动 RPC 服务器:

1
css复制代码python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090

看到如下结果,则表示 RPC 服务器启动成功:

1
ruby复制代码INFO:root:RPCServer: bind to 0.0.0.0:9090

准备预训练模型

注意:确保主机已经(用 LLVM)安装了完整的 TVM。

使用 MXNet Gluon 模型集合 中的预训练模型。更多有关这部分的信息详见 编译 MXNet 模型 教程。

1
2
3
4
5
6
python复制代码from mxnet.gluon.model_zoo.vision import get_model
from PIL import Image
import numpy as np

# 用一行代码来获取模型
block = get_model("resnet18_v1", pretrained=True)

为了测试模型,下载一张猫的图片,并转换其格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
ini复制代码img_url = "https://github.com/dmlc/mxnet.js/blob/main/data/cat.png?raw=true"
img_name = "cat.png"
img_path = download_testdata(img_url, img_name, module="data")
image = Image.open(img_path).resize((224, 224))

def transform_image(image):
image = np.array(image) - np.array([123.0, 117.0, 104.0])
image /= np.array([58.395, 57.12, 57.375])
image = image.transpose((2, 0, 1))
image = image[np.newaxis, :]
return image

x = transform_image(image)

synset 用于将 ImageNet 类的标签,转换为人类更容易理解的单词。

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码synset_url = "".join(
[
"https://gist.githubusercontent.com/zhreshold/",
"4d0b62f3d01426887599d4f7ede23ee5/raw/",
"596b27d23537e5a1b5751d2b0481ef172f58b539/",
"imagenet1000_clsid_to_human.txt",
]
)
synset_name = "imagenet1000_clsid_to_human.txt"
synset_path = download_testdata(synset_url, synset_name, module="data")
with open(synset_path) as f:
synset = eval(f.read())

以下代码可将 Gluon 模型移植到可移植的计算图上:

1
2
3
4
5
6
ini复制代码# 在 mxnet.gluon 中支持 MXNet 静态计算图(符号)和 HybridBlock
shape_dict = {"data": x.shape}
mod, params = relay.frontend.from_mxnet(block, shape_dict)
# 添加 softmax 算子提高概率
func = mod["main"]
func = relay.Function(func.params, relay.nn.softmax(func.body), None, func.type_params, func.attrs)

以下是一些基本的数据工作负载配置:

1
2
3
4
ini复制代码batch_size = 1
num_classes = 1000
image_shape = (3, 224, 224)
data_shape = (batch_size,) + image_shape

编译计算图

用计算图的配置和参数调用 relay.build() 函数,从而编译计算图。但是,不能在具有 ARM 指令集的设备上部署 x86 程序。除了用来指定深度学习工作负载的参数 net 和 params,Relay 还需要知道目标设备的编译选项。不同选项会导致性能不同。

如果在 x86 服务器上运行示例,可将其设置为 llvm。如果在树莓派上运行,需要指定它的指令集。若要在真实设备上运行,需将 local_demo 设置为 False。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ini复制代码local_demo = True

if local_demo:
target = tvm.target.Target("llvm")
else:
target = tvm.target.arm_cpu("rasp3b")
# 上面一行代码是下面代码的简单形式:
# target = tvm.target.Target('llvm -device=arm_cpu -model=bcm2837 -mtriple=armv7l-linux-gnueabihf -mattr=+neon')

with tvm.transform.PassContext(opt_level=3):
lib = relay.build(func, target, params=params)

# 在 `relay.build` 之后,会得到三个返回值:计算图,库和新参数,因为我们做了一些优化,它们会改变参数,但模型的结果不变。

# 将库保存在本地临时目录中。
tmp = utils.tempdir()
lib_fname = tmp.relpath("net.tar")
lib.export_library(lib_fname)

输出结果:

1
2
3
4
vbnet复制代码/workspace/python/tvm/relay/build_module.py:411: DeprecationWarning: Please use input parameter mod (tvm.IRModule) instead of deprecated parameter mod (tvm.relay.function.Function)
DeprecationWarning,
/workspace/python/tvm/driver/build_module.py:268: UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.
"target_host parameter is going to be deprecated. "

通过 RPC 远程部署模型

利用 RPC 可将模型从主机部署到远程设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ini复制代码# 从远程设备获取 RPC session。
if local_demo:
remote = rpc.LocalSession()
else:
# 下面是教程环境,把这个改成你目标设备的 IP 地址
host = "10.77.1.162"
port = 9090
remote = rpc.connect(host, port)

# 将库上传到远程设备,并加载它
remote.upload(lib_fname)
rlib = remote.load_module("net.tar")

# 创建远程 runtime 模块
dev = remote.cpu(0)
module = runtime.GraphModule(rlib["default"](dev))
# 设置输入数据
module.set_input("data", tvm.nd.array(x.astype("float32")))
# 运行
module.run()
# 得到结果
out = module.get_output(0)
# 得到排第一的结果
top1 = np.argmax(out.numpy())
print("TVM prediction top-1: {}".format(synset[top1]))

输出结果:

1
bash复制代码TVM prediction top-1: tiger cat

下载 Python 源代码:deploy_model_on_rasp.py

下载 Jupyter Notebook:deploy_model_on_rasp.ipynb

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

拥有你自己的Copilot!基于Llama3和CodeGPT

发表于 2024-04-27

当谈到代码自动补全和辅助编程工具时,GitHub Copilot是一个备受推崇的选择。然而,有时我们可能希望在本地环境中构建一个类似的解决方案,以便更好地控制数据和隐私,更重要的是Llama3是免费,而Github Copilot是收费的。本文将分享如何基于Llama3和CodeGPT这两个强大的开源项目,搭建自己的本地Copilot。

部署Llama3模型

在超越GPT-3.5!Llama3个人电脑本地部署教程中我已经分享过如何使用Ollama在本地部署Llama3模型,本文不再赘述。

安装CodeGPT扩展

打开Visual Studio Code,转到扩展标签页。搜索“CodeGPT”并安装这个扩展。CodeGPT是一个可以使用多种大语言模型辅助代码编程的插件。

注意:要认证发布者是CodeGPT,不要安装CSDN发布的!

image-20240425204658304

设置Llama3为CodeGPT默认模型

安装完CodeGPT之后,VSCode左侧会出现CodeGPT的产品图标,按照下图设置Llama3为CodeGPT使用的模型。

image-20240425205154839

测试

下面是笔者的测试截图,可以看到模型工作正常,提供了一个Echo Server例子:

image-20240425210425337

在日常开发中,我们可以在任意源码处点击右键让CodeGPT对代码进行解释或者优化。

image-20240425210538668

与收费的GitHub Copilot不同,Llama3提供了免费的解决方案,并且我们可以更好地控制数据和隐私。通过安装Llama3模型和CodeGPT扩展,我们能够在本地环境中享受到强大的代码辅助功能。无论是解释代码还是进行优化,CodeGPT都能为我们提供准确而实用的建议。

希望本文对你构建自己的本地Copilot有所帮助,让你在编程过程中更高效、更愉快!

在搭建本地Copilot的过程中有任何疑问都可以关注公众号加群进行交流。

WX20240420-091554@2x.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Conda创建与激活虚拟环境(指定虚拟环境创建位置) 1C

发表于 2024-04-27

1.Conda优势

Conda是一个开源的软件包管理系统和环境管理系统,主要用于在不同的计算环境中安装和管理软件包和其依赖项。它最初是为Python而设计的,但现在也可以用于管理其他语言的软件包。

Conda提供了对虚拟环境的支持,这使得用户可以在同一台计算机上同时管理多个相互独立的Python环境这对于开发和测试不同的项目或在项目之间切换时非常有用。

总结:Conda所创建的每一个虚拟环境都可以对应你的每一个Python项目,你的每一个Python项目所需的软件包等等东西可能不同,为了不使项目之间产生冲突,你可以为他们每一个配置一个虚拟环境,虚拟环境之间互不干扰。

2.Anaconda的下载和安装

太简单了,这里跳过,简单说就是进入官网,下载,按照提示安装即可,B站视频很多(这里的Anaconda就是Conda)

3.Conda创建虚拟环境以及相关注意问题

方法一:

  1. 打开Anaconda Prompt(找不到的直接在开始里面搜索,或者去你安装Anaconda的文件夹里面找),打开后窗口页面如下

![{KYZDBSSA_QC_3FH`0QT@E7.png](https://gitee.com/songjianzaina/juejin_p4/raw/master/img/968a864e40e07bb0819d0a7de61bb40dbf8c10353e24efb0315fa93a3713317c)
2. 创建和激活虚拟环境

  • 法一:

创建:

在prompt中输入以下代码:

conda create --name myenv python=3.6

myenv为你想要创建的虚拟环境的名称,python=3.6为该环境配置的python版本,如果不想添加python的话删掉即可

激活:注意,你通过这种形式创建的虚拟环境默认存在C盘当中,且其可以在Anaconda中找到(也就是下图的环境列表中找到),因此激活该虚拟环境的办法一是直接在下面的conda环境列表里面点击切换,二是在Anaconda prompt中输入代码conda activate 你设定的环境变量名称

64~M50%YDJ3%}Q6L9Z23SY.png

  • 法二(在其他盘创建虚拟环境):

创建:如果你想要将虚拟环境创建在D盘,可以使用--prefix参数来指定路径。以下是一个示例命令,用于将虚拟环境创建在D盘中:

conda create --prefix D:\path\to\myenv python=3.6

通过上述命令,你可以将虚拟环境创建在D盘的指定路径下。然后,当你激活这个虚拟环境时,可以使用conda activate D:\path\to\myenv来激活它,但无法使用环境名称来激活它。[注意:该方法创建的虚拟环境不会在Anaconda环境列表中显示]

方法二:

直接在Anaconda环境列表面板创建(此处原理等同于上文法一):

![~G6P(GK1N8BVFG3JNP)`V@Q.png](https://gitee.com/songjianzaina/juejin_p4/raw/master/img/b9e6c5b780b3094e8a4533cf9736b142dd4ac4ba7fa74eefa384f71b77f9e0c2)
选择你需要的python版本

UXC6WIP_}2FFUG5QP}CC1VF.png
最后,大家可以通过在prompt中输入conda env list来查看自己的全部环境,顺带说一句,如果是用上文方法二中的法二来创建的虚拟环境在列表中会显示其位置但不会显示其名称,同时在Anaconda的环境列表里面也是不会显示的,请大家注意!

那么如果我既想把环境创建在其他盘来减少C盘内存的占用又想可以直接通过他的名字直接激活该虚拟环境,这该怎么办呢?

详解请看下文 更改Conda虚拟环境创建位置并用其名称激活的方法

欢迎大家点赞收藏和交流!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

更改Conda虚拟环境创建位置并用其名称激活的方法

发表于 2024-04-27

很多同学说,用了网上或者我前文给的创建虚拟环境的方法创建虚拟环境后,要么就是虚拟环境无法使用其名称进行激活,必须使用其路径,特别麻烦,要么就是还是创建在C盘,占据大量内存,那到底有什么方法能够更换Conda虚拟环境创建位置并用其名称激活呢?

笔者给出下解:

要实现将虚拟环境创建在D盘,并且在激活环境时直接使用环境名称的方法,可以通过设置环境变量来实现。你可以在Conda的配置文件中指定环境的路径,这样在激活环境时就可以直接使用环境名称。

下面是具体的步骤:

  1. 打开Anaconda Prompt或者命令行窗口。
  2. 使用以下命令打开Conda的配置文件(通常是.condarc文件):
1
erlang复制代码notepad %USERPROFILE%.condarc
  1. 在打开的配置文件中添加以下内容:
1
2
vbnet复制代码envs_dirs:
- D:\path\to\envs

确保将路径D:\path\to\envs替换为你希望存储虚拟环境的实际路径。

  1. 保存并关闭配置文件。
  2. 现在,当你使用conda create --name myenv python=3.6创建虚拟环境时,它会被保存在你指定的D盘路径下。然后,你可以使用conda activate myenv来激活这个虚拟环境,而无需输入完整的路径。

通过这种方法,你可以轻松地在D盘创建虚拟环境,并且在激活时直接使用环境名称。

该方法的原理是修改了Anaconda配置安装虚拟环境位置的源文件。

希望该方法对你有帮助,欢迎点赞收藏和交流!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

大厂前端面试题 var x = 100;consolelo

发表于 2024-04-27

前言

大家面试的时候,会不会碰到这么一道题目。

1
2
3
4
5
php复制代码var global = 100;
function fn(){
console.log(global);
}
fn();

面试官问,这段代码打印的是什么?心中暗喜,这不简简单单打印的就是100嘛,这面试官问的也特简单了吧,于是脱口而出100,然后面试官继续问,为什么是100?然后你仔细一思索,于是这么回答到:因为在全局域中定义了一个变量global,函数作用域能访问到全局作用域的变量,所以打印的是100。于是面试官说,你还是回家等消息吧。然后你百思不得其解,我回答的没错呀,为什么面试官叫我回家呢?你回答的确实没错,但是还是那句话,你回答的还不够优秀,没有答到面试官的心窝窝里面去。

接下来就由蘑菇头来解答一下这道简单但是其实并不简单的面试题。

首先上述回答一点问题都没有,但是我们要知道,大厂考这么简单的一道题,那么他想考察的知识点肯定不会这么简单,你以为他想考察的是作用域,其实,面试官真正想要考察的是编译原理。他想知道你对这么一段代码的理解程度。我们从编译原理的角度来分析这里为什么打印的是100。

预编译

我们先清楚一个概念,什么是预编译。代码在执行前需要进行编译操作,用于确定变量的作用域,提高运行效率。预编译的过程包括词法分析、语法分析、语义分析、代码生成等步骤,用于确定各种代码之间的关联。

所以上面这段代码会执行一次预编译的过程。在此之前,我们先了解一下v8是怎么理解一个函数的,比如下面这个函数

1
2
3
scss复制代码function foo(){//函数的声明
}
foo();//函数的调用

foo函数,在JavaScript中它是一个对象,按道理来说对象身上就会有一些属性和方法,所以foo函数身上也会有一些属性和方法如:

1
2
3
4
lua复制代码foo.name //"foo";
foo.length //0; 表示函数参数的长度
foo.prototype //undefined; 表示函数的原型
foo.[[scope]] //函数的作用域属性 我们无法访问 v8引擎内部属性 -隐式属性

这个作用域属性也就是我们现在要聊的。每个函数都会有一个作用域属性,在 JavaScript 中,函数的作用域属性是指在函数创建时,除了开辟内存和赋值外,系统还会为该函数设置一个作用域。当前函数的作用域等于当前函数创建时所在的上下文。这个作用域也叫函数AO对象,AO 对象(Activation Object)是函数执行前的一瞬间生成的一个对象。它主要用于存储函数的上下文信息,包括函数的参数、局部变量、内部函数等,当函数调用结束后,AO对象会被销毁,当再次调用时,AO对象会再次创建。里面会用键值对的形式存储有效标识符。在全局作用域下会创建GO对象。如果您还不了解作用域的话可以移步这篇文章:作用域你真的了解吗? - 掘金 (juejin.cn)

代码是一行一行执行的,当v8扫描到函数的声明时,并不会进行编译,当碰到函数的调用时,v8才会进行编译,他会先找到这个函数的声明,并且创建AO对象。

OK,当我们了解这些之后,我们可以完整的分析一下上述代码v8是如何理解的。

1
2
3
4
5
php复制代码var global = 100;
function fn(){
console.log(global);
}
fn();

首先,v8会先创建GO对象,然后在全局作用域下找所有的变量声明,将变量名当做key,值为undefined存入GO对象中,所以现在GO对象中有一个global属性,然后在全局中找函数声明,将函数名作为key,值为函数体存入,现在GO对象中有global和fn。执行语句,将global的值更新为100,然后开始执行函数体fn(),在执行fn()函数体之前,会进行编译,v8会创建AO对象,然后在函数作用域下找所有的形参和变量名,将形参和变量名作为key,值为undefined存入AO对象中,然后就是形参和实参统一,将AO对象形参key的值更新为实参的值,然后在函数体内找函数声明,将函数名作为AO的属性名值为该函数体,并且该AO对象指向会指向GO对象。这个函数编译完成,执行fn,打印global,会先在当前作用域AO中查找,如果没有找到,则会在上一级作用域中查找,直到全局作用域中查找。如果全局作用域中也没有找到,则会报错,。在全局中找到一个global值为100,所以打印100。如果该函数体fn内还有函数执行,会再次执行上述过程,在执行语句调用前编译并且创建新的AO对象并且指向上一个AO对象也就是fn的作用域。

1
2
3
4
5
6
7
8
javascript复制代码//GO AO 对象更新过程
GO:{
global:undefined --> 100,
fn:function(){}
}
AO:{
//找不到global变量,去上一级GO作用域找
}

当我们知道V8的编译原理之后,如果碰到以下问题,那我们就能神挡杀神,佛挡杀佛。

1
2
3
4
5
6
7
8
9
10
11
12
13
javascript复制代码function fn(a){
console.log(a);//function a(){}
var a = 123;
console.log(a);//123
function a(){}
console.log(a);//123
var b = function(){}
console.log(b);//function(){}
function c(){}
var c = a;
console.log(c);//123
}
fn(1);

问打印的是什么?把自己想像成v8,看看他是怎么理解的。

1
2
3
4
5
6
7
8
9
javascript复制代码//赋值过程
GO{
fn:fn(){}
}
AO{
a:undefined --> 1 --> function a(){} -->123
b:undefined --> function(){}
c:undefined -->function c(){} -->123
}

总结

今天我们学习了v8的编译过程,主要可以分成两种:

1、发生在全局

a.创建GO对象

b.找变量声明,将变量名key和值undefined存入GO对象中

c.在全局找函数声明,将函数名作为GO的属性名,值为该函数体

d.执行函数体,函数体中使用变量时,会先在当前作用域中查找,如果没有找到,则会在上一级作用域中查找,直到全局作用域中查找。如果全局作用域中也没有找到,则会报错。

全局代码先编译,然后执行,在执行过程中可能会碰到其他函数调用,函数调用时,会先编译该函数,然后执行。

2、发生在函数体内

a.创建函数作用域AO对象,找形参和变量名,将变量名key和值undefined存入AO对象中

b.形参和实参统一

c.在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Tapable的神秘之处-源码解析(2) Tapable的神

发表于 2024-04-27

Tapable的神秘之处-源码解析(2)

前言:研究webpack过程中,发现tapable在其中占据了很重要的成分,所以就看看它的奥妙之处

Tapable是一个在webpack中被广泛使用的核心模块,它提供了一组灵活的钩子函数,可以在不同的生命周期中插入自定义的逻辑。通过使用Tapable,我们可以轻松地实现各种功能,从简单的插件拓展到复杂的编译过程优化。在这篇文章中,我们将深入探索Tapable的神秘之处,了解它的底层实现。

仓库地址:github.com/webpack/tap…

源码目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
js复制代码tapable
├─ .babelrc
├─ .editorconfig
├─ .eslintrc
├─ .gitattributes
├─ .gitignore
├─ .prettierrc.js
├─ .travis.yml
├─ README.md
├─ lib
│ ├─ AsyncParallelBailHook.js
│ ├─ AsyncParallelHook.js
│ ├─ AsyncSeriesBailHook.js
│ ├─ AsyncSeriesHook.js
│ ├─ AsyncSeriesLoopHook.js
│ ├─ AsyncSeriesWaterfallHook.js
│ ├─ Hook.js
│ ├─ HookCodeFactory.js
│ ├─ HookMap.js
│ ├─ index.js
│ ├─ MultiHook.js
│ ├─ SyncBailHook.js
│ ├─ SyncHook.js
│ ├─ SyncLoopHook.js
│ ├─ SyncWaterfallHook.js
│ ├─ util-browser.js
│ └─ __tests__
│     ├─ AsyncParallelHooks.js
│     ├─ AsyncSeriesHooks.js
│     ├─ Hook.js
│     ├─ HookCodeFactory.js
│     ├─ HookStackOverflow.js
│     ├─ HookTester.js
│     ├─ MultiHook.js
│     ├─ SyncBailHook.js
│     ├─ SyncHook.js
│     ├─ SyncHooks.js
│     ├─ SyncWaterfallHook.js
│     └─ __snapshots__
│       ├─ AsyncParallelHooks.js.snap
│       ├─ AsyncSeriesHooks.js.snap
│       ├─ HookCodeFactory.js.snap
│       └─ SyncHooks.js.snap
├─ LICENSE
├─ package.json
├─ tapable.d.ts
└─ yarn.lock

书接上文的Tapable的神秘之处-源码解析(1),今天我们继续来看看lib目录中的其他文件

工具方法lib/HookMap.js

代码里定义个一个HookMap类的构造函数 接受两个可选参数:factory(工厂函数)和name(HookMap的实例名称)。在构造函数中,初始化了一些实例属性,包括_map、name、_factory、_interceptors等,并设置了默认的调用方法(call、callAsync、promise)

1
2
3
4
5
6
7
8
9
10
11
js复制代码初始化配置部分
constructor(factory, name = undefined) {
   // 内部映射用于存储和管理钩子信息
   this._map = new Map();
   // hookMap实例名称标识,用于后续引用。
   this.name = name;
   // 传入的工厂函数 factory 赋值给 this._factory,这样以后就可以用来创建新的钩子。
   this._factory = factory;
   // 用于存储拦截器,拦截器可以在钩子创建过程中修改它们或者在钩子触发事件之前做一些操作
   this._interceptors = [];
}
1
2
3
4
5
6
7
8
9
10
11
js复制代码HookMap.prototype.tap = util.deprecate(function(key, options, fn) {
return this.for(key).tap(options, fn);
}, "HookMap#tap(key,…) is deprecated. Use HookMap#for(key).tap(…) instead.");

HookMap.prototype.tapAsync = util.deprecate(function(key, options, fn) {
return this.for(key).tapAsync(options, fn);
}, "HookMap#tapAsync(key,…) is deprecated. Use HookMap#for(key).tapAsync(…) instead.");

HookMap.prototype.tapPromise = util.deprecate(function(key, options, fn) {
return this.for(key).tapPromise(options, fn);
}, "HookMap#tapPromise(key,…) is deprecated. Use HookMap#for(key).tapPromise(…) instead.");

get(key)方法和for(key)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
js复制代码get(key) {
   return this._map.get(key);
}

for(key) {
   // 寻找this._map里面是否存在对应key的钩子,如果寻找的到,直接就返回现有的钩子
   const hook = this.get(key);
   if (hook !== undefined) {
       return hook;
  }
// 调用_factory函数创建一个新的钩子(按需创建)
   let newHook = this._factory(key);
// 遍历所有的拦截器,并且新的钩子注入,方便拦截器可以修改钩子的状态信息
   const interceptors = this._interceptors;
   for (let i = 0; i < interceptors.length; i++) {
       newHook = interceptors[i].factory(key, newHook);
  }
// 存储到内部映射中
   this._map.set(key, newHook);
   return newHook;
}
  • get(key) 方法: 这个方法接受一个键(key)作为参数,并尝试从HookMap实例的内部映射(_map)中检索与这个键相关联的钩子。如果找到了这个钩子,它就会被返回;如果没有找到,方法会返回undefined。
  • for(key) 方法: 这个方法也接受一个键(key)作为参数,目的是要确保与这个键相关联的钩子存在。如果内部映射中已经有了对应的钩子,它就会直接返回这个现有的钩子。

如果找不到,方法将执行以下步骤:

1. 调用`_factory`函数创建一个新的钩子。这个工厂函数是在`HookMap`构造时传入的,用于按需创建钩子。
2. 遍历`_interceptors`数组,这个数组包含了所有的拦截器。拦截器是在钩子创建过程中可以介入的对象,每个拦截器都有可能修改新创建的钩子。
3. 对每个拦截器调用`factory`方法,传入当前的键和新创建的钩子。这允许拦截器根据键和钩子的当前状态来修改钩子。
4. 将新创建(并可能被拦截器修改过的)钩子与键关联并存储到内部映射中。
5. 返回新创建的钩子。

这样,for方法确保无论何时请求特定的键,都会得到一个钩子,如果这个钩子不存在,就现场创建一个。这允许HookMap按需动态地管理钩子,而无需预先定义所有可能的钩子。

intercept(interceptor)方法

1
2
3
4
5
6
7
8
9
10
js复制代码intercept(interceptor) {
   this._interceptors.push(
       Object.assign(
          {
               factory: defaultFactory
          },
           interceptor
      )
  );
}

该方法的作用是向 HookMap 实例的内部拦截器数组 _interceptors 添加一个新的拦截器对象

结尾

这个 HookMap 类为 tapable 提供了以下功能和特性:

  • 管理多个钩子实例:HookMap管理多个不同类型的钩子实例。它提供了一种集中管理和访问钩子的机制,可以方便地对多个钩子进行操作和触发。
  • 提供钩子的注册和访问接口:HookMap提供了钩子的注册和访问接口。通过注册接口,可以将钩子实例添加到HookMap中,并指定一个标识符来区分不同的钩子。通过访问接口,可以根据标识符获取对应的钩子实例,方便进行后续的操作。
  • 支持批量操作:HookMap支持批量操作多个钩子实例。可以一次性触发或执行多个钩子,避免了对每个钩子实例进行独立操作的麻烦。
  • 提供钩子的生命周期管理:HookMap可以跟踪和管理钩子的生命周期。它可以在钩子注册或注销时执行相应的生命周期钩子函数,用于进行一些额外的处理或清理操作。

综上所述,HookMap在tapable中的作用和意义是提供了一个集中管理和操作多个钩子实例的机制,方便进行批量操作和生命周期管理。它是实现插件系统和事件机制的重要组成部分。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

解决Android Studio不能创建aidl文件问题

发表于 2024-04-27

如图,在准备创建aidl文件时选项置灰

image.png

仔细看其实就是配置文件的问题,在build.gradle(:app)下的配置添加

1
2
3
gradle复制代码    buildFeatures {
aidl true
}

完整配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
gradle复制代码plugins {
id 'com.android.application'
}

android {
namespace 'com.example.aidl'
compileSdk 34

defaultConfig {
applicationId "com.example.aidl"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
aidl true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {

implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…456…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%