Kotlin 笔记 -- Kotlin 语言特性的理解(一)

函数引用、匿名函数、lambda表达式、inline函数的理解

在这里插入图片描述

  • 双冒号对函数进行引用的本质是生成一个函数对象
  • 只有函数对象才拥有invoke()方法,而函数是没有这个方法的
  • kotlin中函数有自己的类型,但是函数本身不是对象,因此要引用函数类型就必须通过双冒号的方式将函数转换成一个对象,这样之后才能拿这个对象进行赋值

在这里插入图片描述

  • 匿名函数是一个对象,它不是函数
  • 匿名函数跟双冒号引用函数是一种东西
  • 而 lambda 表达式是一种特殊的匿名函数对象,它可以省略参数和调用者括号等,更加方便而已
  • 因为匿名函数是一个对象,所以它(包括lambda表达式)才可以被当成一个参数传递
  • 双冒号函数引用、匿名函数对象、lambda这三者本质都是函数类型的对象
  • Java8 中虽然也支持 lambda 方式的写法(SAM 转换),但是 Java8 中的 lambda 和 kotlin 中的 lambda 有本质的区别,一个是编译器的简化写法,一个是函数对象的传递。Java 中即便能写成 lambda 的方式,它也是生成的一个接口类的匿名对象。

在这里插入图片描述

  • kotlin 中的 const 用来声明编译时常量,作用同 java 里的 static final ,会用字面量直接替换调用处的变量。但它只能写在顶级作用域。

在这里插入图片描述
在这里插入图片描述

  • kotlin中函数对象作为参数传递以后,会创建一个临时对象给真正的函数调用
  • 这种函数如果是在for循环这样的高频调用的场景里,就会因为创建大量的临时对象而导致内存抖动和频繁的GC, 甚至引发OOM

在这里插入图片描述

  • 如果给函数加上inline关键字,它会将调用的函数插入到调用处进行平铺展开,这样就可以避免生成临时函数对象带来的影响。所以inline关键字的优化,要是针对高阶函数的。

inline、nolinline、crossinline

  • inline通过内联(即函数内容直插到调用处)的方式来编译函数
  • noinline局部关掉这个优化,来摆脱「不能把函数类型的参数当对象使用」的限制
  • crossinline让内联函数里的函数类型的参数可以被间接调用,代价是不能在 Lambda 表达式里使用 return

noinlinecrossinline 主要是用来解决加上 inline 之后,可能导致的一些副作用或者附带伤害,进行补救的措施,至于什么时候需要使用它们,不需要记住规则,因为 Android Studio 会在需要的时候提示它。

什么是 SAM 转换

SAM 转换(Single Abstract Method),是针对只有一个方法的接口类的简化写法,例如:

// Single Abstract Method
public interface Listener {
    void onChanged();
}

public class MyView {
    private Listener mListener;
    
    public void setListener(Listener listener) {
        mListener = listener;
    }
}
MyView view = new MyView();
view.setListener(new Listener() {
	@Override
    public void onChanged() {

    }
});

如果你写成这种写法,编译器就会提示你可以将其转换成 lambda 表达式(jdk 1.8):

在这里插入图片描述

于是代码就可以简化成下面这样:

MyView view = new MyView();
view.setListener(() -> {
	// todo
});

当然如果是在 kotlin 中调用 java 的这种代码,还可以将小括号去掉,直接调用方法后面跟上 {} 变成更彻底的 lambda 写法。

MyView().setListener { 
 	// todo
}

泛型中的 out 与 in

在Kotlin中out代表协变in代表逆变,为了加深理解我们可以将Kotlin的协变看成Java的上界通配符,将逆变看成Java的下界通配符

// Kotlin 使用处协变
fun sumOfList(list: List<out Number>)
// Java 上界通配符
void sumOfList(List<? extends Number> list)
// Kotlin 使用处逆变
fun addNumbers(list: List<in Int>)
// Java 下界通配符
void addNumbers(List<? super Integer> list)

我们知道 Java 的上界通配符和下界通配符主要用于函数的入参和出参,它们俩一个只读,一个只写,而 kotlin 中将这两个分别命名为outin在含义上更加明确了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总的来说,Kotlin 泛型更加简洁安全,但是和 Java 一样都是有类型擦除的,都属于编译时泛型。

另外,kotlin 可以直接使用 outin 在类上指定泛型的读写模式,但是 Java 不可以:

// 这个类,就是只能获取,不能修改了
// 声明的时候加入 一劳永逸了 <out T>
class Worker<out T> {

	// 能获取
	fun getData(): T? = null
	
	// 不能修改
	/* 
	 * fun setData(data: T) { }
	 * fun addData(data: T) { }
	 */
}
// 这个类,就是只能修改,不能获取
// 声明的时候加入 一劳永逸了 <in T>
class Student<in T> {
	/* fun a(list: Mutablelist<in T>) **/
	
	fun setData(data: T) {}
	
	fun addData(data: T) {}
	
	// 不能获取
	// fun getData() : T
}
// Java 不允许你在声明方向的时候,控制读写模式
public class Student /*<? super T>*/ { 

}

在这里插入图片描述

  1. 类上的泛型 T 前面的 outin 关键字作用于整个类范围所有使用该泛型的地方。
  2. Kotlin 为什么这样设计:它表示所有使用 T 的场景都是只用来输出,或者只用来输入的,那么为了避免我在每个使用的位置都给变量或者参数写上out这么麻烦,就干脆直接声明在了类上面。
  3. 什么时候该用 outin :这是一个设计问题,类的设计者需要考虑这个类的职责,是否是只用于生产或者只用于消费的。

在类上使用 outin 时赋值的区别:

  • 子类泛型对象可以赋值给父类泛型对象,用 out
  • 父类泛型对象可以赋值给子类泛型对象,用 in

在这里插入图片描述

// 子类泛型对象可以赋值给父类泛型对象,用 out
val production1: Producer<Food> = FoodStore() 
val production2: Producer<Food> = FastFoodStore() 
val production3: Producer<Food>= BurgerStore() 
// 父类泛型对象可以赋值给子类泛型对象,用 in
val consumer1: Consumer<Burger> = Everybody() 
val consumer2: Consumer<Burger> = ModernPeople() 
val consumer3: Consumer<Burger> = American() 

这赋值这一点上, 使用 outin 与 Java 的上界通配符和下界通配符是一样的行为。

Kotlin 泛型中的 * 相当于Java 泛型中的 ?

在这里插入图片描述

Java 和 Kotlin 中类的泛型参数有多继承的区别:

在这里插入图片描述

Kotlin 泛型方法示例:

fun <T> instanceRetrofit(apiInterface: Class<T>) : T {
    // OKHttpClient 用于请求服务器
    val mOkHttpClient = OkHttpClient().newBuilder() 
        .readTimeout(10000, TimeUnit.SECONDS) // 添加读取超时时间 
        .connectTimeout(10000, TimeUnit.SECONDS) // 添加连接超时时间 
        .writeTimeout(10000, TimeUnit.SECONDS) // 添加写出超时时间
        .build()

    val retrofit: Retrofit = Retrofit.Builder()
        .client(mOkHttpClient) // 请求方,处理响应方
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 使用 RxJava 处理响应
        .addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析 JavaBean
        .build()

    return retrofit.create(apiInterface)
}

简单总结一下:

kotlin的 out in 和 Java 的<? extends T><? super T> 虽然含义是一样的,但是写法上有点不同:

  • Java 的上界通配符和下界通配符只能用作方法的参数,用于入参和出参的作用,不能直接写到类上面。
  • kotlin 中的 out in 可以直接写到类上面,直接表明整个类中使用到泛型 T 的地方都具备这个含义。
  • 当在类上面声明泛型 Tout 时,表示 T 只能从当前类中输出,而不能输入。
  • 当在类上面声明泛型 Tin 时,表示 T 只能从当前类中输入,而不能输出。

Kotlin 标准库中的常用扩展函数 apply、also、run、let

使用时可以通过简单的规则作出一些判断:

  • 需要返回自身 -> 从 applyalso 中选
    • 作用域中使用 this 作为参数 ----> 选择 apply
    • 作用域中使用 it 作为参数 ----> 选择 also
  • 不需要返回自身 -> 从 runlet 中选择
    • 作用域中使用 this 作为参数 ----> 选择 run
    • 作用域中使用 it 作为参数 ----> 选择 let
  • apply 适合对一个对象做附加操作的时候
  • let 适合配合判空的时候 (最好是成员变量,而不是局部变量,局部变量更适合用 if )
  • with 适合对同一个对象进行多次操作的时候

apply VS also

applyalso 的返回值都是当前调用者的对象,也就是 T 类型的当前实例:

public inline fun <T> T.apply(block: T.() -> Unit): T { 
	block()
	return this 
}
public inline fun <T> T.also(block: (T) -> Unit): T { 
	block(this)
	return this 
}

这俩同是当前调用者 T 类型的扩展函数,但是 apply 中的 block 同时也是当前调用者 T 类型的扩展函数,所以其 lambda 中可以使用 this 访问当前对象,而 also 中的 block 只是一个普通的函数,不过这个普通函数的参数传入的是当前对象,所以其 lambda 中只能用 it 访问当前对象。

在这里插入图片描述

apply 常用于创建对象实例后,马上对其进行一些操作调用:

ArrayList<String>().apply { 
	add( "testApply")
	add("testApply") 
	add( "testApply") 
	println("$this") 
}.also { 
	println(it)
} 

let VS run

letrun 的返回值都是 block 的返回值,即 lambda 表达式的返回值:

public inline fun <T, R> T.let(block: (T) -> R): R { 
	return block(this)
}
public inline fun <T, R> T.run(block: T.() -> R): R { 
	return block()
} 

这俩同是当前调用者 T 类型的扩展函数,但是 run 中的 block 同时也是当前调用者 T 类型的扩展函数,所以其 lambda 中可以使用 this 访问当前对象,而 let 中的 block 只是一个普通的函数,不过这个普通函数的参数传入的是当前对象,所以其 lambda 中只能用 it 访问当前的对象。

在这里插入图片描述

let 比较常用的一个操作是判空操作

// 避免为 nu1ll 的操作
str?.let {
	println(it.length) 
}

object 单例

我们知道 object 就是 kotlin 中天生的单例模式,我们看一下它翻译成 Java 代码是什么样的:

object Singleton {
    var x: Int = 2 
    fun y() { }
}

上面代码编译成Java字节码后反编译对应如下代码:

在这里插入图片描述

其实它就是恶汉模式的单例

如果是一个普通类想生成单例,可以使用伴生对象 companion object 来生成:

class Singleton {
    companion object {
        var x: Int = 2
        fun y() { }
    }
}

上面代码翻译成 Java 后长下面这样:

在这里插入图片描述

我们看到它还是一个恶汉模式的单例,只不过这个恶汉的对象实例是一个名为 Companion静态内部类的实例对象。另外,只有 companion object 中的成员属性是放到外部类中的,而 companion object 中的成员方法是放在静态内部类中的。

如果要实现 Java 中静态内部类版本的单例模式

public class Singleton {

    private Singleton() {}
     
    private static class SingletonHolder {
        /** 静态初始化器,由 JVM 类加载过程来保证线程安全 */
        private static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

可以像下面这样写:

class Singleton private constructor() {

    private object Holder {
        val INSTANCE = Singleton()
    }
    
    companion object {
        val instance = Holder.INSTANCE
    }
}

Kotlin 中很没用的两个东西 Nothing 与 Unit

之所以说这两个东西没啥用,是因为这两个相当于是用来进行标记、提示之类的作用;只能说它们的实际用处不是那么大,但是用来标记和提醒比较方便。

比如在 Java 中,各种源码中可以看到类似下面的写法:

/**
* 当遇到姓名为空的时候,请调用这个函数来抛异常
* @return throw NullPointerException
*/
public String throwOnNameNull() {
   throw new NullPointerException("姓名不能为空!");
}

对应到 Kotlin 的等价写法:

/**
 * 当遇到姓名为空的时候,请调用这个函数来抛异常
 */
fun throwOnNameNull() : String {
    throw NullPointerException("姓名不能为空!")
}

这个函数的主要作用是用来抛出一个异常,但是如果这么写会让人很困惑,明明只是抛出异常,返回值却是一个String,所以这个时候可以用 Unit 或者 Nothing 代替它:

/**
 * 当遇到姓名为空的时候,请调用这个函数来抛异常
 */
fun throwOnNameNull() : Nothing {
    throw NullPointerException("姓名不能为空!")
}

这样开发者在看到这个函数时,就会知道,它什么也不会返回。就是一个提醒的作用。

Nothing 还可以用作泛型实参,以起到一个空白占位符的作用,例如:

val emptyList: List<Nothing> = listOf() 
var apples: List<Apple> = emptyList 
var users: List<User> = emptyList 
var phones: List<Phone> = emptyList 
var images: List<Image> = emptyList  

val emptySet: Set<Nothing> = setOf() 
var apples: Set<Apple> = emptySet 
var users: Set<User> = emptySet 
var phones: Set<Phone> = emptySet
var images: Set<Image> = emptySet  

val emptyMap: Map<String, Nothing> = emptyMap() 
var apples: Map<String, Apple> = emptyMap 
var users: Map<String, User> = emptyMap 
var phones: Map<String, Phone> = emptyMap 
var images: Map<String, Image> = emptyMap 
val emptyProducer: Producer<Nothing> = Producer() 
var appleProducer: Producer<Apple> = emptyProducer 
var userProducer: Producer<User> = emptyProducer 
var phoneProducer: Producer<Phone> = emptyProducer 
var imageProducer: Producer<Image> = emptyProducer 

这样写唯一的好处就是,简单方便,节省内存。

Nothing 还有一个作用就是当你不知道返回什么的时候,就可以返回它,例如:

fun sayMyName(first: String, second: String) {
	val name = if (first == "Walter" && second == "White") { 
		"Heisenberg"
	} else {
		return // 语法层面的返回值类型为 Nothing,赋值给 name 
	}
	println(name) 
}

这里虽然没有显示的写出来,但是在语法层面这里的 return 返回的就是 Nothing

对于 Unit,它主要用来对标 Java 中的 void 关键字,表示什么也不返回,即返回空。为什么要设计一个这个呢,直接使用 void 不就行了吗?这是因为 Java 中的 void 关键字,虽然表示返回空,但是它不是一个实际的类型,在某些地方,你就不能用它来作为一个类型去表达“什么也不返回”的含义,例如最常见的例子就是以前开发中经常使用的 AsyncTask

class BackgroundTask extends AsyncTask<String, Void, Void> {

 	@Override
    protected Void doInBackground(String... strings) {
        // do something
        return null;
    }

    @Override
    protected void onProgressUpdate(Void[] values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(Void o) {

    }
}

回忆一下AsyncTask的三个泛型参数:public abstract class AsyncTask<Params, Progress, Result>

  • ParamsdoInBackground方法的接受参数类型
  • ProgressonProgressUpdate方法的接受参数类型
  • ResultonPostExecute方法的接受参数类型,以及doInBackground方法的返回参数类型

在上面代码中,由于void是一个关键字,而不是一个类型,所以不能用于泛型的位置作为实参,所以就会出现很尴尬的场景,我们必须再造一个类似于void关键字的类型,也就是大写的 Void(它其实是一个类),并且上面代码中,我们看到 doInBackground 方法的返回参数类型是 Void,但实际上是 return 了一个 null,而且你不写这个 null 又不行编译不过。这就令人非常尴尬了。

为了避免这种令人尴尬的场景,kotlin 就干脆直接定义了一个 Unit 类型来代表这种 Void,它是一个实际的类型,你可以用它来定义变量,它也可以出现在任何需要类型的地方。

在这里插入图片描述
在这里插入图片描述

但是 Kotlin 中一般函数没有返回值时,我们可以省略写这个 Unit

Unit 更常见的身影是 lambda 表达式以及高阶函数的参数定义中,例如:

fun foo(block : (Int, String) -> Unit) : Unit {
    return block(123, "hello")
}
val sum : (Int, Int) -> Unit = { x, y -> x + y }
fun main() {
    val a = sum(1, 2)
}

确切的说,在 Kotlin 中它的主要用途是用来表达函数类型,因为如果只是一个关键字的话,而不是一个类型,那么你就无法把它在需要表达函数类型的地方写出来。

关于 Unit 另外一个比较常见的场景是在 Jetpack Compose 的副作用 API 中的使用,例如:

LaunchedEffect(Unit) {
	...
}

这种写法显得比较奇怪,但是看一眼 Unit 的源码就明白了:

/**
 * The type with only one value: the `Unit` object. This type corresponds to the `void` type in Java.
 */
public object Unit {
    override fun toString() = "kotlin.Unit"
}

我们发现它就是一个 object 单例,而 object 对应到 Java 中就是一个 final 类,因此 object 单例在运行时是不变的,它就是一个常量

所以LaunchedEffect(Unit)也可以写成LaunchedEffect(1)LaunchedEffect(2)LaunchedEffect("aaa")LaunchedEffect(true)都可以,只不过你想不到写啥的时候,直接写 Unit 会更方便。

接口代理 by

它跟代理模式差不多,但是有一点不同的 kotlin 的 by 更多的是将一个接口的实现委托给一个实现类对象。

比如 Android 中的 ContextWrapper 就是将对 Context 的操作全部都委托给了内部一个叫做 mBaseContext 成员对象去处理:

public class ContextWrapper extends Context {
	Context mBase;
	
	public ContextWrapper(Context base) { 
		mBase = base; 
	}
	
	@Override
	public AssetManager getAssets() {
		return mBase.getAssets(); 
	}
	
	@Override
	public Resources getResources() {
	 	return mBase.getResources(); 
	}
	
	@Override
	public PackageManager getPackageManager() {
		return mBase.getPackageManager(); 
	}
	
	@Override
	public ContentResolver getContentResolver() { 
		return mBase.getContentResolver(); 
	}
	...
}

再比如我想定制一个类型是 UserList 对象,实现按年龄排序之类的需求,可以这样写:

class UserList implements List<User> {
	List<User> userList;
	
	public UserList(List<User> userList) { 
		this.userList = userList; 
	} 
	
	public List<User> higherRiskUsers() { ...}
	
	public void sortWithAge() { ...} 
	
	@Override
	public int size() { 
		return 0; 
	} 
	
	@Override
	public boolean isEmpty() { 
		return false;
	} 
	
	@Override
	public boolean contains(@Nullable Object o) { 
		return false;
	}
	......
} 

这样就是将对 List的操作委托给了内部的userList去操作,你可以传入一个ArrayList或者一个LinkedList的实现给它。

但是这样写的话,我们发现里面多了很多不需要但是不得不写的方法,例如size()isEmpty()等等,如果使用 Kotlin 的 by 关键字进行委托,就会简化很多,例如:

class UserList(private val list: List<User>): List<User> by list {
	
	public List<User> higherRiskUsers() { ...}
	
	public void sortWithAge() { ...} 	
}

这样那些不得不写但又没有营养的方法,我们就可以不用手动去写了,而是交给构造函数中传入的 list 参数的实现类实例对象去自动实现这些方法。我们只需要关心在这个类中,如何实现真正需要添加的业务方法即可。

总结一下它的语法就是:

class 类名(val 实际接口的实现者[a] : 接口类) : 接口类 by [a] {

}

其中 a 传入的就是要实现的接口的实际实现对象实例。

当然在使用 by 关键字也可以不完全委托给构造函数传入的实例对象,如果在类中覆写了所实现接口的某个方法,就会以你覆写的为准,而不是交给委托的对象,这一点比较灵活。

比如 Kotlin 的协程源码中有一个叫 SubscribedSharedFlow 就是对 SharedFlow 进行了委托,但是它没有完全委托给构造函数传入的sharedFlow对象,而是重写了SharedFlow接口的 collect 方法:

在这里插入图片描述
在这里插入图片描述

通过这种 方式我们可以进行某种“局部定制”功能,这个感觉有点类似 Java 里面你继承一个父类抽象类,但是懒于实现其每个抽象方法,但是又想在需要的时候自由覆盖其某个抽象方法,kotlin一个by关键字解决了这个麻烦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/254689.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arcgis javascript api4.x加载天地图cgs2000坐标系

需求&#xff1a;arcgis javascript api4.x加载天地图cgs2000坐标系 效果&#xff1a; 示例代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"wid…

vscode使用remote ssh到server上 - Node进程吃满CPU

起因&#xff1a;Node进程吃满CPU 分析 我发现每次使用vscode的remote插件登陆到server后&#xff0c;就会出现node进程&#xff0c;不太清楚干什么用的&#xff0c;但是绝对和它有关。 查找原因 首先找到了这篇文章&#xff0c;解决了rg进程的问题&#xff1a; https://blo…

克服端口顺序影响,使用PCAN实现固定设备ID/通道分配

来源&#xff1a;虹科智能互联 虹科干货 | 克服端口顺序影响&#xff0c;使用PCAN实现固定设备ID/通道分配 原文链接&#xff1a;https://mp.weixin.qq.com/s/Ik2fp9sWyI9MiQOOHO1dCA 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 导读 多设备协同工作是常见的需求…

Mac managing Multiple Python Versions With pyenv 【 mac pyenv 管理多个python 版本 】

文章目录 1. 简介2. 安装2.1 brew 安装 pyenv2.2 脚本安装 3. pyenv 安装 Python4. 卸载 python5. 管理 python 1. 简介 Pyenv 是一个用于管理和切换多个 Python 版本的工具。它允许开发人员在同一台计算机上同时安装和使用多个不同的 Python 版本&#xff0c;而无需对系统进行…

Apache Seatunnel本地源码构建编译运行调试

Apache Seatunnel本地源码构建编译运行调试 文章目录 1. 环境准备1.1 Java环境1.2 Maven1.3 IDEA1.4 Docker环境1.5 Mysql8.0.281.6 其它环境准备 2. 源码包下载3. idea项目配置3.1 项目导入3.2 maven配置3.3 项目JDK配置3.4 项目启动参数配置3.4.1 seatunnel项目启动参数配置3…

SpringBoot+WebSocket

SpringBootWebSocket 1.导入依赖&#xff1a; -- Spring Boot 2.x 使用 javax.websocket-- Spring Boot 3.x 使用 jakarta.websocket<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId&g…

git基础命令(小白适合看)

作者&#xff1a;爱塔居 欢迎大佬指正 1.git add 跟上文件地址 要注意斜杆&#xff0c;然后文件地址就是我们修改文件的地址。 就比如git add xx/xx/xx.x&#xff0c;记得加后缀&#xff0c;然后如果是几个文件&#xff0c;就加空格 例如 git add xx/xx/xx.x yy/yy/yy.y 2.…

机器翻译:跨越语言边界的智能大使

导言 机器翻译作为人工智能领域的瑰宝&#xff0c;正在以前所未有的速度和精度&#xff0c;为全球沟通拓展新的可能性。本文将深入研究机器翻译的技术原理、应用场景以及对语言交流未来的影响。 1. 简介 机器翻译是一项致力于通过计算机自动将一种语言的文本翻译成另一种语言的…

BearPi Std 板从入门到放弃 - 先天神魂篇(3)(RT-Thread I2C设备 读取光照强度BH1750)

简介 使用BearPi IOT Std开发板及其扩展板E53_SC1&#xff0c; SC1上有I2C1 的光照强度传感器BH1750 和 EEPROM AT24C02&#xff0c; 本次主要就是读取光照强度; 主板: 主芯片: STM32L431RCT6LED : PC13 \ 推挽输出\ 高电平点亮串口: Usart1I2C使用 : I2C1E53_SC1扩展板 : LE…

Linux服务器性能优化小结

文章目录 生产环境监测常见专业名词扫盲服务器平均负载服务器平均负载的定义如何判断平均负载值以及好坏情况如果依据平均负载来判断服务器当前状况系统平均负载和CPU使用率的区别 CPU上下文切换基本概念3种上下文切换进程上下文切换线程上下文切换中断上下文切换 查看上下文切…

视频号链接提取器详细使用指南,教你轻松下载号视频!

视频号下载提取器的使用方法会因不同工具而略有差异&#xff0c;但大体上可以按照以下步骤进行操作&#xff1a; 1. 找到一个适合的视频号下载提取器&#xff1a;可以在微信搜一搜中输入关键词“超级短视频去水印解析助手”&#xff0c;选择进入公众号、获取在线视频下载提取工…

12.18_黑马数据结构与算法笔记Java

目录 thinking:orElse? thinking:map.computerifabsent? thinking&#xff1a;subString&#xff1f; 184 哈希表 问2 解释拆分 185 哈希算法 概述 186 哈希算法 Object.hashCode 187 哈希算法 String.hashCode 188 哈希算法 冲突测试 189 哈希算法 MurmurHash 190…

【轮式移动机器人课程笔记3】移动机器人运动学简介

文章目录 写在前面L3 移动机器人运动学简介3.1 运动学概述3.2 研究机器人运动学的意义3.3 机器人运动的描述3.4 机器人正微分运动学3.5 机器人逆微分运动学3.6 总结 写在前面 前两节课介绍了移动机器人、机械手、类型&#xff0c;本节课重点讲解移动机器人运动学相关知识&…

hive的分区表和分桶表详解

分区表 Hive中的分区就是把一张大表的数据按照业务需要分散的存储到多个目录&#xff0c;每个目录就称为该表的一个分区。在查询时通过where子句中的表达式选择查询所需要的分区&#xff0c;这样的查询效率会提高很多。 静态分区表基本语法 创建分区表 create table dept_p…

数据安全无阻,轻松远程工作!迅软DSE出差加密指南,让你出差更放心!

文件加密软件是确保内网文件安全使用的重要工具&#xff0c;但在终端脱离内部网络、面对外出或居家办公等情境时&#xff0c;文件加密的挑战也相应增加。为解决这一问题&#xff0c;迅软DSE文件加密软件提供了离线授权功能&#xff0c;确保在终端脱离公司网络后的设定时间内&am…

使用Docker运行Nacos并安装cpolar内网穿透工具实现远程访问

文章目录 1. Docker 运行Nacos2. 本地访问Nacos3. Linux安装Cpolar4. 配置Nacos UI界面公网地址5. 远程访问 Nacos UI界面6. 固定Nacos UI界面公网地址7. 固定地址访问Plik Nacos是阿里开放的一款中间件,也是一款服务注册中心&#xff0c;它主要提供三种功能&#xff1a;持久化…

openwrt 搭建web

折腾 软路由 有几年了&#xff0c;最近试了下 移动的 IPV6, 既然可以拿到 公网的 IPV6&#xff0c; 所以想折腾下, 经过不懈努力 实现了&#xff1a;通过 ipv4/ipv6 地址访问我的 web站点 (白飘不花钱的方式) 1 动态DNS 折腾 DDNS 无非是想 白飘 公网IP&#xff0c;但是 仅仅…

一个企业为什么要数字化转型?答案在这里!

一个企业为什么要数字化转型&#xff1f; 先简单说说原因。 因为很多行业现在存在大量的产能过剩、产品过剩、服务过剩&#xff0c;经营维度低、行业竞争激烈......企业生存困难&#xff0c;必须改变经营维度才能活下来&#xff0c;才能变现。 单方面举个例子&#xff0c;可…

windows如何环境搭建属于自己的Zblog博客并发布上线公网访问?

文章目录 1. 前言2. Z-blog网站搭建2.1 XAMPP环境设置2.2 Z-blog安装2.3 Z-blog网页测试2.4 Cpolar安装和注册 3. 本地网页发布3.1. Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 想要成为一个合格的技术宅或程序员&#xff0c;自己搭建网站制作网页是绕…

LibreNMS:从docker出发

引言 LibreNMS 是一个免费开源的网络监控和自动化工具&#xff0c;用于监视网络设备、服务器和应用程序的性能和状态。它提供了一个集中的管理平台&#xff0c;帮助管理员实时监控和管理整个网络基础设施。 以下是 LibreNMS 的一些主要特点和功能&#xff1a; 自动发现&#…