第 10 章 - Go语言字符串操作

在Go语言中,字符串是一个不可变的字节序列。Go语言提供了丰富的内置函数来处理字符串,同时标准库strings包也提供了大量的功能用于字符串的搜索、替换、分割等操作。接下来,我们将从字符串的定义、常用方法以及格式化等方面进行详细的讲解。

字符串的定义

在Go语言中,字符串是使用双引号 "" 包围的一系列字符。如果需要包含特殊字符,可以使用转义序列(如\n表示换行)。

go 复制代码
s := "Hello, world!"

字符串一旦创建就不能更改其内容,如果需要修改字符串,实际上是创建了一个新的字符串。

字符串的常用方法

1. 长度与访问
  • 获取字符串长度可以通过内置函数 len() 实现。
  • 访问字符串中的单个字符,可以使用索引方式 s[i],这将返回一个 byte 类型的值。注意,这种方式只适用于ASCII字符,对于多字节的Unicode字符,需要使用 rune 类型来正确地处理。
go 复制代码
s := "Hello"
fmt.Println(len(s)) // 输出 5
fmt.Println(s[0])   // 输出 72 (H的ASCII码)
2. 搜索与替换
  • strings.Index(s, substr) 返回子串 substr 在字符串 s 中首次出现的位置,如果未找到则返回 -1
  • strings.Replace(s, old, new, n) 将字符串 s 中前 nold 子串替换为 new,如果 n-1 则替换所有。
go 复制代码
import "strings"

s := "Hello, world! Hello, everyone!"
index := strings.Index(s, "world")
fmt.Println(index) // 输出 7

replaced := strings.Replace(s, "world", "Go", -1)
fmt.Println(replaced) // 输出 "Hello, Go! Hello, everyone!"
3. 分割与拼接
  • strings.Split(s, sep) 使用 sep 作为分隔符将字符串 s 分割成多个部分,并返回这些部分组成的切片。
  • strings.Join(a []string, sep string) 使用 sep 作为连接符将切片 a 中的所有元素连接成一个新的字符串。
go 复制代码
s := "apple,banana,grape"
items := strings.Split(s, ",")
fmt.Println(items) // 输出 [apple banana grape]

joined := strings.Join(items, " | ")
fmt.Println(joined) // 输出 apple | banana | grape

字符串的格式化

Go语言中的 fmt 包提供了强大的字符串格式化功能。常见的格式化动词包括 %v(默认格式)、%s(字符串)、%d(十进制整数)等。

go 复制代码
name := "Alice"
age := 30
fmt.Printf("Name: %s, Age: %d\n", name, age) // 输出 Name: Alice, Age: 30

结合源码分析

strings.Replace 函数为例,我们可以查看其源码来理解它是如何工作的。以下是简化版的实现:

go 复制代码
func Replace(s, old, new string, n int) string {
    if old == new || n == 0 {
        return s // 如果没有要替换的内容或替换次数为0,则直接返回原字符串
    }

    // 计算需要多少空间
    var size int
    m := Count(s, old) // 计算old在s中出现的次数
    if n < 0 || m < n {
        n = m
        size = len(s) + (len(new)-len(old))*m
    } else {
        size = len(s) + (len(new)-len(old))*n
    }
    if size == len(s) { // 如果不需要替换,直接返回原字符串
        return s
    }

    // 开始替换
    t := make([]byte, size)
    i, j := 0, 0
    for ; n > 0 && i <= len(s)-len(old); n-- {
        if got := s[i : i+len(old)]; got == old {
            copy(t[j:], new) // 替换为新字符串
            j += len(new)
            i += len(old)
        } else {
            t[j] = s[i]
            j++
            i++
        }
    }
    // 复制剩余的部分
    copy(t[j:], s[i:])
    return string(t)
}

这个函数首先检查是否有必要进行替换,然后计算替换后的新字符串所需的总长度,并分配相应大小的字节切片。之后通过循环逐个查找并替换旧字符串,直到达到指定的替换次数或遍历完整个原始字符串。最后,将剩余未处理的部分复制到新字符串中,并将其转换回字符串类型返回。

以上就是关于Go语言中字符串操作的基本介绍,希望对你有所帮助!

当然,我们可以进一步深入探讨一些高级特性和技巧,比如字符串的编码处理、正则表达式操作、以及一些性能优化建议等。

字符串的编码处理

在处理国际化的应用程序时,字符串的编码是一个重要的考虑因素。Go语言支持UTF-8编码,这是互联网上最常用的字符编码之一。以下是一些处理字符串编码的常见场景:

1. 转换编码

虽然Go语言默认使用UTF-8,但在某些情况下,你可能需要与其他编码(如GBK、Shift-JIS等)进行转换。这通常需要借助第三方库,如golang.org/x/text/encoding

go 复制代码
import (
    "golang.org/x/text/encoding/simplifiedchinese"
    "golang.org/x/text/transform"
    "io/ioutil"
)

func main() {
    gbksrc := []byte("你好,世界")
    e := simplifiedchinese.GBK.NewEncoder()
    utf8dst, _, _ := transform.Bytes(e, gbksrc)
    fmt.Println(string(utf8dst)) // 输出 "你好,世界" (UTF-8 编码)
}
2. 处理多字节字符

由于Go语言中的字符串是以字节形式存储的,因此在处理多字节字符(如中文、日文等)时需要特别小心。使用rune类型可以确保正确处理多字节字符。

go 复制代码
s := "你好,世界"
for index, runeValue := range s {
    fmt.Printf("%d\t%q\t%d\n", index, runeValue, runeValue)
}
// 输出:
// 0 '你' 20320
// 3 '好' 22909
// 6 ',' 12290
// 9 '世' 19990
// 12 '界' 30028

正则表达式操作

Go语言的regexp包提供了强大的正则表达式支持,可用于复杂的文本匹配和替换任务。

1. 基本匹配
go 复制代码
import "regexp"

func main() {
    pattern := `^\w+`
    str := "Hello, world!"
    matched, _ := regexp.MatchString(pattern, str)
    fmt.Println(matched) // 输出 true
}
2. 查找所有匹配项
go 复制代码
pattern := `\w+`
str := "Hello, world! Go is awesome."
re := regexp.MustCompile(pattern)
matches := re.FindAllString(str, -1)
fmt.Println(matches) // 输出 [Hello world Go is awesome]
3. 替换匹配项
go 复制代码
pattern := `\w+`
str := "Hello, world! Go is awesome."
re := regexp.MustCompile(pattern)
result := re.ReplaceAllString(str, "XXX")
fmt.Println(result) // 输出 XXX, XXX! XXX XXX XXX.

性能优化建议

当处理大量字符串数据时,性能优化非常重要。以下是一些建议:

1. 避免不必要的字符串拷贝

字符串是不可变的,每次修改都会创建一个新的字符串。如果频繁修改字符串,可以考虑使用bytes.Buffer来减少内存分配。

go 复制代码
var buffer bytes.Buffer
buffer.WriteString("Hello, ")
buffer.WriteString("world!")
fmt.Println(buffer.String()) // 输出 Hello, world!
2. 使用切片代替多次拼接

如果需要拼接多个字符串,可以先将它们存储在一个切片中,然后一次性使用strings.Join来拼接。

go 复制代码
parts := []string{"Hello", "world", "Go"}
result := strings.Join(parts, ", ")
fmt.Println(result) // 输出 Hello, world, Go
3. 预分配足够大的缓冲区

在处理大量数据时,预分配足够大的缓冲区可以减少内存分配和垃圾回收的开销。

go 复制代码
buf := make([]byte, 1024*1024) // 预分配1MB的缓冲区

结合实际案例

假设我们有一个日志文件,每行记录了用户的请求信息,格式如下:

复制代码
2024-01-01T12:00:00Z GET /api/v1/users HTTP/1.1 200 123
2024-01-01T12:00:01Z POST /api/v1/users HTTP/1.1 201 456

我们需要解析这些日志,提取出请求方法、路径和状态码,并统计每个路径的请求次数。

go 复制代码
package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

type LogEntry struct {
    Method  string
    Path    string
    Status  int
}

func parseLogLine(line string) (LogEntry, error) {
    parts := strings.Fields(line)
    if len(parts) < 6 {
        return LogEntry{}, fmt.Errorf("invalid log line: %s", line)
    }
    method := parts[1]
    path := parts[2]
    status, err := strconv.Atoi(parts[4])
    if err != nil {
        return LogEntry{}, err
    }
    return LogEntry{Method: method, Path: path, Status: status}, nil
}

func main() {
    file, err := os.Open("access.log")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    pathCounts := make(map[string]int)

    for scanner.Scan() {
        line := scanner.Text()
        entry, err := parseLogLine(line)
        if err != nil {
            fmt.Println("Error parsing log line:", err)
            continue
        }
        pathCounts[entry.Path]++
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error reading file:", err)
        return
    }

    for path, count := range pathCounts {
        fmt.Printf("Path: %s, Requests: %d\n", path, count)
    }
}

在这个示例中,我们读取日志文件的每一行,解析出请求方法、路径和状态码,并使用一个映射来统计每个路径的请求次数。这展示了如何在实际应用中使用字符串处理功能。

希望这些内容对你有帮助!

在Go语言中,字符串是一个不可变的字节序列。Go语言提供了丰富的内置函数来处理字符串,同时标准库strings包也提供了大量的功能用于字符串的搜索、替换、分割等操作。接下来,我们将从字符串的定义、常用方法以及格式化等方面进行详细的讲解。

字符串的定义

在Go语言中,字符串是使用双引号 "" 包围的一系列字符。如果需要包含特殊字符,可以使用转义序列(如\n表示换行)。

go 复制代码
s := "Hello, world!"

字符串一旦创建就不能更改其内容,如果需要修改字符串,实际上是创建了一个新的字符串。

字符串的常用方法

1. 长度与访问
  • 获取字符串长度可以通过内置函数 len() 实现。
  • 访问字符串中的单个字符,可以使用索引方式 s[i],这将返回一个 byte 类型的值。注意,这种方式只适用于ASCII字符,对于多字节的Unicode字符,需要使用 rune 类型来正确地处理。
go 复制代码
s := "Hello"
fmt.Println(len(s)) // 输出 5
fmt.Println(s[0])   // 输出 72 (H的ASCII码)
2. 搜索与替换
  • strings.Index(s, substr) 返回子串 substr 在字符串 s 中首次出现的位置,如果未找到则返回 -1
  • strings.Replace(s, old, new, n) 将字符串 s 中前 nold 子串替换为 new,如果 n-1 则替换所有。
go 复制代码
import "strings"

s := "Hello, world! Hello, everyone!"
index := strings.Index(s, "world")
fmt.Println(index) // 输出 7

replaced := strings.Replace(s, "world", "Go", -1)
fmt.Println(replaced) // 输出 "Hello, Go! Hello, everyone!"
3. 分割与拼接
  • strings.Split(s, sep) 使用 sep 作为分隔符将字符串 s 分割成多个部分,并返回这些部分组成的切片。
  • strings.Join(a []string, sep string) 使用 sep 作为连接符将切片 a 中的所有元素连接成一个新的字符串。
go 复制代码
s := "apple,banana,grape"
items := strings.Split(s, ",")
fmt.Println(items) // 输出 [apple banana grape]

joined := strings.Join(items, " | ")
fmt.Println(joined) // 输出 apple | banana | grape

字符串的格式化

Go语言中的 fmt 包提供了强大的字符串格式化功能。常见的格式化动词包括 %v(默认格式)、%s(字符串)、%d(十进制整数)等。

go 复制代码
name := "Alice"
age := 30
fmt.Printf("Name: %s, Age: %d\n", name, age) // 输出 Name: Alice, Age: 30

结合源码分析

strings.Replace 函数为例,我们可以查看其源码来理解它是如何工作的。以下是简化版的实现:

go 复制代码
func Replace(s, old, new string, n int) string {
    if old == new || n == 0 {
        return s // 如果没有要替换的内容或替换次数为0,则直接返回原字符串
    }

    // 计算需要多少空间
    var size int
    m := Count(s, old) // 计算old在s中出现的次数
    if n < 0 || m < n {
        n = m
        size = len(s) + (len(new)-len(old))*m
    } else {
        size = len(s) + (len(new)-len(old))*n
    }
    if size == len(s) { // 如果不需要替换,直接返回原字符串
        return s
    }

    // 开始替换
    t := make([]byte, size)
    i, j := 0, 0
    for ; n > 0 && i <= len(s)-len(old); n-- {
        if got := s[i : i+len(old)]; got == old {
            copy(t[j:], new) // 替换为新字符串
            j += len(new)
            i += len(old)
        } else {
            t[j] = s[i]
            j++
            i++
        }
    }
    // 复制剩余的部分
    copy(t[j:], s[i:])
    return string(t)
}

这个函数首先检查是否有必要进行替换,然后计算替换后的新字符串所需的总长度,并分配相应大小的字节切片。之后通过循环逐个查找并替换旧字符串,直到达到指定的替换次数或遍历完整个原始字符串。最后,将剩余未处理的部分复制到新字符串中,并将其转换回字符串类型返回。

我们可以进一步深入探讨一些高级特性和技巧,比如字符串的编码处理、正则表达式操作、以及一些性能优化建议等。

字符串的编码处理

在处理国际化的应用程序时,字符串的编码是一个重要的考虑因素。Go语言支持UTF-8编码,这是互联网上最常用的字符编码之一。以下是一些处理字符串编码的常见场景:

1. 转换编码

虽然Go语言默认使用UTF-8,但在某些情况下,你可能需要与其他编码(如GBK、Shift-JIS等)进行转换。这通常需要借助第三方库,如golang.org/x/text/encoding

go 复制代码
import (
    "golang.org/x/text/encoding/simplifiedchinese"
    "golang.org/x/text/transform"
    "io/ioutil"
)

func main() {
    gbksrc := []byte("你好,世界")
    e := simplifiedchinese.GBK.NewEncoder()
    utf8dst, _, _ := transform.Bytes(e, gbksrc)
    fmt.Println(string(utf8dst)) // 输出 "你好,世界" (UTF-8 编码)
}
2. 处理多字节字符

由于Go语言中的字符串是以字节形式存储的,因此在处理多字节字符(如中文、日文等)时需要特别小心。使用rune类型可以确保正确处理多字节字符。

go 复制代码
s := "你好,世界"
for index, runeValue := range s {
    fmt.Printf("%d\t%q\t%d\n", index, runeValue, runeValue)
}
// 输出:
// 0 '你' 20320
// 3 '好' 22909
// 6 ',' 12290
// 9 '世' 19990
// 12 '界' 30028

正则表达式操作

Go语言的regexp包提供了强大的正则表达式支持,可用于复杂的文本匹配和替换任务。

1. 基本匹配
go 复制代码
import "regexp"

func main() {
    pattern := `^\w+`
    str := "Hello, world!"
    matched, _ := regexp.MatchString(pattern, str)
    fmt.Println(matched) // 输出 true
}
2. 查找所有匹配项
go 复制代码
pattern := `\w+`
str := "Hello, world! Go is awesome."
re := regexp.MustCompile(pattern)
matches := re.FindAllString(str, -1)
fmt.Println(matches) // 输出 [Hello world Go is awesome]
3. 替换匹配项
go 复制代码
pattern := `\w+`
str := "Hello, world! Go is awesome."
re := regexp.MustCompile(pattern)
result := re.ReplaceAllString(str, "XXX")
fmt.Println(result) // 输出 XXX, XXX! XXX XXX XXX.

性能优化建议

当处理大量字符串数据时,性能优化非常重要。以下是一些建议:

1. 避免不必要的字符串拷贝

字符串是不可变的,每次修改都会创建一个新的字符串。如果频繁修改字符串,可以考虑使用bytes.Buffer来减少内存分配。

go 复制代码
var buffer bytes.Buffer
buffer.WriteString("Hello, ")
buffer.WriteString("world!")
fmt.Println(buffer.String()) // 输出 Hello, world!
2. 使用切片代替多次拼接

如果需要拼接多个字符串,可以先将它们存储在一个切片中,然后一次性使用strings.Join来拼接。

go 复制代码
parts := []string{"Hello", "world", "Go"}
result := strings.Join(parts, ", ")
fmt.Println(result) // 输出 Hello, world, Go
3. 预分配足够大的缓冲区

在处理大量数据时,预分配足够大的缓冲区可以减少内存分配和垃圾回收的开销。

go 复制代码
buf := make([]byte, 1024*1024) // 预分配1MB的缓冲区

结合实际案例

假设我们有一个日志文件,每行记录了用户的请求信息,格式如下:

复制代码
2024-01-01T12:00:00Z GET /api/v1/users HTTP/1.1 200 123
2024-01-01T12:00:01Z POST /api/v1/users HTTP/1.1 201 456

我们需要解析这些日志,提取出请求方法、路径和状态码,并统计每个路径的请求次数。

go 复制代码
package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

type LogEntry struct {
    Method  string
    Path    string
    Status  int
}

func parseLogLine(line string) (LogEntry, error) {
    parts := strings.Fields(line)
    if len(parts) < 6 {
        return LogEntry{}, fmt.Errorf("invalid log line: %s", line)
    }
    method := parts[1]
    path := parts[2]
    status, err := strconv.Atoi(parts[4])
    if err != nil {
        return LogEntry{}, err
    }
    return LogEntry{Method: method, Path: path, Status: status}, nil
}

func main() {
    file, err := os.Open("access.log")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    pathCounts := make(map[string]int)

    for scanner.Scan() {
        line := scanner.Text()
        entry, err := parseLogLine(line)
        if err != nil {
            fmt.Println("Error parsing log line:", err)
            continue
        }
        pathCounts[entry.Path]++
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error reading file:", err)
        return
    }

    for path, count := range pathCounts {
        fmt.Printf("Path: %s, Requests: %d\n", path, count)
    }
}

在这个示例中,我们读取日志文件的每一行,解析出请求方法、路径和状态码,并使用一个映射来统计每个路径的请求次数。这展示了如何在实际应用中使用字符串处理功能。

我们可以继续深入探讨更多关于Go语言字符串操作的高级特性和最佳实践。接下来,我们将讨论一些更复杂的字符串处理场景,例如字符串的高效拼接、字符串的并行处理、以及字符串的国际化和本地化等。

字符串的高效拼接

在Go语言中,字符串是不可变的,这意味着每次拼接操作都会创建一个新的字符串。对于少量的拼接操作,这通常不是问题,但对于大量拼接操作,这种做法会导致大量的内存分配和垃圾回收开销。为了提高性能,可以使用以下几种方法:

1. 使用 strings.Builder

strings.Builder 是一个高效的字符串拼接工具,适用于需要多次拼接的场景。它内部使用一个可变的字节切片来存储数据,避免了频繁的内存分配。

go 复制代码
package main

import (
    "fmt"
    "strings"
)

func main() {
    var b strings.Builder
    b.WriteString("Hello, ")
    b.WriteString("world!")
    result := b.String()
    fmt.Println(result) // 输出 Hello, world!
}
2. 使用 bytes.Buffer

bytes.Buffer 也是一个高效的字符串拼接工具,它不仅支持字符串操作,还支持IO操作,适合更复杂的场景。

go 复制代码
package main

import (
    "bytes"
    "fmt"
)

func main() {
    var b bytes.Buffer
    b.WriteString("Hello, ")
    b.WriteString("world!")
    result := b.String()
    fmt.Println(result) // 输出 Hello, world!
}

字符串的并行处理

在处理大规模数据时,可以利用并行处理来提高性能。Go语言的并发模型(goroutines 和 channels)非常适合这种场景。

1. 并行处理字符串

假设我们需要对一个大型字符串数组进行处理,可以使用 goroutines 来并行处理每个字符串。

go 复制代码
package main

import (
    "fmt"
    "strings"
    "sync"
)

func processString(s string) string {
    return strings.ToUpper(s)
}

func main() {
    data := []string{"hello", "world", "go", "is", "awesome"}
    var wg sync.WaitGroup
    var mu sync.Mutex
    results := make([]string, len(data))

    for i, s := range data {
        wg.Add(1)
        go func(i int, s string) {
            defer wg.Done()
            result := processString(s)
            mu.Lock()
            results[i] = result
            mu.Unlock()
        }(i, s)
    }

    wg.Wait()
    fmt.Println(results) // 输出 [HELLO WORLD GO IS AWESOME]
}

字符串的国际化和本地化

在开发国际化应用时,处理不同语言和地区的字符串是一个重要任务。Go语言提供了一些工具和库来帮助实现这一点。

1. 使用 fmt.Sprintf 进行格式化

fmt.Sprintf 支持多种格式化选项,可以用于生成不同语言的字符串。

go 复制代码
package main

import (
    "fmt"
)

func main() {
    name := "Alice"
    age := 30
    fmt.Println(fmt.Sprintf("Name: %s, Age: %d", name, age)) // 输出 Name: Alice, Age: 30
}
2. 使用 i18n

Go语言社区有一些流行的国际化库,如 github.com/nicksnyder/go-i18n/v2/i18n,可以帮助你管理多语言资源。

go 复制代码
package main

import (
    "github.com/nicksnyder/go-i18n/v2/i18n"
    "golang.org/x/text/language"
)

func main() {
    bundle := i18n.NewBundle(language.English)
    bundle.LoadMessageFile("messages.en.json")

    localizer := i18n.NewLocalizer(bundle, "en")

    name := "Alice"
    age := 30
    message := localizer.MustLocalize(&i18n.LocalizeConfig{
        DefaultMessage: &i18n.Message{
            ID:    "Greeting",
            Other: "Name: {{.Name}}, Age: {{.Age}}",
        },
        TemplateData: map[string]interface{}{
            "Name": name,
            "Age":  age,
        },
    })

    fmt.Println(message) // 输出 Name: Alice, Age: 30
}

字符串的加密和解密

在处理敏感信息时,字符串的加密和解密是一个重要环节。Go语言的 crypto 包提供了多种加密算法的支持。

1. 使用 crypto/aes 进行AES加密
go 复制代码
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "io"
)

func encrypt(key, text []byte) (string, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }

    ciphertext := make([]byte, aes.BlockSize+len(text))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return "", err
    }

    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(ciphertext[aes.BlockSize:], text)

    return base64.URLEncoding.EncodeToString(ciphertext), nil
}

func decrypt(key, ciphertext []byte) (string, error) {
    ciphertext, err := base64.URLEncoding.DecodeString(string(ciphertext))
    if err != nil {
        return "", err
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }

    if len(ciphertext) < aes.BlockSize {
        return "", fmt.Errorf("ciphertext too short")
    }

    iv := ciphertext[:aes.BlockSize]
    ciphertext = ciphertext[aes.BlockSize:]

    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(ciphertext, ciphertext)

    return string(ciphertext), nil
}

func main() {
    key := []byte("thisisaverysecretkey")
    plaintext := []byte("Hello, world!")

    encrypted, err := encrypt(key, plaintext)
    if err != nil {
        fmt.Println("Encryption error:", err)
        return
    }
    fmt.Println("Encrypted:", encrypted)

    decrypted, err := decrypt(key, []byte(encrypted))
    if err != nil {
        fmt.Println("Decryption error:", err)
        return
    }
    fmt.Println("Decrypted:", decrypted) // 输出 Hello, world!
}

总结

通过上述内容,我们详细探讨了Go语言中字符串操作的各种高级特性和最佳实践,包括高效的字符串拼接、并行处理、国际化和本地化、以及加密和解密等。希望这些内容能够帮助你在实际项目中更好地处理字符串相关的问题。以上就是关于Go语言中字符串操作的基本介绍,希望对你有所帮助!

相关推荐
一只叫煤球的猫4 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9655 小时前
tcp/ip 中的多路复用
后端
bobz9655 小时前
tls ingress 简单记录
后端
皮皮林5516 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友6 小时前
什么是OpenSSL
后端·安全·程序员
bobz9656 小时前
mcp 直接操作浏览器
后端
前端小张同学9 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook9 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康10 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在10 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net