kotlin 知识点 七 泛型的高级特性
对泛型进行实化
泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的,因为Java 中完全没有这个概
念。而如果我们想要深刻地理解泛型实化,就要先解释一下Java 的泛型擦除机制才行。
在JDK 1.5之前,Java 是没有泛型功能的,那个时候诸如List之类的数据结构可以存储任意类型
的数据,取出数据的时候也需要手动向下转型才行,这不仅麻烦,而且很危险。比如说我们在
同一个List中存储了字符串和整型这两种数据,但是在取出数据的时候却无法区分具体的数据类
型,如果手动将它们强制转成同一种类型,那么就会抛出类型转换异常。
于是在JDK 1.5中,Java 终于引入了泛型功能。这不仅让诸如List之类的数据结构变得简单好
用,也让我们的代码变得更加安全。
但是实际上,Java 的泛型功能是通过类型擦除机制来实现的。什么意思呢?就是说泛型对于类
型的约束只在编译时期存在,运行的时候仍然会按照JDK 1.5之前的机制来运行,JVM是识别不
出来我们在代码中指定的泛型类型的。例如,假设我们创建了一个List集合,虽然
在编译时期只能向集合中添加字符串类型的元素,但是在运行时期JVM并不能知道它本来只打算
包含哪种类型的元素,只能识别出来它是个List。
所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,其中当然也包括了
Kotlin 。这种机制使得我们不可能使用a is T或者T::class.java这样的语法,因为T的实际
类型在运行的时候已经被擦除了。
然而不同的是,Kotlin 提供了一个内联函数的概念,我们在第6章的Kotlin 课堂中已经学过了这
个知识点。内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样的话也就不存
在什么泛型擦除的问题了,因为代码在编译之后会直接使用实际的类型来替代内联函数中的泛
型声明,其工作原理如图
可以看到,bar()是一个带有泛型类型的内联函数,foo()函数调用了bar()函数,在代码编
译之后,bar()函数中的代码将可以获得泛型的实际类型。
这就意味着,Kotlin 中是可以将内联函数中的泛型进行实化的。
那么具体该怎么写才能将泛型实化呢?首先,该函数必须是内联函数才行,也就是要用inline
关键字来修饰该函数。其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行
实化。示例代码如下:
inline fun <reified T> getGenericType() {
}
上述函数中的泛型T就是一个被实化的泛型,因为它满足了内联函数和reified关键字这两个前
提条件。那么借助泛型实化,到底可以实现什么样的效果呢?从函数名就可以看出来了,这里
我们准备实现一个获取泛型实际类型的功能,代码如下所示:
inline fun <reified T> getGenericType() = T::class.java
虽然只有一行代码,但是这里却实现了一个Java 中完全不可能实现的功能:
getGenericType()函数直接返回了当前指定泛型的实际类型。T.class这样的语法在Java
中是不合法的,而在Kotlin 中,借助泛型实化功能就可以使用T::class.java这样的语法了。
现在我们可以使用如下代码对getGenericType()函数进行测试:
fun main() { val result1 = getGenericType<String>() val result2 = getGenericType<Int>() println("result1 is $result1") println("result2 is $result2")
}
这里给getGenericType()函数指定了两种不同的泛型,由于getGenericType()函数会将
指定泛型的具体类型返回,因此这里我们将返回的结果进行打印。
现在运行一下main()函数,结果如图
泛型实化功能的运行结果
可以看到,如果将泛型指定成了String,那么就可以得到java.lang.String的类型;如果
将泛型指定了Int,就可以得到java.lang.Integer的类型。
关于泛型实化的基本用法就介绍到这里,接下来我们看一看,泛型实化在Andr oid 项目当中具体
可以有哪些应用。
泛型实化的应用
泛型实化功能允许我们在泛型函数当中获得泛型的实际类型,这也就使得类似于a is T、
T::class.java这样的语法成为了可能。而灵活运用这一特性将可以实现一些不可思议的语法
结构,下面我们赶快来看一下吧。
到目前为止,我们已经将Andr oid 的四大组件全部学完了,除了ContentP rovider 之外,你会
发现其余的3个组件有一个共同的特点,它们都是要结合Intent 一起使用的。比如说启动一个
Activity 就可以这么写:
val intent = Intent(context, TestActivity::class.java)
context.startActivity(intent)
有没有觉得TestActivity::class.java这样的语法很难受呢?当然,如果在没有更好选择
的情况下,这种写法也是可以忍受的,但是Kotlin 的泛型实化功能使得我们拥有了更好的选择。
新建一个reified.kt 文件,然后在里面编写如下代码:
inline fun <reified T> startActivity(context: Context) { val intent = Intent(context, T::class.java) context.startActivity(intent)
}
这里我们定义了一个startActivity()函数,该函数接收一个Context参数,并同时使用
inline和reified关键字让泛型T成为了一个被实化的泛型。
接下来就是神奇的地方了,Intent 接收的第二个参数本来应该是一个具体Activity 的Class类型,但由于现在T已经是一个
被实化的泛型了,因此这里我们可以直接传入T::class.java。最后调用Context的
startActivity()方法来完成Activity 的启动。
现在,如果我们想要启动TestA ctivity ,只需要这样写就可以了:
startActivity<TestActivity>(context)
Kotlin 将能够识别出指定泛型的实际类型,并启动相应的Activity 。怎么样,是不是觉得代码瞬
间精简了好多?这就是泛型实化所带来的神奇功能。
不过,现在的startActivity()函数其实还是有问题的,因为通常在启用Activity 的时候还可
能会使用Intent 附带一些参数,比如下面的写法:
val intent = Intent(context, TestActivity::class.java)
intent.putExtra("param1", "data")
intent.putExtra("param2", 123)
context.startActivity(intent)
而经过刚才的封装之后,我们就无法进行传参了。
这个问题也不难解决,只需要借助高阶函数就可以轻松搞定。回到
reified.kt 文件当中,这里添加一个新的startActivity()函数重载,如下所示:
inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {val intent = Intent(context, T::class.java) intent.block() context.startActivity(intent)
}
可以看到,这次的startActivity()函数中增加了一个函数类型参数,并且它的函数类型是
定义在Intent 类当中的。在创建完Intent 的实例之后,随即调用该函数类型参数,并把Intent 的
实例传入,这样调用startActivity()函数的时候就可以在Lambda 表达式中为Intent 传递
参数了,如下所示:
startActivity<TestActivity>(context) { putExtra("param1", "data") putExtra("param2", 123)
}
不得不说,这种启动Activity 的代码写起来实在是太舒服了,泛型实化和高阶函数使这种语法结
构成为了可能,感谢Kotlin 提供了如此多优秀的语言特性。
泛型的协变
泛型的协变和逆变功能不太常用,而且我个人认为有点不容易理解。但是Kotlin 的内置API中使
用了很多协变和逆变的特性,因此如果想要对这个语言有更加深刻的了解,这部分内容还是有
必要学习一下的。
我在学习协变和逆变的时候查阅了很多资料,这些资料大多十分晦涩难懂,因此也让我对这两
个知识点产生了一些畏惧。但是真正掌握之后,发现其实也并不是那么难,所以这里我会尽量
使用最简明的方式来讲解这两个知识点,希望你可以轻松掌握。
在开始学习协变和逆变之前,我们还得先了解一个约定。一个泛型类或者泛型接口中的方法,
它的参数列表是接收数据的地方,因此可以称它为in位置,而它的返回值是输出数据的地方,因
此可以称它为out 位置,如图
有了这个约定前提,我们就可以继续学习了。首先定义如下3个类:
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)
这里先定义了一个Person类,类中包含name和age这两个字段。然后又定义了Student和
Teacher这两个类,让它们成为Person类的子类。
现在我来问你一个问题:如果某个方法接收一个Person类型的参数,而我们传入一个Student
的实例,这样合不合法呢?很显然,因为Student是Person的子类,学生也是人呀,因此这是
一定合法的。
那么我再来升级一下这个问题:如果某个方法接收一个List类型的参数,而我们传
入一个List的实例,这样合不合法呢?看上去好像也挺正确的,但是Java 中是不
允许这么做的,因为List不能成为List的子类,否则将可能存在类型
转换的安全隐患。
为什么会存在类型转换的安全隐患呢?下面我们通过一个具体的例子进行说明。这里自定义一
个SimpleData类,代码如下所示:
class SimpleData<T> { private var data: T? = null fun set(t: T?) { data = t } fun get(): T? { return data }
}
SimpleData是一个泛型类,它的内部封装了一个泛型data字段,调用set()方法可以给data
字段赋值,调用get()方法可以获取data字段的值。
接着我们假设,如果编程语言允许向某个接收SimpleData参数的方法传入
SimpleData的实例,那么如下代码就会是合法的:
fun main() { val student = Student("Tom", 19) val data = SimpleData<Student>() data.set(student) handleSimpleData(data) // 实际上这行代码会报错,这里假设它能编译通过val studentData = data.get()
}
fun handleSimpleData(data: SimpleData<Person>) { val teacher = Teacher("Jack", 35) data.set(teacher)
}
发现这段代码有什么问题吗?在main()方法中,我们创建了一个Student的实例,并将它封装
到SimpleData当中,然后将SimpleData作为参数传递给
handleSimpleData()方法。但是handleSimpleData()方法接收的是一个
SimpleData参数(这里假设可以编译通过),那么在handleSimpleData()方法
中,我们就可以创建一个Teacher的实例,并用它来替换SimpleData参数中的原
有数据。这种操作肯定是合法的,因为Teacher也是Person的子类,所以可以很安全地将
Teacher的实例设置进去。
但是问题马上来了,回到main()方法当中,我们调用SimpleData的get()方法
来获取它内部封装的Student数据,可现在SimpleData中实际包含的却是一个
Teacher的实例,那么此时必然会产生类型转换异常。
所以,为了杜绝这种安全隐患,Java 是不允许使用这种方式来传递参数的。换句话说,即使
Student是Person的子类,SimpleData并不是SimpleData的子
类。
不过,回顾一下刚才的代码,你会发现问题发生的主要原因是我们在handleSimpleData()方
法中向SimpleData里设置了一个Teacher的实例。如果SimpleData在泛型T上是
只读的话,肯定就没有类型转换的安全隐患了,那么这个时候SimpleData可不可
以成为SimpleData的子类呢?
讲到这里,我们终于要引出泛型协变的定义了。假如定义了一个MyClass的泛型类,其中A
是B的子类型,同时MyClass< A > 又是 MyClass< B > 的子类型,那么我们就可以称 MyClass 在T
这个泛型上是协变的。
但是如何才能让 MyClass< A > 成为 MyClass< B > 的子类型呢?
刚才已经讲了,如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的。而要实现这一点,则
需要让MyClass类中的所有方法都不能接收T类型的参数。换句话说,T只能出现在out 位置
上,而不能出现在in位置上。
现在修改SimpleData类的代码,如下所示:
class SimpleData<out T>(val data: T?) { fun get(): T? { return data }
}
这里我们对SimpleData类进行了改造,在泛型T的声明前面加上了一个out关键字。这就意味
着现在T只能出现在out 位置上,而不能出现在in位置上,同时也意味着SimpleData在泛型T上
是协变的。
由于泛型T不能出现在in位置上,因此我们也就不能使用set()方法为data参数赋值了,所以这
里改成了使用构造函数的方式来赋值。你可能会说,构造函数中的泛型T不也是在in位置上的
吗?没错,但是由于这里我们使用了val关键字,所以构造函数中的泛型T仍然是只读的,因此
这样写是合法且安全的。另外,即使我们使用了var关键字,但只要给它加上private修饰
符,保证这个泛型T对于外部而言是不可修改的,那么就都是合法的写法。
经过了这样的修改之后,下面的代码就可以完美编译通过且没有任何安全隐患了:
fun main() { val student = Student("Tom", 19) val data = SimpleData<Student>(student) handleMyData(data) val studentData = data.get()
}
fun handleMyData(data: SimpleData<Person>) { val personData = data.get()
}
由于SimpleData类已经进行了协变声明,那么SimpleData自然就是
SimpleData的子类了,所以这里可以安全地向handleMyData()方法中传递参
数。
然后在handleMyData()方法中去获取SimpleData封装的数据,虽然这里泛型声明的是
Person类型,实际获得的会是一个Student的实例,但由于Person是Student的父类,向上
转型是完全安全的,所以这段代码没有任何问题。
学到这里,关于协变的内容你就掌握得差不多了,不过最后还有个例子需要回顾一下。前面我
们提到,如果某个方法接收一个List类型的参数,而传入的却是一个
List的实例, 在Java 中是不允许这么做的。注意这里我的用语,在Java 中是不允
许这么做的。
你没有猜错,在Kotlin 中这么做是合法的,因为Kotlin 已经默认给许多内置的API加上了协变声
明,其中就包括了各种集合的类与接口。还记得我们在第2章中学过的吗?Kotlin 中的List本身
就是只读的,如果你想要给List添加数据,需要使用MutableList 才行。既然List是只读的,也
就意味着它天然就是可以协变的,我们来看一下List简化版的源码:
public interface List<out E> : Collection<E> { override val size: Int override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> public operator fun get(index: Int): E
}
List在泛型E的前面加上了out关键字,说明List在泛型E上是协变的。不过这里还有一点需要说
明,原则上在声明了协变之后,泛型E就只能出现在out 位置上,可是你会发现,在
contains()方法中,泛型E仍然出现在了in位置上。
这么写本身是不合法的,因为在in位置上出现了泛型E就意味着会有类型转换的安全隐患。但是
contains()方法的目的非常明确,它只是为了判断当前集合中是否包含参数中传入的这个元
素,而并不会修改当前集合中的内容,因此这种操作实质上又是安全的。那么为了让编译器能
够理解我们的这种操作是安全的,这里在泛型E的前面又加上了一个@UnsafeVariance注解,
这样编译器就会允许泛型E出现在in位置上了。但是如果你滥用这个功能,导致运行时出现了类
型转换异常,Kotlin 对此是不负责的。
泛型的逆变
理解了协变之后再来学习逆变,我觉得会相对比较容易一些,因为它们之间是有所关联的。
不过仅从定义上来看,逆变与协变却完全相反。那么这里先引出定义吧,假如定义了一个
MyClass < T >的泛型类,其中A是B的子类型,同时 MyClass< B > 又是 MyClass < A >的子类型,
那么我们就可以称MyClass在T这个泛型上是逆变的。协变和逆变的区别如图
协变与逆变的区别
从直观的角度上来思考,逆变的规则好像挺奇怪的,原本A是B的子类型,怎么MyClass能
反过来成为MyClass的子类型了呢?别担心,下面我们通过一个具体的例子来学习一下,
你就明白了。
这里先定义一个Transformer接口,用于执行一些转换操作,代码如下所示:
interface Transformer<T> { fun transform(t: T): String
}
可以看到,Transformer接口中声明了一个transform()方法,它接收一个T类型的参数,并
且返回一个String类型的数据,这意味着参数T在经过transform()方法的转换之后将会变成
一个字符串。至于具体的转换逻辑是什么样的,则由子类去实现,Transformer接口对此并不
关心。
那么现在我们就尝试对Transformer接口进行实现,代码如下所示:
fun main() { val trans = object : Transformer<Person> { override fun transform(t: Person): String { return "${t.name} ${t.age}" } } handleTransformer(trans) // 这行代码会报错
}
fun handleTransformer(trans: Transformer<Student>) { val student = Student("Tom", 19) val result = trans.transform(student)
}
首先我们在main()方法中编写了一个Transformer的匿名类实现,并通过
transform()方法将传入的Person对象转换成了一个“姓名+ 年龄”拼接的字符串。而
handleTransformer()方法接收的是一个Transformer类型的参数,这里在
handleTransformer()方法中创建了一个Student对象,并调用参数的transform()方法
将Student对象转换成一个字符串。
这段代码从安全的角度来分析是没有任何问题的,因为Student是Person的子类,使用
Transformer的匿名类实现将Student对象转换成一个字符串也是绝对安全的,并
不存在类型转换的安全隐患。但是实际上,在调用handleTransformer()方法的时候却会提
示语法错误,原因也很简单,Transformer并不是Transformer的子
类型。
那么这个时候逆变就可以派上用场了,它就是专门用于处理这种情况的。修改Transformer接
口中的代码,如下所示:
interface Transformer<in T> { fun transform(t: T): String
}
这里我们在泛型T的声明前面加上了一个in关键字。这就意味着现在T只能出现在in位置上,而
不能出现在out 位置上,同时也意味着Transformer在泛型T上是逆变的。
没错,只要做了这样一点修改,刚才的代码就可以编译通过且正常运行了,因为此时
Transformer已经成为了Transformer的子类型。
逆变的用法大概就是这样了,如果你还想再深入思考一下的话,可以想一想为什么逆变的时候
泛型T不能出现在out 位置上?为了解释这个问题,我们先假设逆变是允许让泛型T出现在out 位
置上的,然后看一看可能会产生什么样的安全隐患。
修改Transformer中的代码,如下所示:
interface Transformer<in T> { fun transform(name: String, age: Int): @UnsafeVariance T
}
可以看到,我们将transform()方法改成了接收name和age这两个参数,并把返回值类型改成
了泛型T。由于逆变是不允许泛型T出现在out 位置上的,这里为了能让编译器正常编译通过,所
以加上了@UnsafeVariance注解,这和List源码中使用的技巧是一样的。
那么,这个时候可能会产生什么样的安全隐患呢?我们来看一下如下代码就知道了:
fun main() { val trans = object : Transformer<Person> { override fun transform(name: String, age: Int): Person {return Teacher(name, age) } } handleTransformer(trans)
}
fun handleTransformer(trans: Transformer<Student>) { val result = trans.transform("Tom", 19)
}
上述代码就是一个典型的违反逆变规则而造成类型转换异常的例子。在
Transformer的匿名类实现中,我们使用transform()方法中传入的name和age
参数构建了一个Teacher对象,并把这个对象直接返回。由于transform()方法的返回值要求
是一个Person对象,而Teacher是Person的子类,因此这种写法肯定是合法的。
但在handleTransformer()方法当中,我们调用了Transformer的
transform()方法,并传入了name和age这两个参数,期望得到的是一个Student对象的返
回,然而实际上transform()方法返回的却是一个Teacher对象,因此这里必然会造成类型转
换异常。
由于这段代码是可以编译通过的,那么我们可以运行一下,打印出的异常信息如图
逆变使用不当造成的类型转换异常
可以看到,提示我们Teacher类型是无法转换成Student类型的。
也就是说,Kotlin 在提供协变和逆变功能时,就已经把各种潜在的类型转换安全隐患全部考虑进
去了。只要我们严格按照其语法规则,让泛型在协变时只出现在out 位置上,逆变时只出现在in
位置上,就不会存在类型转换异常的情况。虽然@UnsafeVariance注解可以打破这一语法规
则,但同时也会带来额外的风险,所以你在使用@UnsafeVariance注解时,必须很清楚自己
在干什么才行。
最后我们再来介绍一下逆变功能在Kotlin 内置API中的应用,比较典型的例子就是Comparable
的使用。Comparable是一个用于比较两个对象大小的接口,其源码定义如下:
interface Comparable<in T> { operator fun compareTo(other: T): Int
}
可以看到,Comparable在T这个泛型上就是逆变的,compareTo()方法则用于实现具体的比
较逻辑。那么这里为什么要让Comparable接口是逆变的呢?想象如下场景,如果我们使用
Comparable实现了让两个Person对象比较大小的逻辑,那么用这段逻辑去比较两
个Student对象的大小也一定是成立的,因此让Comparable成为
Comparable的子类合情合理,这也是逆变非常典型的应用。
相关文章:
kotlin 知识点 七 泛型的高级特性
对泛型进行实化 泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的,因为Java 中完全没有这个概 念。而如果我们想要深刻地理解泛型实化,就要先解释一下Java 的泛型擦除机制才行。 在JDK 1.5之前,Java 是没有泛型功能的,…...
正则表达式–断言
原文地址:正则表达式–断言 – 无敌牛 欢迎参观我的个人博客:正则表达式特殊字符 – 无敌牛 断言assertions 1、(?...):正向预查(positive lookahead),表示某个字符串后面应该跟着什么。但这个字符串本身…...
OceanBase数据库实战:Windows Docker部署与DBeaver无缝对接
一、前言 OceanBase 是一款高性能、高可扩展的分布式数据库,适用于大规模数据处理和企业级应用。 随着大数据和云计算的普及,OceanBase 在企业数字化转型中扮演着重要角色。学习 OceanBase 可以帮助开发者掌握先进的分布式数据库技术,提升数…...
C++:开胃菜练习项目---定长内存池的实现以及测试
项目介绍 简介 作为学习tcmalloc高并发内存池项目前的一个铺垫。 作为程序员(C/C)我们知道申请内存使用的是malloc,malloc其实就是一个通用的大众货,什么场景下都可以用,但是什么场景下都可以用就意味着什么场景下都不会有很高的性能…...
【LLM】本地部署LLM大语言模型+可视化交互聊天,附常见本地部署硬件要求(以Ollama+OpenWebUI部署DeepSeekR1为例)
【LLM】本地部署LLM大语言模型可视化交互聊天,附常见本地部署硬件要求(以OllamaOpenWebUI部署DeepSeekR1为例) 文章目录 1、本地部署LLM(以Ollama为例)2、本地LLM交互界面(以OpenWebUI为例)3、本…...
JVM相关面试题
1. 类加载与双亲委派机制 聊一下你对类加载器的理解。 类加载器是JVM用来加载类文件到内存的组件。它负责将字节码文件解析为java.lang.Class实例,并存储到运行时数据区的方法区中。类加载器分为Bootstrap ClassLoader、Extension ClassLoader和Application ClassLo…...
WordPress Course Booking System SQL注入漏洞复现 (CVE-2025-22785)(附脚本)
免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…...
二:前端发送POST请求,后端获取数据
接着一:可以通过端口访问公网IP之后 二需要实现:点击飞书多维表格中的按钮,向服务器发送HTTP请求,并执行脚本程序 向服务器发送HTTP请求: 发送请求需要明确一下几个点 请求方法: 由于是向服务器端发送值…...
Go语言中的信号量:原理与实践指南
Go语言中的信号量:原理与实践指南 引言 在并发编程中,控制对共享资源的访问是一个经典问题。Go语言提供了丰富的并发原语(如sync.Mutex),但当我们需要灵活限制并发数量时,信号量(Semaphore&am…...
cpp中的继承
一、继承概念 在cpp中,封装、继承、多态是面向对象的三大特性。这里的继承就是允许已经存在的类(也就是基类)的基础上创建新类(派生类或者子类),从而实现代码的复用。 如上图所示,Person是基类&…...
3DGS(三维高斯散射)与SLAM技术结合的应用
3DGS(三维高斯散射)与SLAM(即时定位与地图构建)技术的结合,为动态环境感知、高效场景建模与实时渲染提供了新的可能性。以下从技术融合原理、应用场景、优势挑战及典型案例展开分析: 一、核心融合原理 1. …...
DeepSeek赋能大模型内容安全,网易易盾AIGC内容风控解决方案三大升级
在近两年由AI引发的生产力革命的背后,一场关乎数字世界秩序的攻防战正在上演:AI生成的深度伪造视频导致企业品牌声誉损失日均超千万,批量生成的侵权内容使版权纠纷量与日俱增,黑灰产利用AI技术持续发起欺诈攻击。 与此同时&#…...
mybatis 细节(${ ..}和#{..},resultType 和 resultMap的区别,别名的使用,Mapper 代理模式)
${..}和#{..} 占位符 #{..} #{}实现的是向prepareStatement中的预处理语句中设置参数值,sql语句中#{}表示一个占位符即?。 <!-- 根据id查询用户信息 --> <select id"findUserById" parameterType"int" resultType"user"&g…...
电子科技大学考研复习经验分享
电子科技大学考研复习经验分享 本人情况:本科就读于电科软院,24年2月开始了解考研,24年3月开始数学,9月决定考本院(开始全天候图书馆学习)并开始专业课学习,11月底开始政治学习,最后…...
【python】提取word\pdf格式内容到txt文件
一、使用pdfminer提取 import os import re from pdfminer.high_level import extract_text import docx2txt import jiebadef read_pdf(file_path):"""读取 PDF 文件内容:param file_path: PDF 文件路径:return: 文件内容文本"""try:text ext…...
Selenium 与 Coze 集成
涵盖两者的基本概念、集成步骤、代码示例以及相关注意事项。 基本概念 Selenium:是一个用于自动化浏览器操作的工具集,支持多种浏览器(如 Chrome、Firefox 等),能够模拟用户在浏览器中的各种操作,如点击、输入文本、选择下拉框等,常用于 Web 应用的自动化测试。Coze:它…...
SQL注入(order by,limit),seacms的报错注入以及系统库的绕过
1:如果information_schema被过滤了,该怎么绕过 1.1:介绍一下information_schema这个库 information_schema 是一个非常重要的系统数据库,它在SQL标准中定义,并且被许多关系型数据库管理系统(RDBMS&#x…...
数据保护API(DPAPI)深度剖析与安全实践
Windows DPAPI 安全机制解析 在当今数据泄露与网络攻击日益频繁的背景下,Windows 提供的 DPAPI(Data Protection API)成为开发者保护本地敏感数据的重要工具。本文将从 双层密钥体系、加密流程、跨上下文加密、已知攻击向量与防御措施、企业…...
Sqlserver安全篇之_隐藏实例功能和禁用SQL Server Browser服务
总结: 1、隐藏实例功能和禁用SQL Server Browser服务的功能一样,对应非默认实例(且这个默认实例是1433端口)的情况下,都是需要在连接字符串中提供端口号才能连接到实例 2、隐藏实例功能后,就算开启了SQL Server Browser服务&#…...
muduo网络库2
Muduo网络库:底层实质上为Linux的epoll pthread线程池,且依赖boost库。 muduo的网络设计核心为一个线程一个事件循环,有一个main Reactor负载accept连接,然后把连接分发到某个sub Reactor(采用轮询的方式来选择sub Reactor)&…...
【ISP】畸变校正 LDC
ISP(Image Signal Processor,图像信号处理器)中的 LDC(Lens Distortion Correction,镜头畸变校正)是一种用于校正镜头畸变的图像处理技术。镜头畸变是由于镜头的光学特性导致的图像失真现象,主要…...
deepseek 学习资料整理
deepseek 学习资料整理 deepseek_清华大学指导手册_pdf_1-5 无套路,无需关注,无需登录,无需app,直接下载: 下载地址 文件列表: 001_清华大学_DeepSeek从入门到精通.pdf 002_清华大学_DeepSeek如何赋能职…...
【deepseek】本地部署+webui访问
背景 最近deepseek很火,但是官网的老是被限流使用,还有就是自己也想着玩一玩,于是准备在自己电脑跑一个 直接附上结果地址mydeepseek 准备工作 windows和linux都可 我这里选择linux,ubuntu系统 安装ollama 看下图࿰…...
LeetCodeHot100_0x02
LeetCodeHot100_0x02 11. 滑动窗口最大值(不熟) 求解思路: 暴力法的时间复杂度是O(NK),在K常数较大时复杂度就高了。所以我们要想办法将K优化掉,即本题的难点在于如何在O(1)的时间复杂度求出当前窗口中的最大值。这个…...
STM32MP157A-FSMP1A单片机移植Linux系统SPI总线驱动
SPI总线驱动整体上与I2C总线驱动类型,差别主要在设备树和数据传输上,由于SPI是由4根线实现主从机的通信,在设备树上配置时需要对SPI进行设置。 原理图可知,数码管使用的SPI4对应了单片机上的PE11-->SPI4-NSS,PE12-->SPI4-S…...
H7 based Phalanx G1 ETH Data Switch Hub UART Interface 介绍
外接接口配置 H7 based Phalanx G1 ETH Data Switch hub UART interface 1.对外接接口进行详细介绍 以下是针对 H7 based Phalanx G1 设备的外接接口配置的详细解析,重点说明其 ETH Data Switch Hub 和 UART Interface 的技术特性与应用场景: 一、核…...
Vue04
自定义指令 directives是Vue的一个配置项 这里写自定义指令 自定义指令被调用的时机 指令与元素成功绑定时 指令所在的模板被重新解析时 函数式 <span v-big"n"></span> directives:{ big(element,binding){ element.innerText bingin…...
OpenCV(9):视频处理
1 介绍 视频是由一系列连续的图像帧组成的,每一帧都是一幅静态图像。视频处理的核心就是对这些图像帧进行处理。常见的视频处理任务包括视频读取、视频播放、视频保存、视频帧处理等。 视频分析: 通过视频处理技术,可以分析视频中的运动、目标、事件等。…...
短剧源码部署搭建小程序搭建IAA+IAP混合解锁模式
在当今数字化内容消费迅速增长的时代,短剧作为一种新兴的内容形式,凭借其短小精悍、节奏紧凑的特点,迅速吸引了大量用户。作为一名软件体验测试人员,我有幸体验了一款集创新与实用为一体的短剧小程序。这款小程序不仅在前端用户体…...
基于 CFD 预测的机器学习第 2 部分:在 Benchmark 应用程序上使用 Stochos 预测流场
了解机器学习和 Stochos 如何彻底改变制造业的 CFD 预测。 挑战 预测复杂流体动力学场景中的流场一直是工程师和科学家面临的重大挑战。传统的计算流体动力学 (CFD) 方法需要大量的计算资源和时间,因此难以处理实时预测和大规模模拟。 此外…...
NLP的预处理数据
处理文本数据的主要工具是Tokenizer。Tokenizer根据一组规则将文本拆分为tokens。然后将这些tokens转换为数字,然后转换为张量,成为模型的输入。模型所需的任何附加输入都由Tokenizer添加。 如果您计划使用预训练模型,重要的是使用与之关联的…...
数据结构——单链表
前言 1. 什么是链表 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。与顺序表不同,链表的存储数据在内存是随机分布的。 2. 链表的分类 链表的种类多种多样,其中最常见的有八种…...
SurfaceFlinger代码笔记
drawLayers是做client合成,合成完以后的buffer会放在RenderSurface里 FrameBufferSurface里的buffer是通过setClientTarget给到HWC的(HWC应该给client合成的buffer留了一个slot) Output.cpp这个文件非常关键,代表着具体一个Display的操作 d…...
Linux-Ansible模块进阶
文章目录 Copy和FetchFile模块 🏡作者主页:点击! 🤖Linux专栏:点击! ⏰️创作时间:2025年02月22日18点49分 Copy和Fetch copy和fetch模块实践 copy模块需要注意的点:在收集日志之前…...
【机器学习】强化学习(2)——捋清深度强化学习的思路
在之前学习的过程中我了解到深度学习中很重要的一个概念是反向传播,最近看论文发现深度强化学习(DRL)有各种各样的方法,但是却很难区分他们的损失函数的计算以及反向传播的过程有何不同。在有监督的学习中,损失可以理解…...
touchgfx的工作机制
touchgfx的工作机制 一.MVP软件架构 MVP的全称为Model-View-Presenter Model: 就是数据部分,在整个touchgfx应用中,只有一个Model类实例对象,它为所有的Screen屏幕界面服务,可以理解成是一个全局变量区,同时它还负责和后端系统通信 View: 就是UI界面部分,对应于View类,在整…...
Fisher信息矩阵(Fisher Information Matrix, FIM)与自然梯度下降:机器学习中的优化利器
Fisher信息矩阵与自然梯度下降:机器学习中的优化利器 在机器学习尤其是深度学习中,优化模型参数是一个核心任务。我们通常依赖梯度下降(Gradient Descent)来调整参数,但普通的梯度下降有时会显得“笨拙”,…...
2025数学建模竞赛汇总,错过再等一年
01、2025第十届数维杯大学生数学建模挑战赛(小国赛) 竞赛介绍:数学建模行业内仅次于国赛和美赛的的第三赛事,被多所高校认定为国家级二类竞赛。赛题类型是国内唯一和高教社杯国赛题型风格完全一致的全国性数学建模竞赛࿰…...
设计模式教程:观察者模式(Observer Pattern)
一、模式概述 观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系。一个对象(称为主题)状态发生变化时,所有依赖于它的对象(称为观察者)都会自动得到…...
代码随想录算法训练营第九天| 151.翻转字符串里的单词、右旋转字符串 、28. 实现 strStr()、459.重复的子字符串、字符串总结
151.翻转字符串里的单词 题目链接:151.翻转字符串里的单词 文档讲解:代码随想录翻转字符串里的单词 视频讲解:LeetCode:翻转字符串里的单词 状态:参考自己写出来的 思路: 反转:思路很清晰&#…...
bpmn.js + Node.js_构建高效的后端工作流处理系统
1. 引言 1.1 研究背景与意义 随着企业业务的复杂化,传统的流程管理工具已难以满足需求。BPMN(Business Process Model and Notation)作为一种标准化的流程建模语言,结合 bpmn.js 和 Node.js 可以实现高效的工作流管理系统,提升企业的运营效率。 1.3 BPMN 和 bpmn.js 简…...
DeepSeek系统架构的逐层分类拆解分析,从底层基础设施到用户端分发全链路
一、底层基础设施层 1. 硬件服务器集群 算力单元: GPU集群:基于NVIDIA H800/H100 GPU构建,单集群规模超10,000卡,采用NVLink全互联架构实现低延迟通信。国产化支持:适配海光DCU、寒武纪MLU等国产芯片,通过…...
嵌入式硬件基础知识
1.电阻(主要是贴片电阻) 01 基础课程-电阻 1.电阻封装 2.相关参数 1.功率额定值: 电阻能够长期承受的最大功率,功率过大可能导致电阻过热或损坏。封装尺寸越大,散热能力越强,功率额定值通常越高。 2.容差: 电阻…...
springboot+dubbo+zookeeper的注册服务和调用实践
目录 zookeeper为什么可作为注册中心zookeeper注册中心优缺点启动zookeeper编写springboot项目提供dubbo服务1. 服务接口2. Springboot引入dubbo实现服务接口2.1 工程目录和依赖2.2 启动程序和application.properties2.3 DubboService 实现服务接口2.4 测试api,用于…...
ARM Cortex-M处理器中的MSP和PSP
在ARM Cortex-M系列处理器中,MSP(主堆栈指针)和PSP(进程堆栈指针)是两种不同的堆栈指针,主要用于实现堆栈隔离和提升系统可靠性。以下是它们的核心区别和应用场景: 1. 基本定义 MSP(…...
计算机网络:应用层 —— 电子邮件
文章目录 电子邮件的起源与发展电子邮件的组成电子邮件协议邮件发送和接收过程邮件发送协议SMTP协议多用途因特网邮件扩展MIME 电子邮件的信息格式 邮件读取协议邮局协议POP因特网邮件访问协议IMAP 基于万维网的电子邮件 电子邮件(E-mail)是因特网上最早…...
Vue3 + Spring WebMVC 验证码案例中的跨域问题与解决方法
最近在基于vue3 SpringWebMVC前后端分离的开发环境中实现一个验证码的案例,在开发过程中遇到了一些复杂的跨域问题,现已解决,故将解决方法分享,希望能帮到有需要的人。 出现的问题: 对于验证码的实现,我选…...
【Python爬虫(60)】解锁社交媒体数据宝藏:Python爬虫实战攻略
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
Comfy UI 快捷键
Comfy UI 页面的快捷键操作(记录下,以防忘记): 捷径命令Ctrl Enter将当前图表排队等待生成Ctrl Shift Enter将当前图表排成第一个生成图表Ctrl Z/Ctrl Y撤消/重做Ctrl S保存工作流程Ctrl O加载工作流Ctrl A选择所有节点A…...
【C++】Arrays
《C程序设计基础教程》——刘厚泉,李政伟,二零一三年九月版,学习笔记 文章目录 1、一维数组的定义与初始化1.1、一维数组的定义1.2、一维数组的初始化 2、一维数组的使用3、一维数组与函数4、二维数组4.1、二维数组的定义4.2、二维数组的初始…...