SwiftUI 实现一个 iOS 上 Files App 兼容的文件资源管理器

news2025/1/23 7:20:10

在这里插入图片描述

功能需求

在 SwiftUI 中自己白手起家写一个 iOS(或iPadOS)上迷你的文件资源管理器是有些难度滴,不过从 iOS 11 (2017年) 官方引入自家的 Files App 之后,我们就可以借助它的魔力轻松完成这一个功能了。

在这里插入图片描述

如上所示,我们使用 SwiftUI 原生功能完成了一个小巧的 iOS Files App 文件管理器,实现了 iOS 中 Files App 中文件的导入、导出、移动和删除等功能。

在本篇博文中,您将学到如下内容:

  • 如何在 App 中注册自定义文件类型?
  • 如何在 SwiftUI 中的 ForEach 循环中遍历异构数据集合([any])?
  • 如何在 SwiftUI 中导入、导出、移动以及删除文件?

请小伙伴们在飞行舱中稍事休息,本次航程将精彩纷呈!

Let‘s go!!!😉


功能分析

1. 注册自定文件类型

如果我们 App 需要处理自定义文件,则需要在 Xcode 项目中注册该文件类型。

比如,我们希望实现一种自定义的形状(Shape)文件类型,供 App 导入导出:

在这里插入图片描述

首先,我们需要定义该文件的 UTType:

import UniformTypeIdentifiers

extension UTType {
    static let shapeFile = UTType(importedAs: "com.hopy.Shapes")
}

接着,在 Xcode 中选中 App 项目的 info 选项卡,并展开底部的 Imported Type Identifiers 子项,并填入对应的文件类型信息:

在这里插入图片描述

其中有几点需要注意:

  • Identifier 是我们之前创建的 UTType 类型:com.hopy.Shapes
  • Conforms to 填入的是 public.data 类型。因为我们希望该文件以 Data 的方式被读写,你也可以使用其它通用类型。

为了满足后面组成异构文件集合的需求,我们需要创建 IdentifiableFile 协议,以支持多个异构文件类型:

protocol IdentifiableFile: Identifiable, FileDocument {
    // 影子ID(shadow ID),后面会介绍其用途
    var sid: String { get }
    var url: URL? { get set }
    var fileName: String { get set }
}

最后,我们可以实现 Shape 文件结构的主体了:

struct ShapeFile: IdentifiableFile, Codable {
    
    enum ShapeColor: String, Codable, CaseIterable {
        case red, blue, green, yellow, gray
        
        var drawColor: Color {
            switch self {
            case .red:
                return .red
            case .yellow:
                return .yellow
            case .green:
                return .green
            case .blue:
                return .blue
            case .gray:
                return .gray
            }
        }
    }
    
    enum ShapeType: Codable, CaseIterable {
        case rect, circle, capsule
        
        var name: String {
            switch self {
            case .rect:
                return "矩形"
            case .circle:
                return "圆形"
            case .capsule:
                return "胶囊"
            }
        }
    }
    
    var id = UUID()
    var sid: String { id.uuidString }
    var fileName: String
    var url: URL?
    var title = "Untitled"
    var type = ShapeType.rect
    var color = ShapeColor.red
    
    init(fileName: String, title: String = "", type: ShapeType = .rect, color: ShapeColor = .red) {
        self.fileName = fileName
        self.title = title
        self.type = type
        self.color = color
    }
    
    @ViewBuilder static func draw(type: ShapeType, color: Color) -> some View {
        switch type {
        case .rect:
            Rectangle()
                .foregroundStyle(color.gradient)
        case .circle:
            Circle()
                .foregroundStyle(color.gradient)
        case .capsule:
            Capsule()
                .foregroundStyle(color.gradient)
        }
    }
}

extension ShapeFile: FileDocument {
	// 待实现
}

为了支持 SwiftUI 中的文件操作,我们需要自定义文件类型遵守 FileDocument 协议,相关实现将在后面详述。

2. 创建异构文件集合类型

除了自定义 Shape 文件类型以外,我们还想支持普通的文本(Text)文件类型,于是需要再创建一个类似的 TextFile 文件结构:

struct TextFile: IdentifiableFile, Equatable {
    var id = UUID()
    var sid: String { id.uuidString }
    
    var url: URL?
    var fileName: String
    var text = ""
    
    init(fileName: String, text: String) {
        self.fileName = fileName
        self.text = text
    }
    
    static func ==(lhs: TextFile, rhs: TextFile) -> Bool {
        lhs.url == rhs.url
    }
    
    static var stub: Self {
        TextFile(fileName: "无名文件", text: "Empty File")
    }
}

extension TextFile: FileDocument {
	// 待实现
}

此时,有了两种不同的文件类型,我们可以将它们放在异构集合中以便统一操作:

let someFiles: [any IdentifiableFile] = [
    TextFile(fileName: "txt", text: "hello world!"),
    ShapeFile(fileName: "shape"),
]

如上,我们使用异构集合来存放不同种类的文件,注意集合的类型是 [any IdentifiableFile] 。


想了解更多 Swift 5.5 后新引入的 some,any 关键字以及主关联类型知识的小伙伴们,请猛戳以下链接观赏:

  • 深入浅出 Swift 中的 some、any 关键字以及主关联类型(primary associated types)

3. 在 SwiftUI 的 ForEach 中遍历异构集合并显示

我们可能会这样在 SwiftUI 中遍历上面的异构文件集合,试图逐一在 List 中显示它们:


struct ContentView: View {
    
    @State private var files = someFiles

    var body: some View {
        List {
            // 如果我们希望在 FileCell 中修改 file 的内容,则需要使用 files 集合绑定:
            /* ForEach($files) { $file in
                    FileCell($file)
                }
            }*/
            ForEach(files){ file in
                FileCell(file: file)
            }
        }
    }
}

不幸的是,这样做无法通过编译:

在这里插入图片描述

通过前面代码可以确认,我们的文件类型绝对是遵守 Identifiable 协议的,但异构 any IdentifiableFile 类型却“不吃这一套”,编译器会认为 any Identifiable 不遵守 Identifiable。

所幸的是,我们可以手动让 ForEach 明白 Identifiable 的“真谛”:

在这里插入图片描述

上面的 sid 是之前实现的“影子id”属性,我们利用它来满足 ForEach 对 Identifiable 的渴望,它的类型必须为结构(Struct)。


注意:这里我们不能用前面 Identifiable 协议中定义的 id 属性,因为这违反了 id 的 Self 类型必须是类(Class)这一条件!
在这里插入图片描述


现在,我们可以用 ForEach 遍历 [any IdentifiableFile] 集合了,但如何处理传入 FileCell 中的 file (其类型为 any IdentifiableFile)对象呢?

很简单!我们可以在操作 file 潜在的真实对象之前,先对其解包(Unwrap),把 any IdentifiableFile 转换为实际的文件对象类型后再访问它:

struct FileCell: View {
    let file: any IdentifiableFile
    @State private var urlExpanding = false
    
    private var isShapeFile: Bool {
        if let _ = file as? ShapeFile {
            return true
        }
        return false
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            HStack {
                
                Image(systemName: isShapeFile ? "hexagon" : "doc.text")
                    .foregroundStyle(.blue.gradient)
                
                Text(file.fileName)
                    .font(.title2.bold())
                
                Spacer()
                
                if let shapeFile = file as? ShapeFile, !shapeFile.title.isEmpty {
                    Text("#\(shapeFile.title)#")
                        .font(.subheadline)
                        .foregroundStyle(.gray.gradient)
                }
            }
            
            Button(action: {
                urlExpanding.toggle()
            }){
                Text(file.url?.absoluteString ?? "<还未导出>")
                    .font(.headline)
                    .lineLimit(urlExpanding ? nil : 2)
                    .multilineTextAlignment(.leading)
            }
            .buttonStyle(.borderless)
            .tint(.gray.opacity(0.88))
        }
    }
}

4. 导入、导出、移动以及删除文件

现在 SwiftUI 中的 ForEach 已经可以遍历和显示异构文件集合的对象了,接下来就让我们来逐一实现文件的导入、导出、移动以及删除操作吧。

4.1 文件导入

从 SwiftUI 2.0 开始,Apple 引入了新的 fileImporter() 修改器方法,专门用来将 Files App 中的文件导入到我们自己的 App 中去。

那么,Files App 中哪些文件对外可见呢?主要是以下几种:

  1. iCloud 云中的文件;
  2. 设备中其它 App 中可供访问的文件(比如:在 Documents 目录中,并允许外部发现的文件);
  3. 设备中其它文件资源 App 可对外访问的文件,比如 百度网盘,钉钉 中的文件;
  4. 共享的文件(比如多 iCloud 用户间共享的文件,或 共享服务器 中的文件)

我们可以在同一个 fileImporter() 方法中选择导入多种不同文件类型:

struct ContentView: View {
    
    @State private var files = [any IdentifiableFile]()
    @State private var importing = false

	private func isFileExist(_ url: URL) -> Bool {
        files.contains { $0.url == url }
    }

    var body: some View {
        List {
            ForEach(files){ file in
                FileCell(file)
            }
        }
        .fileImporter(isPresented: $importing, allowedContentTypes: [.plainText, .text, .shapeFile]){ result in
            switch result {
            case .success(let url):
                
                guard !isFileExist(url) else {
                    msg = "相同文件 \(url.lastPathComponent) 已存在!!!"
                    return
                }
                
                Task.detached {
                    do {
                        let data = try await dataFromStream(url: url)
                        let decoder = JSONDecoder()
                        if var shapeFile = try? decoder.decode(ShapeFile.self, from: data) {
                            
                            // 需要设置一个不同的 id
                            shapeFile.id = UUID()
                            shapeFile.url = url
                            let tmp = shapeFile
                            
                            await MainActor.run {
                                files.append(tmp)
                            }
                        } else {
                            let text = String(data: data, encoding: .utf8) ?? ""
                            var textFile = TextFile(fileName: url.lastPathComponent, text: text)
                            textFile.url = url
                            let tmp = textFile
                            await MainActor.run {
                                files.append(tmp)
                            }
                        }
                    }catch{
                        await MainActor.run {
                            // 设置 msg 以便弹出 Alert 通知用户错误,实现从略...
                            msg = "ERR: \(error.localizedDescription)"
                        }
                    }
                }
                
            case .failure(let error):
                print("ERR: \(error)")
            }
        }
    }
}

在这里插入图片描述

在这里插入图片描述

如上,我们分别导入了 Text 和 Shape 两种不同文件。

4.2 FileDocument 协议

在实现文件导出之前,我们需要让自定义文件类型遵守 FileDocument 协议,其中要做 3 件事:

  1. 确定文件的 UTType;
  2. 实现 init(configuration: ReadConfiguration) 构造器去完成文件的读取操作;
  3. 实现 fileWrapper(configuration: WriteConfiguration) 方法去完成文件的保存操作;

同步读取大文件内容会造成界面的挂起,如果小伙伴们想了解大文件异步快速读取的知识,请移步如下链接观赏:

  • Swift 如何闪电般异步读取大文件?

以下是 ShapeFile 结构遵守 FileDocument 协议的实现:

enum AppError: Error {
    case illegalFormat
}

extension ShapeFile: FileDocument {
    static var readableContentTypes: [UTType] {
        [.shapeFile]
    }
    
    init(configuration: ReadConfiguration) throws {
        if let data = configuration.file.regularFileContents {
            let tmp = try JSONDecoder().decode(Self.self, from: data)
            self = tmp
        }else{
            throw AppError.illegalFormat
        }
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = try JSONEncoder().encode(self)
        let fw = FileWrapper(regularFileWithContents: data)
        fw.filename = fileName
        return fw
    }
}

可以看到,遵守 FileDocument 协议很容易,我们如法炮制完成 TextFile 的协议“契约”:

extension TextFile: FileDocument {
    static var readableContentTypes: [UTType] {
        [.plainText, .text]
    }
    
    init(configuration: ReadConfiguration) throws {
        if let data = configuration.file.regularFileContents {
            guard let fileName = configuration.file.filename else {
                throw AppError.illegalFormat
            }
            
            self.fileName = fileName
            text = String(decoding: data, as: UTF8.self)
        }else{
            throw AppError.illegalFormat
        }
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8) ?? Data()
        let fw = FileWrapper(regularFileWithContents: data)
        fw.filename = fileName
        return fw
    }
}

注意:如果不希望导出到 Files App 中的文件是默认名称,我们必须在 fileWrapper(…) 方法中为 FileWrapper 对象正确设置其 filename 属性的值。

4.3 文件导出

现在,我们可以将遵守 FileDocument 协议的文件对象导出到 Files App 中去了。

同文件导入类似,我们可以使用 fileExporter() 修改器方法来完成文件的导出操作。不过 fileExporter() 方法仅支持单一种类文件类型的导出,对于我们的 App 来说,必须使用两个 fileExporter() 方法来分别支持 Text 和 Shape 文件的导出。

为了方便起见,我们定义了一个 FileProxy 结构来适配 fileExporter() 方法的调用:

struct FileProxy<File: IdentifiableFile> {
	// 是否执行文件导出(弹出文件导出窗口)
    var execute = false
    // 被导出的文件类型
    var file: File?
}

在下面的代码中,我们逐一实现了 TextFile 和 ShapeFile 文件的导出功能,并在文件成功导出后,在文件对象中存放其对应的保存位置(URL)以供后续使用:

struct ContentView: View {
    
    @State private var files = [any IdentifiableFile]()
    @State private var exportingTextProxy = FileProxy<TextFile>()
    @State private var exportingShapeProxy = FileProxy<ShapeFile>()


    var body: some View {
        List {
            ForEach(files){ file in
                FileCell(file)
            }
        }
        .fileExporter(isPresented: $exportingTextProxy.execute, document: exportingTextProxy.file, contentType: .plainText) { result in
            switch result {
            case .success(let url):
                guard let file = exportingTextProxy.file else {return}
                
                if file.url == nil {
                    let idx = files.firstIndex { $0.sid == file.sid }!
                    files[idx].url = url
                }
                
                msg = "文件 \(file.fileName) 导出成功 (->\(url.absoluteString))!"
            case .failure(let error):
                msg = error.localizedDescription
            }
        }
        .fileExporter(isPresented: $exportingShapeProxy.execute, document: exportingShapeProxy.file, contentType: .shapeFile) { result in
            switch result {
            case .success(let url):
                guard let file = exportingShapeProxy.file else {return}
                
                if file.url == nil {
                    let idx = files.firstIndex { $0.sid == file.sid }!
                    files[idx].url = url
                }
                
                msg = "文件 \(file.fileName) 导出成功 (->\(url.absoluteString))!"
            case .failure(let error):
                msg = error.localizedDescription
            }
        }
    }
}

在这里插入图片描述

4.4 文件移动

相对文件导出而言,文件移动就简单很多了。

为了移动(Files App里)指定路径中的文件,我们只需要使用文件保存位置的 URL 地址即可:

struct ContentView: View {
    
    @State private var files = [any IdentifiableFile]()
    @State private var moving = false
    @State private var movingFileURL = URL(filePath: "")


    var body: some View {
        List {
            ForEach(files){ file in
                FileCell(file)
            }
        }
        .fileMover(isPresented: $moving, file: movingFileURL) { result in
            switch result {
            case .success(let dstURL):
                let idx = files.firstIndex { $0.url == movingFileURL }!
                files[idx].url = dstURL
                msg = "文件 \(movingFileURL.lastPathComponent) 已移动到 \(dstURL)!"
            case .failure(let error):
                msg = error.localizedDescription
            }
        }
    }
}

注意在以上代码中,我们同样在文件成功移动后更新了它原有 url 属性值为新的路径。

在这里插入图片描述

4.5 文件删除

你可能会猜测,文件删除也有一个类似 fileDeleter() 的修改器方法…

答案是:你想多了… 😃

对于文件删除操作,只需用我们的老朋友 FileManager 中的 removeItem 方法即可:

private let fm = FileManager.default

List {
    ForEach($files, id: \.sid) { $file in
        NavigationLink(destination: {
            FileDetailsView(file: $file)
        }){
            FileCell(file: file)
                .swipeActions {
                    if let url = file.url {
                        Button("移动", action: {
                            movingFileURL = url
                            moving = true
                        })
                        .tint(.blue)
                    }
                    
                    Button("导出", action: {
                        if let textFile = file as? TextFile {
                            exportingTextProxy.file = textFile
                            exportingTextProxy.execute = true
                            
                        }else if let shapeFile = file as? ShapeFile {
                            exportingShapeProxy.file = shapeFile
                            exportingShapeProxy.execute = true
                        }
                        
                        
                    })
                    .tint(.orange)
                    
                    Button("删除", role: .destructive){
                        do {
                            if let url = file.url {
                                try fm.removeItem(at: url)
                            }
                            
                            files.removeAll {$0.sid == file.sid}
                        }catch{
                            msg = "删除文件失败: \(error.localizedDescription)"
                        }
                    }
                }
        }
    }
}

注意在以上代码中,我们顺面补全了前面文件导出和移动操作中缺失的代码片段。

在这里插入图片描述


其实,我们也可以直接在 Files App 弹出的文件操作窗口中完成文件的删除、重命名、共享等操作:

在这里插入图片描述


调用 FileManager#removeItem() 方法后,Files App 里存储路径对应的文件立即“灰飞烟灭”,童叟无欺!

5. 如何解决目前 SwiftUI 文件操作的一些小怪癖?

在以上代码中,我们分别在 SwiftUI 中实现了文件的导入、导出和移动等操作。

这看似很好很和谐,不过如果在同一个 View 中串行调用上述这些文件操作对应的修改器方法时,则会让秃头码农们“欲哭无泪”:总有些文件操作窗口无法弹出,具体哪些窗口弹出失灵则和这些修改器的顺序有关:

List {
    ForEach($files, id: \.sid) { $file in
        NavigationLink(destination: {
            FileDetailsView(file: $file)
        }){
            FileCell(file: file)
                .swipeActions {...}
        }
    }
}
.fileMover(...) {...}
.fileExporter(...) {...}    // TextFile 导出
.fileExporter(...) {...}    // ShapeFile 导出
.fileImporter(...) {...}

如上代码所示,无论我们怎么调整这些文件操作修改器的相对顺序,总有些文件操作窗口无法被弹出。

为什么会这样呢?

答案是:这应该是 SwiftUI 中的一个“怪癖”!说明 SwiftUI 文件操作功能未经严格测试就拿来给我等“小白鼠”使用,这也是 SwiftUI 目前还不能完全实现商业软件开发的佐证吧!

虽然不能在同一个 View 上串行调用这些文件操作修改器,我们也不是完全没有办法,一种解决方法是将不同的文件操作修改器方法放在不同的视图(View)上:

NavigationStack {
    ZStack {
        Text("")
            .frame(size: .zero)
            .hidden()
            // .fileMover 不能和其它 fileXXX 修改器方法放在一起,否则依照它们之间的排放顺序,总会有几个修改器方法无法生效。
            .fileMover(...) { result in
                ...
            }
            
        
        Text("")
            .frame(size: .zero)
            .hidden()
            .fileExporter(...) { result in
                ...
            }
            
        Text("")
            .frame(size: .zero)
            .hidden()
            .fileExporter() { result in
                ...
            }

        List {
            ForEach($files, id: \.sid) { $file in
                NavigationLink(destination: {
                    FileDetailsView(file: $file)
                }){
                    FileCell(file: file)
                }
            }
        }
        .fileImporter(...){ result in
            ...
        }
    }
    .navigationTitle("文件管理器")
}

至此,我们在自己的 App 中实现了以上全部的文件导入、导出、移动和删除功能,棒棒哒!!!💯🚀

尾声

源代码哪里寻?

因为全部源代码较多,这里不便贴出。

不过,如果您是我本系列博文专栏的订阅读者,可以私信我免费获取完整源代码。

总结

在本篇博文中,我们使用 SwiftUI 完成了一个 iOS(iPadOS类似)中的文件资源管理器,其中逐一实现了 Files App 里文件的导入、导出、移动和删除等操作。

那么,最后还得照例问一下小伙伴:你们学会了么?😎


结束语

Hi,我是大熊猫侯佩,一名非自由App开发者,希望我的文章可以解决你的痛点、难点问题。

如果还有问题欢迎在下面一起讨论吧 😉

感谢观赏,再会。

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

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

相关文章

防抖和节流 含义及区别图文详解秒懂

防抖和节流都是为解决短时间内频繁触发某个功能函数而导致的性能问题。比如&#xff0c;触发频率过高而导致响应速度跟不上&#xff0c;以致出现延迟&#xff0c;假死或卡顿的现象。 防抖 图解&#xff1a;一件事情&#xff0c;计划5s以后触发&#xff0c;结果中途意外触发了…

一款可以自动写代码的编辑器,解放你的双手

Cursor 是集成了 GPT-4 的 IDE 工具&#xff0c;目前免费并且无需 API Key&#xff0c;支持 Win、Mac、Linux 平台&#xff0c;可以按要求生成代码&#xff0c;或者让 AI 帮助优化代码&#xff0c;分析代码。Cursor目前已经集成了openai的GPT-4&#xff0c;它或将彻底改变我们写…

gdb调试 与 coredump

gdb调试 与 coredump调试 1. 启动gdb2.gdb中的相关命令3. coredump调试&#xff08;附属于gdb调试中一种&#xff0c;当程序出现错误时&#xff0c;会使用coredump调试&#xff09;1&#xff09;coredump是什么&#xff1f;2&#xff09;前期设置3&#xff09;什么情况下会导致…

JavaEE进阶5/25

1.五大类注解详解&#xff08;重点 1.Controller 控制器&#xff0c;用于业务逻辑层&#xff0c;来控制用户的行为。它用来检查用户参数的有效性。 当用户的参数有效的话会继续分发到服务层。controller可以理解为程序的安保系统 2.Service 服务层。归属服务层&#xff0c;调用…

MySQL---SQL优化上(explain分析执行计划、查看SQL的执行效率、定位低效率SQL)

1. 查看SQL的执行效率 MySQL 客户端连接成功后&#xff0c;通过 show [session|global] status 命令可以查看服务器状态信息。通 过查看状态信息可以查看对当前数据库的主要操作类型。 --下面的命令显示了当前 session 中所有统计参数的值 show session status like Com____…

【滴水逆向P77】加载进程(PE查看器)应用程序源码解析

在上一篇文章中讲解了通用控件&#xff0c;做了一个基本的加载进程&#xff08;PE查看器&#xff09;的应用程序项目&#xff0c;Win32通用控件&#xff0c;加载进程&#xff08;PE查看器&#xff09;项目初步&#xff0c;大家如果有不懂的可以去看看&#xff0c;由于不是很了解…

如何搭建一个高效、可靠的积分商城系统?

互联网购物的普及&#xff0c;积分商城系统已经成为商家和消费者之间互动的一种常见方式。它不仅可以帮助商家增加品牌影响力&#xff0c;还可以提高顾客体验&#xff0c;从而增加销售额。下面就如何搭建一个高效、可靠的积分商城系统作一些简单介绍。 第一步&#xff1a;确定需…

MyBatis源码学习三之查询主逻辑

MyBatis源码学习三之查询主逻辑 继续上一章节。 MyBatis的一个主要流程图。从图中可以看出&#xff0c;核心的东西主要集中在3个Handler中。分别是入参处理&#xff0c;执行sql语句处理&#xff0c;以及返回结果处理。 一 实例 Test public static void main(String[] args…

Revit建模|10种方法帮你解决Revit操作卡顿!

大家好&#xff0c;这里是建模助手。 相信各位BIMer在使用Revit建模时&#xff0c;肯定遇到过软件加载慢或者程序未响应的现象。我们经过测试发现&#xff0c;除了硬件配置及软件本身的问题以外&#xff0c;建模习惯及软件使用方法不当也会造成软件卡顿。 以下就是我们总结的…

TPlinker解读

参考&#xff1a; 关系抽取之TPLinker解读加源码分析 TPLinker 实体关系抽取代码解读 实体关系联合抽取&#xff1a;TPlinker TPLinker中文注释版 Tagging TPLinker模型需要对关系三元组(subject, relation, object)进行手动Tagging&#xff0c;过程分为三部分&#xff1a; &…

阿里面试,HR说我不配21K,直接翻脸.....

好家伙&#xff0c;这奇葩事可真是多&#xff0c;前两天和粉丝聊天&#xff0c;他说前段时间面试阿里的测开岗&#xff0c;最后和面试官干起来了。 我问他为什么&#xff0c;他说没啥&#xff0c;就觉得面试官太装了&#xff0c;我说要21K&#xff0c;他说太高了&#xff0c;说…

【全国产龙芯平台】迅为iTOP-LS3A5000_7A2000开发板+银河麒麟操作系统

硬件准备 1.M.2.ssd硬盘&#xff08;最好大于等于128G&#xff09;&#xff1b; 2.迅为LS3A5000开发板&#xff1b; 3.U盘&#xff08;需大于8g&#xff09;&#xff0c;制作启动盘使用&#xff1b; 4.hdmi显示器&#xff1b; 5.搭载linux环境的计算机。 安装步骤 1 制作…

【多线程】两阶段终止模式

目录 一、两阶段终止模式说明二、错误思路三、实现思路图四、实现思路五、方法说明六、interrupt实现6.1 代码示例6.2 示例截图 七、volatile实现7.1 代码示例7.2 示例截图 一、两阶段终止模式说明 1.在一个线程t1中优雅地终止另一个线程t2&#xff0c;指终止t2线程前&#xff…

【C++】位图/布隆过滤器+海量数据处理

✍作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;C 文章目录 前言一 位图1.位图法介绍2.位图实现的细节 二、布隆过滤器1.布隆过滤器概念2.布隆过滤器实现 三、海量数据处理1. 位图应用2. 哈希切割3. 布隆过滤器 前言 题目 给40亿个不重复的无符号整数&#xff0c;没…

Java

FileOutputStream写数据的3种方式 void write(int b) //一次写一个字节的数据 void write(byte[] b) //一次写一个字节数组数据 void write(byte[] b, int off,int len) //一次写一个字节数组的部分数据 参数一:数组;参数二:起始索引 0;参数三:个数换行: windows:“\r\n” lin…

springboot+java小区社区宽带安装管理系统

本次程序软件的开发的目的就是让使用者可以通过使用该软件提高信息数据的管理效率&#xff0c;同时该程序软件也需要针对不同的操作用户设置对应的功能&#xff0c;因此&#xff0c;此程序的操作流程应该尽量与用户日常操作软件的行为习惯相贴合&#xff0c;另外&#xff0c;程…

分享最强国内免费ChatGPT的镜像网站,记得收藏(2023年更新中)

众所周知的原因&#xff0c;要想在国内使用ChatGPT&#xff0c;肯定是要“折腾一番”的。但是对于绝大多数普通小白&#xff0c;有没有比较容易的方法就用上官方的ChatGPT呢&#xff1f;答案是肯定的&#xff0c;下面就给大家分享几个2023年我正在使用的ChatGPT镜像网址&#x…

Python入门学习

一、执行Python&#xff08;Hello World&#xff09;程序 对于大多数程序语言&#xff0c;第一个入门编程代码便是 “Hello World&#xff01;”&#xff0c;以下代码为使用 Python 输出 “Hello World&#xff01;” 1.1 创建hello.py文件 1.2 编写程序 #!/usr/bin/python…

听我一句劝,别去外包,干了五年,废了....

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入杭州某软件公司&#xff0c;干了接近5年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了5年的功能测试…

Gateway网关参数进行验签POST 包含requestbody 请求体封装

Gateway网关自定义拦截器的不可重复读取数据 特别注意一点, 因为在网关层 拿出 request 流之后,必须重写getbody()方法把所有的参数放进去,否则后面转发的请求无法接收到任何数据, 坑,巨坑,因为版本问题网上很多都不能兼容, 我的springboot环境 依赖包 <parent><gr…