Compose 没有 inputType 怎么过滤(限制)输入内容?这题我会!

news2024/11/26 16:34:49

前言

闲话

在我之前的文章 《Compose For Desktop 实践:使用 Compose-jb 做一个时间水印助手》 中,我埋了一个坑,关于在 Compose 中如何过滤 TextField 的输入内容。时隔好几个月了,今天这篇文章就是来填这个坑的。

为什么需要添加过滤

在正式开始之前,我们先来回答一下标题这个问题,为什么需要过滤呢?

众所周知,在安卓的原生 View 体系中,输入框是 EditText 我们可以通过在 xml 布局文件中添加 inputType 属性来指定预设的几个允许的输入内容格式,例如:numbernumberDecimalnumberSigned 等属性,分别表示过滤输入结果仅允许输入数字、十进制数字、带符号数字。另外,我们也可以通过给 EditText 自定义继承自 InputFilter 的过滤方法(setFilters)来自定义我们自己的过滤规则。

但是在 Compose 中我们应该怎么去做呢?

在 Compose 中,输入框是 TextField,查遍 TextField 的参数列表,我们就会发现,并没有给我们提供任何类似于 EditText 的过滤参数。

最多只能找到一个 keyboardOptions: KeyboardOptions = KeyboardOptions.Default 参数,而这个参数也只是请求输入法展示我们要求的输入类型,例如只展示数字键盘,但是,这里只是请求,并不是要求,所以人家输入法还不一定理呢,哈哈哈。而且这个参数并不能限制输入框的内容,只是更改了默认弹出软键盘的类型而已。

其实想想也不难理解,毕竟 Compose 是声明式 UI ,如何处理输入内容确实应该由我们自己来实现。

但是如果官方能提供几个类似 EditText 的预设过滤参数就好了,可惜并没有。

所以我们需要自己实现对输入内容的过滤。

回到标题内容,为什么我们需要过滤输入内容?

这里我们将以上文中中提到的 时间水印助手 为例子讲解:

s1.png

在这个界面中,我们需要输入多个参数。

比如参数 “导出图像质量” ,我们需要将输入内容限制在 0.0 - 1.0 的浮点数。

当然,我们完全可以不在输入时限制,可以允许用户随意输入任意内容,然后在实际提交数据时再做校验,但是,显然,这样是不合理的,用户体验也不佳。

所以我们最好还是能直接在用户输入时就做好限制。

实现

先简单试一下

其实想做过滤也不是不行,想想好像还是挺简单的嘛,这里以简单的限制输入内容长度(类似 EditTExt 中的 maxLength 属性)为例子举例:

var inputValue by remember { mutableStateOf("") }

TextField(
    value = inputValue,
    onValueChange = { inputValue = it }
)

可能读者们会说,嗨,不就是限制输入长度嘛,这在声明式 UI 中都不叫事,看我直接这样就行了:

val maxLength = 8
var inputValue by remember { mutableStateOf("") }

TextField(
    value = inputValue,
    onValueChange = {
        if (it.length <= maxLength) inputValue = it
    }
)

我们在输入值改变时加一个判断,只有输入值的长度小于了定义的最大长度我们才改变 inputValue 的值。

咋一看,好想没有问题是吧?

但是,你再仔细想想。

真的这么简单吗?

你有没有想过以下两种情况:

  1. 我们在已经输入了 8 个字符后,把光标移动到中间位置,此时再输入内容,你猜会发生什么?
  2. 我们在输入了不足8个的字符(例如 5 个后),同时粘贴超过限制字符数的内容(例如 4 个),你猜会发生什么?

不卖关子了,其实对于 情况 1 ,会出现内容确实没有继续添加了,但是光标会往后走的情况:

s2.gif

而对于 情况 2 ,相信不用我说,读者也能猜出来了,那就是粘贴后没有任何反应。

没错,显然因为我们在 onValueChange 中加了判断,如果当前输入的值 (it) 大于了限制的值(maxLength )那么我们将不会做任何响应。但是这个显然是不合理的,因为虽然我们粘贴的所有内容直接插入输入框的话确实会超出最大字符限制,但是并不是说输入框不能再输入内容了,很显然,输入框还可以接受再输入 3 个字符。所以我们应该做的处理是将新输入的内容截断,截取符合数量的内容插入输入框,多余的内容直接舍弃。

原生 View 的 EditText 也是这样的处理逻辑。

那么,现在我们应该怎么做呢?

实践一下,限制输入字符长度

经过上面的小试牛刀,相信大家也知道了,对于限制输入内容,不能简单的直接对输入的 String 做处理,而应该考虑到更多的情况,其中最需要关注的情况有两点:一是对输入框光标的控制;二是对选择多个字符和粘贴多个字符情况的处理(因为正常输入可以保证每次只输入(或删除)一个字符,但是粘贴或多选后不一定)。

很显然,如果想控制光标的话,我们不能直接使用 value 为 String 的 TextField 而应该改用使用 TextFieldValue

var inputValue by remember { mutableStateOf(TextFieldValue()) }

OutlinedTextField(
    value = inputValue,
    onValueChange = {  }
)

TextFieldValue 是一个封装了输入内容(text: String)、和选择以及光标状态(selection: TextRange)的类。

其中 TextRange 有两个参数 startend 分别表示选中文本时的开始和结束位置,如果两个值相等则表示没有选中任何文本,此时 TextRange 表示的是光标位置。

现在,我们已经具备了可以解决上面说的两点问题的前置条件,下面就是应该怎么去解决这个问题了。

其实对于问题 1 ,非常好解决,我们甚至都不需要过多的去变动代码,只需要把使用的 String 值 改成 TextFieldValue 即可:

val maxLength = 8
var inputValue by remember { mutableStateOf(TextFieldValue()) }

OutlinedTextField(
    value = inputValue,
    onValueChange = {
        if (it.text.length <= maxLength) inputValue = it
    }
)

原因也很简单,因为 TextFieldValue 中已经包含了光标信息,这里我们在输入内容超过限制长度时不做更改 inputValue 的值,实际上是连同光标信息一起不做更改了,而上面直接使用的是 String ,则只是不改变输入内容,但是光标位置还是会被改变。

而对于问题 2 ,需要我们做一些特殊的处理。

我们首先定义一个函数来处理输入内容的改动:

fun filterMaxLength(
    inputTextField: TextFieldValue,
    lastTextField: TextFieldValue,
    maxLength: Int
): TextFieldValue {
    // TODO
}

这个函数接收两个参数:inputTextFieldlastTextField 分别表示加上新输入的内容后的 TextFieldValue 和没有输入新内容时的 TextFieldValue

这里有个地方需要注意,就是在 TextFieldonChange 回调中,如果使用的是 TextFieldValue,那么不仅会在输入内容发生改变时才调用 onChange 回调,而是即使只有光标的移动或状态改变都会调用 onChange 回调。

然后,我们在这个函数中处理一下对于粘贴多个字符时的情况:

val inputCharCount = inputTextField.text.length - lastTextField.text.length
if (inputCharCount > 1) { // 同时粘贴了多个字符内容
    val allowCount = maxLength - lastTextField.text.length
    // 允许再输入字符已经为空,则直接返回原数据
    if (allowCount <= 0) return lastTextField

    // 还有允许输入的字符,则将其截断后插入
    val newString = StringBuffer()
    newString.append(lastTextField.text)
    val newChar = inputTextField.text.substring(lastTextField.selection.start..allowCount)
    newString.insert(lastTextField.selection.start, newChar)
    return lastTextField.copy(text = newString.toString(), selection = TextRange(lastTextField.selection.start + newChar.length))
}

这段代码其实很好理解,首先我们通过使用未更新前的字符长度减去本次输入的字符长度得到本次实际新增的字符长度,如果这个长度大于 1 则认为是同时粘贴了多个内容进输入框。(这里有个点需要注意,就是这样得到的值可能会等于 0,表示只是光标的变动,字符没有变;小于 0 ,表示是删除内容。)

然后再使用最大允许输入的字符长度减去未更新前的输入框字符长度,即可得到当前还允许再插入多少个字符 allowCount

如果尚还余有可输入的字符,则通过截取输入内容字符的符合长度的新增字段来获取。

截取的起点使用的是未更新前的光标起始位置(lastTextField.selection.start),截取长度就是还允许输入的字符长度。

需要注意的是,这里之所以使用 lastTextField.selection.start 作为截取起点,而不是 lastTextField.selection.end 是因为粘贴插入时也可能是因为之前已经选中了部分内容,然后再插入的,此时就应该以 未更新时的选中状态起点 作为插入的位置。而如果粘贴插入时并非选中状态,那么使用 startend 都可以,因为此时它俩的值是一样的。

拿到可以插入的字符后,接下里就是将其插入即可:newString.insert(lastTextField.selection.start, newChar)

最后返回时别忘了改一下光标的位置,这里其实也很简单,就是改到新字符插入的位置+实际插入的字符数量: TextRange(lastTextField.selection.start + newChar.length)

最后,完整的限制输入长度的过滤函数如下:

/**
 * 过滤输入内容长度
 *
 * @param maxLength 允许输入长度,如果 小于 0 则不做过滤,直接返回原数据
 * */
fun filterMaxLength(
    inputTextField: TextFieldValue,
    lastTextField: TextFieldValue,
    maxLength: Int
): TextFieldValue {
    if (maxLength < 0) return inputTextField // 错误的长度,不处理直接返回

    if (inputTextField.text.length <= maxLength) return inputTextField // 总计输入内容没有超出长度限制


    // 输入内容超出了长度限制
    // 这里要分两种情况:
    // 1. 直接输入的,则返回原数据即可
    // 2. 粘贴后会导致长度超出,此时可能还可以输入部分字符,所以需要判断后截断输入

    val inputCharCount = inputTextField.text.length - lastTextField.text.length
    if (inputCharCount > 1) { // 同时粘贴了多个字符内容
        val allowCount = maxLength - lastTextField.text.length
        // 允许再输入字符已经为空,则直接返回原数据
        if (allowCount <= 0) return lastTextField

        // 还有允许输入的字符,则将其截断后插入
        val newString = StringBuffer()
        newString.append(lastTextField.text)
        val newChar = inputTextField.text.substring(lastTextField.selection.start..allowCount)
        newString.insert(lastTextField.selection.start, newChar)
        return lastTextField.copy(text = newString.toString(), selection = TextRange(lastTextField.selection.start + newChar.length))
    }
    else { // 正常输入
        return if (inputTextField.selection.collapsed) { // 如果当前不是选中状态,则使用上次输入的光标位置,如果使用本次的位置,光标位置会 +1
            lastTextField
        } else { // 如果当前是选中状态,则使用当前的光标位置
            lastTextField.copy(selection = inputTextField.selection)
        }
    }
}

其实这里的过滤函数还是有问题,不知道读者是否发现了?这里我就不指出也不改了,权当是留给读者们的一个思考题了,哈哈,毕竟不希望读者只是草草看完直接把代码粘贴走就完事了,哈哈哈哈。

我们在使用的时候只需要改一下 TextFieldonChange 回调即可:

val maxLength = 8
var inputValue by remember { mutableStateOf(TextFieldValue()) }
OutlinedTextField(
    value = inputValue,
    onValueChange = {
        inputValue = filterMaxLength(it, inputValue, maxLength)
    }
)

扩展一下,做一个通用的过滤

虽然上面我们已经实现了做一个自己的输入内容限制,但是似乎扩展性不怎么好啊。

我们有没有办法做一个通用的,方便扩展的过滤方法呢?

毕竟 View 中的过滤不仅是自己预设了很多好用的过滤,而且它还提供了一个通用的接口 InputFilter 可以让我们自己定义我们自己需要的过滤方法。

那么,说干就干,首先我们来看看 View 中的 InputFilter 是怎么写的:

s3.png

这么一看,其实也不是很复杂,就是一个 InputFilter 类,只有一个 filter 方法,这个方法返回一个 CharSequence 表示经过过滤处理后的新的字符。

它提供了 6 个参数:

  1. source :要插入的新字符
  2. startsource 中要插入的字符位置起点
  3. endsource 中要插入的字符位置终点
  4. dest : 输入框中的原内容
  5. dstartdest 中要被 source 插入的位置的起点
  6. denddest 中要被 source 插入的位置的终点

我们只需要使用这六个参数对字符进行过滤处理后返回新的字符就可以了。

但是 View 中的 InputFilter 显然只负责过滤字符,不负责处理更改光标的位置。

不管怎样,我们也照猫画虎做一个 Compose 版本的过滤基类 BaseFieldFilter

open class BaseFieldFilter {
    private var inputValue = mutableStateOf(TextFieldValue())

    protected open fun onFilter(inputTextFieldValue: TextFieldValue, lastTextFieldValue: TextFieldValue): TextFieldValue {
        return TextFieldValue()
    }

    protected open fun computePos(): Int {
        // TODO
        return 0
    }

    protecte fun getNewTextRange(
        lastTextFiled: TextFieldValue,
        inputTextFieldValue: TextFieldValue
    ): TextRange? {
        // TODO
        retutn null
    }

    protecte fun getNewText(
        lastTextFiled: TextFieldValue,
        inputTextFieldValue: TextFieldValue
    ): TextRange? {
        // TODO
        return null
    }

    fun getInputValue(): TextFieldValue {
        return inputValue.value
    }

    fun onValueChange(): (TextFieldValue) -> Unit {
        return {
            inputValue.value = onFilter(it, inputValue.value)
        }
    }
}

在这类中我们需要重点关注 onFilter 方法,我们的过滤内容主要在这个方法中编写。

然后在 TextField 中主要会使用到 getInputValueonValueChange 方法。

本来我还打算写几个基础的工具方法 getNewTextgetNewTextRangecomputePos 分别用于计算实际插入的新字符、实际插入字符的位置、新的索引位置。

但是后来发现似乎并不好写出一个很好用的通用方法,所以这里我就留空了。

这个基础类使用起来也很简单,我们只需要将我们自己的过滤方法继承这个基础类,然后重载 onFilter 方法即可,还是以限制输入长度为例,写一个类 FilterMaxLength

/**
 * 过滤输入内容长度
 *
 * @param maxLength 允许输入长度,如果 小于 0 则不做过滤,直接返回原数据
 * */
class FilterMaxLength(
    @androidx.annotation.IntRange(from = 0L)
    private val maxLength: Int
) : BaseFieldFilter() {
    override fun onFilter(
        inputTextFieldValue: TextFieldValue,
        lastTextFieldValue: TextFieldValue
    ): TextFieldValue {
        return filterMaxLength(inputTextFieldValue, lastTextFieldValue, maxLength)
    }

    private fun filterMaxLength(
        inputTextField: TextFieldValue,
        lastTextField: TextFieldValue,
        maxLength: Int
    ): TextFieldValue {
        if (maxLength < 0) return inputTextField // 错误的长度,不处理直接返回

        if (inputTextField.text.length <= maxLength) return inputTextField // 总计输入内容没有超出长度限制


        // 输入内容超出了长度限制
        // 这里要分两种情况:
        // 1. 直接输入的,则返回原数据即可
        // 2. 粘贴后会导致长度超出,此时可能还可以输入部分字符,所以需要判断后截断输入

        val inputCharCount = inputTextField.text.length - lastTextField.text.length
        if (inputCharCount > 1) { // 同时粘贴了多个字符内容
            val allowCount = maxLength - lastTextField.text.length
            // 允许再输入字符已经为空,则直接返回原数据
            if (allowCount <= 0) return lastTextField

            // 还有允许输入的字符,则将其截断后插入
            val newString = StringBuffer()
            newString.append(lastTextField.text)
            val newChar = inputTextField.text.substring(lastTextField.selection.start..allowCount)
            newString.insert(lastTextField.selection.start, newChar)
            return lastTextField.copy(text = newString.toString(), selection = TextRange(lastTextField.selection.start + newChar.length))
        }
        else { // 正常输入
            return if (inputTextField.selection.collapsed) { // 如果当前不是选中状态,则使用上次输入的光标位置,如果使用本次的位置,光标位置会 +1
                lastTextField
            } else { // 如果当前是选中状态,则使用当前的光标位置
                lastTextField.copy(selection = inputTextField.selection)
            }
        }
    }
}

此时,我们在 TextField 中只需要这样即可调用:

val filter = remember { FilterMaxLength(8) }
    
OutlinedTextField(
    value = filter.getInputValue(),
    onValueChange = filter.onValueChange(),
)

怎么样,是不是非常的方便快捷?

我还想要更多!

当然,上面一直都是拿的限制输入长度举例子,那其他的过滤实现呢?你倒是端出来啊,别急,这就为各位奉上我项目中用到的几个过滤方法。

同样的,这些方法或多或少我都留有坑,各位千万不要不检查一下直接就用哦(坏笑)。

哈哈哈,开玩笑了,其实完整没坑的代码各位可以去前言中我提到的那个项目中找。

过滤数字

class FilterNumber(
    private val minValue: Double = -Double.MAX_VALUE,
    private val maxValue: Double = Double.MAX_VALUE,
    private val decimalNumber: Int = -1
) : BaseFieldFilter() {

    override fun onFilter(
        inputTextFieldValue: TextFieldValue,
        lastTextFieldValue: TextFieldValue
    ): TextFieldValue {
        return filterInputNumber(inputTextFieldValue, lastTextFieldValue, minValue, maxValue, decimalNumber)
    }

    private fun filterInputNumber(
        inputTextFieldValue: TextFieldValue,
        lastInputTextFieldValue: TextFieldValue,
        minValue: Double = -Double.MAX_VALUE,
        maxValue: Double = Double.MAX_VALUE,
        decimalNumber: Int = -1,
    ): TextFieldValue {
        val inputString = inputTextFieldValue.text
        val lastString = lastInputTextFieldValue.text

        val newString = StringBuffer()
        val supportNegative = minValue < 0
        var dotIndex = -1
        var isNegative = false

        if (supportNegative && inputString.isNotEmpty() && inputString.first() == '-') {
            isNegative = true
            newString.append('-')
        }

        for (c in inputString) {
            when (c) {
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> {
                    newString.append(c)
                    val tempValue = newString.toString().toDouble()
                    if (tempValue > maxValue) newString.deleteCharAt(newString.lastIndex)
                    if (tempValue < minValue) newString.deleteCharAt(newString.lastIndex) // TODO 需要改进 (例如限制最小值为 100000000,则将无法输入东西)

                    if (dotIndex != -1) {
                        if (decimalNumber != -1) {
                            val decimalCount = (newString.length - dotIndex - 1).coerceAtLeast(0)
                            if (decimalCount > decimalNumber) newString.deleteCharAt(newString.lastIndex)
                        }
                    }
                }
                '.' -> {
                    if (decimalNumber != 0) {
                        if (dotIndex == -1) {
                            if (newString.isEmpty()) {
                                if (abs(minValue) < 1) {
                                    newString.append("0.")
                                    dotIndex = newString.lastIndex
                                }
                            } else {
                                newString.append(c)
                                dotIndex = newString.lastIndex
                            }

                            if (newString.isNotEmpty() && newString.toString().toDouble() == maxValue) {
                                dotIndex = -1
                                newString.deleteCharAt(newString.lastIndex)
                            }
                        }
                    }
                }
            }
        }

        val textRange: TextRange
        if (inputTextFieldValue.selection.collapsed) { // 表示的是光标范围
            if (inputTextFieldValue.selection.end != inputTextFieldValue.text.length) { // 光标没有指向末尾
                var newPosition = inputTextFieldValue.selection.end + (newString.length - inputString.length)
                if (newPosition < 0) {
                    newPosition = inputTextFieldValue.selection.end
                }
                textRange = TextRange(newPosition)
            }
            else { // 光标指向了末尾
                textRange = TextRange(newString.length)
            }
        }
        else {
            textRange = TextRange(newString.length)
        }

        return lastInputTextFieldValue.copy(
            text = newString.toString(),
            selection = textRange
        )
    }
}

仅允许输入指定字符

class FilterOnlyChar() : BaseFieldFilter() {
    private var allowSet: Set<Char> = emptySet()

    constructor(allowSet: String) : this() {
        val tempSet = mutableSetOf<Char>()
        for (c in allowSet) {
            tempSet.add(c)
        }
        this.allowSet = tempSet
    }

    constructor(allowSet: Set<Char>) : this() {
        this.allowSet = allowSet
    }

    override fun onFilter(
        inputTextFieldValue: TextFieldValue,
        lastTextFieldValue: TextFieldValue
    ): TextFieldValue {
        return filterOnlyChar(
            inputTextFieldValue,
            lastTextFieldValue,
            allowChar = allowSet
        )
    }

    private fun filterOnlyChar(
        inputTextFiled: TextFieldValue,
        lastTextFiled: TextFieldValue,
        allowChar: Set<Char>
    ): TextFieldValue {
        if (allowChar.isEmpty()) return inputTextFiled // 如果允许列表为空则不过滤

        val newString = StringBuilder()

        var modifierEnd = 0

        for (c in inputTextFiled.text) {
            if (c in allowChar) {
                newString.append(c)
            }
            else modifierEnd--
        }

        return inputTextFiled.copy(text = newString.toString())
    }
}

过滤电子邮箱地址

class FilterStandardEmail(private val extraChar: String = "") : BaseFieldFilter() {
    private val allowChar: MutableSet<Char> = mutableSetOf('@', '.', '_', '-').apply {
        addAll('0'..'9')
        addAll('a'..'z')
        addAll('A'..'Z')
        addAll(extraChar.asIterable())
    }

    override fun onFilter(
        inputTextFieldValue: TextFieldValue,
        lastTextFieldValue: TextFieldValue
    ): TextFieldValue {
        return inputTextFieldValue.copy(text = filterStandardEmail(inputTextFieldValue.text, lastTextFieldValue.text))
    }

    private fun filterStandardEmail(
        inputString: String,
        lastString: String,
    ): String {
        val newString = StringBuffer()
        var flag = 0 // 0 -> None 1 -> "@" 2 -> "."

        for (c in inputString) {
            if (c !in allowChar) continue

            when (c) {
                '@' -> {
                    if (flag == 0) {
                        if (newString.isNotEmpty() && newString.last() != '.') {
                            if (newString.isNotEmpty()) {
                                newString.append(c)
                                flag++
                            }
                        }
                    }
                }
                '.' -> {
                    // if (flag >= 1) {
                        if (newString.isNotEmpty() && newString.last() != '@' && newString.last() != '.') {
                            newString.append(c)
                            // flag++
                        }
                    // }
                }
                else -> {
                    newString.append(c)
                }
            }
        }

        return newString.toString()
    }

}

过滤十六进制颜色

class FilterColorHex(
    private val includeAlpha: Boolean = true
) : BaseFieldFilter() {

    override fun onFilter(
        inputTextFieldValue: TextFieldValue,
        lastTextFieldValue: TextFieldValue
    ): TextFieldValue {
        return inputTextFieldValue.copy(filterInputColorHex(
            inputTextFieldValue.text,
            lastTextFieldValue.text,
            includeAlpha
        ))
    }

    private fun filterInputColorHex(
        inputValue: String,
        lastValue: String,
        includeAlpha: Boolean = true
    ): String {
        val maxIndex = if (includeAlpha) 8 else 6
        val newString = StringBuffer()
        var index = 0

        for (c in inputValue) {
            if (index > maxIndex) break

            if (index == 0) {
                if (c == '#') {
                    newString.append(c)
                    index++
                }
            }
            else {
                if (c in '0'..'9' || c.uppercase() in "A".."F" ) {
                    newString.append(c.uppercase())
                    index++
                }
            }
        }

        return newString.toString()
    }
}

总结

虽然在 Compsoe 中官方没有提供类似于 EditText 中的 inputType 的预设输入内容过滤,但是得益于 Compose 的声明式 UI,在 Compose 中过滤输入内容更加简单,都不需要太多繁琐的步骤,因为我们可以直接操作输入的内容。

本文就由浅入深的介绍了如何在 Compose 中快速实现类似于安卓原生 View 中的 inputType 输入内容过滤的方法,并且提供了几种常用的过滤供大家使用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Doris

Aggregate 模型 是相同key的数据进行自动聚合的表模型。表中的列按照是否设置了 AggregationType&#xff0c;分为 Key&#xff08;维度列&#xff09;和 Value&#xff08;指标列&#xff09;&#xff0c;没有设置 AggregationType 的称为 Key&#xff0c;设置了 Aggregation…

散列表(哈希表)

目录 散列表 散列函数 散列表常用函数 1. 直接定址法 2. 除留余数法 2.1. exmple 3. 数字分析法 4. 平方取中法 5. 折叠法 处理冲突的方法 1. 开放定址法---线性探测 2. 二次探测法 3. 再Hash法 4. 拉链法(链地址法) 散列表&#xff08;Hash table&#xff0c;也…

Redis缓存击穿及解决问题

缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候&#xff0c;恰好这时间点对这个 Key有大量的并发请求过来&#xff0c;这些请求发现缓存过期- -般都会从后端DB加载数据并回设到缓存&#xff0c;这个时候大并发的请求可能会瞬间把DB压垮。 解决方案有两种…

第五十四天学习记录:C语言进阶:动态内存管理Ⅱ

常见的动态内存错误 1、对NULL指针的解引用操作 int* p(int*)malloc(4); //p进行相关的判断 *p10;//malloc开辟空间失败&#xff0c;有可能对NULL指针解引用 free(p); pNULL;2、对动态开辟的内存的越界访问 int* p(int*)malloc(40);//10个int if(p!NULL) {int i0;//越界for(…

微服务项目租房网

文章目录 一、租房网项目的介绍1、使用的技术介绍2、使用的组件和开发工具的版本以及作用3、项目模块结构4、项目总体架构 二、环境搭建1、启动前端服务2、CentOS7各个组件的安装2.1 安装Docker2.2 安装JDK2.3 安装Redis(6390)2.4 安装FastDFS(8888)2.5 安装MongoDB(27017)2.6 …

Niagara—— 概述

目录 一&#xff0c;核心组件 Systems Emitters Modules Parameters 二&#xff0c;创建系统或发射器向导 System向导 Emetter向导 三&#xff0c;Niagara VFX工作流程 创建系统 创建或添加发射器 创建或添加模块 Niagara是最新一代VFX系统&#xff0c;无需程序员…

Junit测试框架详解

目录 Junit框架 导入Junit到项目 Junit注解 Test Disabled BeforeAll / AfterAll BeforeEach / AfterEach 参数化 单参数 多参数 CSV获取参数 方法获取参数 断言 assertEquals / assertNotEquals assertNull / assertNotNull 用例执行顺序 测试套件Suite 指定…

使用IIS创建WEB服务

文章目录 前言一、Web服务是什么&#xff1f;1.Web服务概述2.如何获取网页资源3.常见Web服务端软件4.什么是IIS 二、安装IIS1.安装Web服务器角色2.准备网页文件3.配置Web站点4.客户端浏览例&#xff1a;配置IIS站点 三、虚拟主机概述1.虚拟Web主机2.虚拟主机的几种类型3.基于端…

软考信管高级——进度管理

进度管理内容 缩短活动工期方法 赶工&#xff0c;投入更多资源或增加工作时间&#xff0c;以缩短关键活动的工期快速跟进&#xff0c;并行施工&#xff0c;以缩短关键路径长度使用高素质的资源或经验更丰富的人员减小活动范围或降低活动要求改进方法或技术&#xff0c;以提高…

活动回顾|解锁 AIGC 密码,探寻企业发展新商机

5月24日&#xff0c;Google Cloud 与 Cloud Ace 联合主办的线下活动顺利落下帷幕。 本次活动&#xff0c;有近 40 位企业精英到场支持。三位 Google Cloud 演讲嘉宾就本次活动主题&#xff0c;为大家带来了比较深度的演讲内容&#xff0c;干货满满。 &#xff08;*以下的嘉宾演…

期末复习总结【MySQL】聚合查询 + 多表联合查询(重点)

文章目录 前言一、聚合查询1, 聚合函数2, 聚合函数使用示例3, GROUP BY 子句4, HAVING 子句 二、联合查询(重点)1, 笛卡尔积2, 内连接2.1, 示例12.2, 示例22.3, 示例3 3, 外连接4, 自连接 总结 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#…

存量时代下,互联网玩家如何“自我造血”?

毫无疑问&#xff0c;互联网已经进入存量时代。 在过去高增长的增量时代&#xff0c;许多互联网企业追求规模效应&#xff0c;痴迷于“先规模后盈利”的打法&#xff0c;力图用规模构建护城河。然而&#xff0c;随着行业整体增长速度放缓&#xff0c;规模扩张变得更为艰难&…

面了个字节跳动拿 38K 出来的测试,让我见识到了跳槽的天花板

最近内卷严重&#xff0c;各种跳槽裁员&#xff0c;相信很多小伙伴也在准备金九银十的面试计划。 作为一个入职5年的老人家&#xff0c;目前工资比较乐观&#xff0c;但是我还是会选择跳槽&#xff0c;因为感觉在一个舒适圈待久了&#xff0c;人过得太过安逸&#xff0c;晋升涨…

【JavaSE】Java基础语法(十六):抽象类

文章目录 1. 抽象类的概述2. 抽象类的特点3. 抽象类的实用价值4. 抽象类的案例 1. 抽象类的概述 当我们在做子类共性功能抽取时&#xff0c;有些方法在父类中并没有具体的体现&#xff0c;这个时候就需要抽象类了&#xff01; 在Java中&#xff0c;一个没有方法体的方法应该定义…

基于TCP、UDP网络编程

文章目录 网络协议分层套接字UDP和TCP差异UDP的APIDatagramSocketDatagramPacket 基于UDP Socket 实现一个回显程序TCP的APISocket的API基于TCP实现回显程序 网络协议分层 应用层&#xff1a; 应用程序拿到数据怎么用传输层&#xff1a; 负责关注传输过程中起点和终点网络层 &…

windows环境下nginx+ftp服务器搭建简易文件服务器

这里写目录标题 1&#xff0c;前言2&#xff0c;FTP服务器搭建3&#xff0c;nginx安装 1&#xff0c;前言 几种文件服务器的对比 1&#xff0c;直接使用ftp服务器&#xff0c;访问图片路径为 ftp://账户:密码192.168.0.106/31275-105.jpg不采用这种方式&#xff0c;不安全容易…

【高效科研工具(二):使用NewbingChat(方法+问题)| Newbing帮你阅读paper、解析paper】

高效科研工具&#xff08;二&#xff09;&#xff1a;使用NewbingChat&#xff08;方法问题&#xff09;| Newbing帮你阅读paper、解析paper 目录 0、前言 1、NewbingChat 介绍 2、NewbingChat 注册方法 3、NewbingChat 遇到的问题 (&#x1f1fa;&#x1f1f8;位置切换问题、…

STM32H7B0VBT6使用Free RTOS配置SD卡+Fatfs文件管理系统

作者&#xff1a;Jack_G 时间&#xff1a;2023.05.26 版本&#xff1a;V1.0 上次修改时间&#xff1a; 环境&#xff1a; \quad \quad \quad \quad STM32Cube MX V6.8.1 \quad \quad \quad \quad STM32CubeH7 Firmware Package V1.11.0 / 04-Nov-2022 \quad \quad \quad \qu…

基于混合蛙跳的路径规划算法

路径规划算法&#xff1a;基于混合蛙跳优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于混合蛙跳优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

改变开发的未来 | 探索无服务器与人工智能的协同效应

近年来&#xff0c;无服务器计算和人工智能深刻改变着应用程序的开发方式。 无服务器计算实现无需管理底层基础架构就能构建和运行应用程序&#xff0c;而人工智能则让应用程序依据数据和算例做出智能决策。借助云计算&#xff0c;开发者打开了一个应用程序开发、构建的全新世…