转载: iOS 优雅的处理网络数据

news2024/11/26 15:40:59

转载: iOS 优雅的处理网络数据

原文链接:https://juejin.cn/post/6952682593372340237

相信大家平时在用 App 的时候, 往往有过这样的体验,那就是加载网络数据等待的时间过于漫长,滚动浏览时伴随着卡顿,甚至在没有网络的情况下,整个应用处于不可用状态。那么我们该怎么去提高用户体验,保证用户没有漫长的等待感,还可以轻松自在的享受等待,对加载后的内容有明确的预期呢?

案例分享

在现代的工作生活中,手机早已不是单纯的通信工具了,它更像是一个集办公,娱乐,消费的终端,潜移默化的成为了我们生活的一部分。所以作为 iOS 开发者的我们,在日常的开发中,也早已不是处理显示零星的数据这么简单,为了流量往往我们需要在 App 里显示大量有价值的信息来吸引用户,如何优雅的显示这些海量的数据,考量的就是你的个人经验了。

正如大多数 iOS 开发人员所知,显示滚动数据是构建移动应用中常见的任务,Apple 的 SDK 提供了 UITableView 和 UICollectionVIew 这俩大组件来帮助执行这样的任务。但是,当需要显示大量数据时,确保平滑如丝的滚动可能会非常的棘手。所以今天正好趁这个机会,和大家分享一下处理大量可滚动数据方面的个人经验。

在这篇文章中,你将会学到以下内容:

1.让你的 App 可以无限滚动(infinite scrolling),并且滚动数据无缝加载

2.让你的 App 数据滚动时避免卡顿,实现平滑如丝的滚动

3.异步存储(Cache)和获取图像,来使你的 App 具有更高的响应速度

无限滚动,无缝加载

提到列表分页,相信大家第一个想到的就是 MJRefresh,用于上拉下拉来刷新数据,当滚动数据到达底部的时候向服务器发送请求,然后在控件底部显示一个 Loading 动画,待请求数据返回后,Loading 动画消失,由 UITableView 或者 UICollectionView 控件继续加载这些数据并显示给用户,效果如下图所示:

image

在这种情况下就造成了一种现象,那就是 App 向服务器请求数据到数据返回这段时间留下了一个空白,如果在网络差的情况下,这段空白的时间将会持续,这给人的体验会很不好。那该如何去避免这种现象呢!或者说我们能否去提前获取到其余的数据,在用户毫无感知的情况下把数据请求过来,看上去就像无缝加载一样呢!

答案当然是肯定的!

为了改善应用程序体验,在 iOS 10 上,Apple 对 UICollectionView 和 UITableView 引入了 Prefetching API,它提供了一种在需要显示数据之前预先准备数据的机制,旨在提高数据的滚动性能。

首先,我先和大家介绍一个概念:无限滚动,无限滚动是可以让用户连续的加载内容,而无需分页。在 UI 初始化的时候 App 会加载一些初始数据,然后当用户滚动快要到达显示内容的底部时加载更多的数据。

多年来,像 Instagram, Twitter 和 Facebook 这样的社交媒体公司都使这种技术。如果查看他们的 App ,你就可以看到无限滚动的实际效果,这里我就给大伙展示下 Instagram 的效果吧!

image

如何实现

由于 Instagram 的 UI 过于复杂,在这我就不去模仿实现了,但是我模仿了它的加载机制,同样的实现了一个简单的数据无限滚动和无缝加载的效果。

简单的说下我的思路:

先自定义一个 Cell 视图,这个视图由一个 UILabel 和 一个 UIImageView 构成,用于显示文本和网络图片;然后模拟网络请求来获取数据,注意该步骤一定是异步执行的;最后用 UITableView 来显示返回的数据,在 viewDidLoad 中先请求网络数据来获取一些初始化数据,然后再利用 UITableView 的 Prefetching API 来对数据进行预加载,从而来实现数据的无缝加载。

那关于无限滚动该如何实现呢!其实这个无限滚动并不是真正意义上的永无止尽,严格意义上来讲它是有尽头的,只不过这个功能背后的数据是不可估量的,只有大量的数据做支持才能让应用一直不断的从服务端获取数据。

正常情况下,我们在构建 UITableView 这个控件的时候,需要对它的行数(numsOfRow)做一个初始化,这个行数对我们实现无限加载和无缝加载是一个很关键的因素,假设我们每次根据服务端返回的数据量去更新 UITableView 的行数并 Reload,那我之前说的 Prefetching API 在这种情况下就失去作用了,因为它起作用的前提是要保证预加载数据时 UITableView 当前的行数要小于它的总行数。当然前者也可以实现数据加载,但它的效果就不是无缝加载,它在每次加载数据的时候都会有一个 Loading 等待的时间。

回到我上面所说的无限滚动, 其实实现起来并不难,正常情况下,我们向服务端请求大量相同类型的数据的时候,都会提供一个接口,我称之为分页请求接口,该接口在每次数据返回的时候,都会告诉客户端总共有多少页数据,每页的数据量是多少,当前是第几页,这样我们就能计算出来总共的数据有多少,而这就是 UITableView 的总行数。

响应数据的示范如下(为清楚起见,它仅显示与分页有关的字段):

json复制代码{

  "has_more": true,
  "page": 1,
  "total": 84,
  "items": [
 
    ...
    ...
  ]
}

下面,我就用代码来一步步的实现它吧!

模拟分页请求

由于没有找到合适的分页测试接口,于是我自己模拟一了一个分页请求接口,每次调用该接口的时候都延时 2s 来模拟网络请求的状态,代码如下:

php复制代码 func fetchImages() {
        guard !isFetchInProcess else {
            return
        }
        
        isFetchInProcess = true
        // 延时 2s 模拟网络环境
        print("+++++++++++ 模拟网络数据请求 +++++++++++")
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 2) {
            print("+++++++++++ 模拟网络数据请求返回成功 +++++++++++")
            DispatchQueue.main.async {
                self.total = 1000
                self.currentPage += 1
                self.isFetchInProcess = false
                // 初始化 30个 图片
                let imagesData = (1...30).map {
                    ImageModel(url: baseURL+"\($0).png", order: $0)
                }
                self.images.append(contentsOf: imagesData)

                if self.currentPage > 1 {
                    let newIndexPaths = self.calculateIndexPathsToReload(from: imagesData)
                    self.delegate?.onFetchCompleted(with: newIndexPaths)
                } else {
                    self.delegate?.onFetchCompleted(with: .none)
                }
            }
        }
    }

数据回调处理:

swift复制代码extension ViewController: PreloadCellViewModelDelegate {
        
    func onFetchCompleted(with newIndexPathsToReload: [IndexPath]?) {
        guard let newIndexPathsToReload = newIndexPathsToReload else {
            tableView.tableFooterView = nil
            tableView.reloadData()
            return
        }
        
        let indexPathsToReload = visibleIndexPathsToReload(intersecting: newIndexPathsToReload)
        indicatorView.stopAnimating()
        tableView.reloadRows(at: indexPathsToReload, with: .automatic)
    }
    
    func onFetchFailed(with reason: String) {
        indicatorView.stopAnimating()
        tableView.reloadData()
    }
}

预加载数据

首先如果你想要 UITableView 预加载数据,你需要在 viewDidLoad() 函数中插入如下代码,并且请求第一页的数据:

scss复制代码override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        ...
        tableView.prefetchDataSource = self
        ...
         // 模拟请求图片
        viewModel.fetchImages()
    }

然后,你需要实现 UITableViewDataSourcePrefetching 的协议,它的协议里包含俩个函数:

less复制代码// this protocol can provide information about cells before they are displayed on screen.

@protocol UITableViewDataSourcePrefetching <NSObject>

@required

// indexPaths are ordered ascending by geometric distance from the table view
- (void)tableView:(UITableView *)tableView prefetchRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;

@optional

// indexPaths that previously were considered as candidates for pre-fetching, but were not actually used; may be a subset of the previous call to -tableView:prefetchRowsAtIndexPaths:
- (void)tableView:(UITableView *)tableView cancelPrefetchingForRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;

@end

第一个函数会基于当前滚动的方向和速度对接下来的 IndexPaths 进行 Prefetch,通常我们会在这里实现预加载数据的逻辑。

第二个函数是一个可选的方法,当用户快速滚动导致一些 Cell 不可见的时候,你可以通过这个方法来取消任何挂起的数据加载操作,有利于提高滚动性能, 在下面我会讲到。

实现这俩个函数的逻辑代码为:

swift复制代码extension ViewController: UITableViewDataSourcePrefetching {
    // 翻页请求
    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        let needFetch = indexPaths.contains { $0.row >= viewModel.currentCount}
        if needFetch {
            // 1.满足条件进行翻页请求
            indicatorView.startAnimating()
            viewModel.fetchImages()
        }
        
        for indexPath in indexPaths {
            if let _ = viewModel.loadingOperations[indexPath] {
                return
            }
            
            if let dataloader = viewModel.loadImage(at: indexPath.row) {
                print("在 \(indexPath.row) 行 对图片进行 prefetch ")
                // 2 对需要下载的图片进行预热
                viewModel.loadingQueue.addOperation(dataloader)
                // 3 将该下载线程加入到记录数组中以便根据索引查找
                viewModel.loadingOperations[indexPath] = dataloader
            }
        }
    }

    
    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]){
        // 该行在不需要显示的时候,取消 prefetch ,避免造成资源浪费
        indexPaths.forEach {
            if let dataLoader = viewModel.loadingOperations[$0] {
                print("在 \($0.row) 行 cancelPrefetchingForRowsAt ")
                dataLoader.cancel()
                viewModel.loadingOperations.removeValue(forKey: $0)
            }
        }
    }
}

最后,再加上俩个有用的方法该功能就大功告成了:

scss复制代码    // 用于计算 tableview 加载新数据时需要 reload 的 cell
    func visibleIndexPathsToReload(intersecting indexPaths: [IndexPath]) -> [IndexPath] {
        let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows ?? []
        let indexPathsIntersection = Set(indexPathsForVisibleRows).intersection(indexPaths)
        return Array(indexPathsIntersection)
    }
    
    // 用于确定该索引的行是否超出了目前收到数据的最大数量
    func isLoadingCell(for indexPath: IndexPath) -> Bool {
        return indexPath.row >= (viewModel.currentCount)
    }

见证时刻的奇迹到了,请看效果:

image

通过日志,我们也可以清楚的看到,在滚动的过程中是有 Prefetch 和 CancelPrefetch 操作的:

image

好了,到这里我就简单的实现了 UITableView 无尽滚动和对数据无缝加载的效果,你学会了吗?

如何避免滚动时的卡顿

当你遇到滚动卡顿的应用程序时,通常是由于任务长时间运行阻碍了 UI 在主线程上的更新,想让主线程有空来响应这类更新事件,第一步就是要将消耗时间的任务交给子线程去执行,避免在获取数据时阻塞主线程。

苹果提供了很多为应用程序实现并发的方式,例如 GCD,我在这里对 Cell 上的图片进行异步加载使用的就是它。

代码如下:

swift复制代码class DataLoadOperation: Operation {
    var image: UIImage?
    var loadingCompleteHandle: ((UIImage?) -> ())?
    private var _image: ImageModel
    private let cachedImages = NSCache<NSURL, UIImage>()
    
    init(_ image: ImageModel) {
        _image = image
    }
    
    public final func image(url: NSURL) -> UIImage? {
        return cachedImages.object(forKey: url)
    }
    
    override func main() {
        if isCancelled {
            return
        }
        
        guard let url = _image.url else {
            return
        }
        downloadImageFrom(url) { (image) in
            DispatchQueue.main.async { [weak self] in
                guard let ss = self else { return }
                if ss.isCancelled { return }
                ss.image = image
                ss.loadingCompleteHandle?(ss.image)
            }
        }
        
    }
    
    // Returns the cached image if available, otherwise asynchronously loads and caches it.
    func downloadImageFrom(_ url: NSURL, completeHandler: @escaping (UIImage?) -> ()) {
        // Check for a cached image.
        if let cachedImage = image(url: url) {
            DispatchQueue.main.async {
                print("命中缓存")
                completeHandler(cachedImage)
            }
            return
        }
        
        URLSession.shared.dataTask(with: url as URL) { data, response, error in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil,
                let _image = UIImage(data: data)
                else { return }
            // Cache the image.
            self.cachedImages.setObject(_image, forKey: url, cost: data.count)
            completeHandler(_image)
            }.resume()
    }
}

那具体如何使用呢!别急,听我娓娓道来,这里我再给大家一个小建议,大家都知道 UITableView 实例化 Cell 的方法是:tableView:cellForRowAtIndexPath: ,相信很多人都会在这个方法里面去进行数据绑定然后更新 UI,其实这样做是一种比较低效的行为,因为这个方法需要为每个 Cell 调用一次,它应该快速的执行并返回重用 Cell 的实例,不要在这里去执行数据绑定,因为目前在屏幕上还没有 Cell。我们可以在 tableView:willDisplayCell:forRowAtIndexPath: 这个方法中进行数据绑定,这个方法在显示cell之前会被调用。

为每个 Cell 执行下载任务的实现代码如下:

swift复制代码 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "PreloadCellID") as? ProloadTableViewCell else {
            fatalError("Sorry, could not load cell")
        }
        
        if isLoadingCell(for: indexPath) {
            cell.updateUI(.none, orderNo: "\(indexPath.row)")
        }
        
        return cell
    }
    
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        // preheat image ,处理将要显示的图像
        guard let cell = cell as? ProloadTableViewCell else {
            return
        }

        // 图片下载完毕后更新 cell
        let updateCellClosure: (UIImage?) -> () = { [unowned self] (image) in
            cell.updateUI(image, orderNo: "\(indexPath.row)")
            viewModel.loadingOperations.removeValue(forKey: indexPath)
        }

        // 1. 首先判断是否已经存在创建好的下载线程
        if let dataLoader = viewModel.loadingOperations[indexPath] {
            if let image = dataLoader.image {
                // 1.1 若图片已经下载好,直接更新
                cell.updateUI(image, orderNo: "\(indexPath.row)")
            } else {
                // 1.2 若图片还未下载好,则等待图片下载完后更新 cell
                dataLoader.loadingCompleteHandle = updateCellClosure
            }
        } else {
            // 2. 没找到,则为指定的 url 创建一个新的下载线程
            print("在 \(indexPath.row) 行创建一个新的图片下载线程")
            if let dataloader = viewModel.loadImage(at: indexPath.row) {
                // 2.1 添加图片下载完毕后的回调
                dataloader.loadingCompleteHandle = updateCellClosure
                // 2.2 启动下载
                viewModel.loadingQueue.addOperation(dataloader)
                // 2.3 将该下载线程加入到记录数组中以便根据索引查找
                viewModel.loadingOperations[indexPath] = dataloader
            }
        }
    }

对预加载的图片进行异步下载(预热):

swift复制代码func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        let needFetch = indexPaths.contains { $0.row >= viewModel.currentCount}
        if needFetch {
            // 1.满足条件进行翻页请求
            indicatorView.startAnimating()
            viewModel.fetchImages()
        }
        
        for indexPath in indexPaths {
            if let _ = viewModel.loadingOperations[indexPath] {
                return
            }
            
            if let dataloader = viewModel.loadImage(at: indexPath.row) {
                print("在 \(indexPath.row) 行 对图片进行 prefetch ")
                // 2 对需要下载的图片进行预热
                viewModel.loadingQueue.addOperation(dataloader)
                // 3 将该下载线程加入到记录数组中以便根据索引查找
                viewModel.loadingOperations[indexPath] = dataloader
            }
        }
    }

取消 Prefetch 时,cancel 任务,避免造成资源浪费

swift复制代码func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]){
        // 该行在不需要显示的时候,取消 prefetch ,避免造成资源浪费
        indexPaths.forEach {
            if let dataLoader = viewModel.loadingOperations[$0] {
                print("在 \($0.row) 行 cancelPrefetchingForRowsAt ")
                dataLoader.cancel()
                viewModel.loadingOperations.removeValue(forKey: $0)
            }
        }
    }

经过这般处理,我们的 UITableView 滚动起来肯定是如丝般顺滑,小伙伴们还等什么,还不赶紧试一试。

图片缓存

虽然我在上面对我的应用增加了并发操作,但是我一看 Xcode 的性能分析,我不禁陷入了沉思,我的应用程序太吃内存了,假如我不停的刷,那我的手机应该迟早会把我的应用给终止掉,下图是我刷到 200 行的时候的性能分析图:

内存 image

磁盘 image

可以看到我的应用的性能分析很不理想,究其原因在于我的应用里显示了大量的图片资源,每次来回滚动的时候,都会重新去下载新的图片,而没有对图片做缓存处理。

所以,针对这个问题,我为我的应用加入了缓存 NSCache 对象,来对图片做一个缓存,具体代码实现如下:

swift复制代码class ImageCache: NSObject {

    private var cache = NSCache<AnyObject, UIImage>()
    public static let shared = ImageCache()
    private override init() {}
   
    func getCache() -> NSCache<AnyObject, UIImage> {
       return cache
    }
}

在下载开始的时候,检查有没有命中缓存,如果命中则直接返回图片,否则重新下载图片,并添加到缓存中:

swift复制代码func downloadImageFrom(_ url: URL, completeHandler: @escaping (UIImage?) -> ()) {
        // Check for a cached image.
        if let cachedImage = getCacheImage(url: url as NSURL) {
            print("命中缓存")
            DispatchQueue.main.async {
                completeHandler(cachedImage)
            }
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil,
                let _image = UIImage(data: data)
                else { return }
            
            // Cache the image.
            ImageCache.shared.getCache().setObject(_image, forKey: url as NSURL)

            completeHandler(_image)
            }.resume()
    }

有了缓存的加持后,再用 Xcode 来查看我的应用的性能,就会发现内存和磁盘的占用已经下降了很多:

内存

image

磁盘

image

关于图片缓存的技术,这里只是用了最简单的一种,外面很多开源的图片库都有不同的缓存策略,感兴趣的可以去 GitHub 上学习一下它们的源码,我在这里就不做赘述了。

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

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

相关文章

NLP论文阅读记录-ACL 2023 | 10 Best-k Search Algorithm for Neural Text Generation

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作2.1优势2.2 挑战 三.本文方法3.1 并行探索3.2 时间衰变3.3堆修剪3.4 模型得分 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果 五 总结 前言 用于神经文本生成…

【Transformer】Transformer and BERT(1)

文章目录 TransformerBERT 太…完整了&#xff01;同济大佬唐宇迪博士终于把【Transformer】入门到精通全套课程分享出来了&#xff0c;最新前沿方向 学习笔记 Transformer 无法并行&#xff0c;层数比较少 词向量生成之后&#xff0c;不会变&#xff0c;没有结合语境信息的情…

Transformer Decoder的输入

大部分引用参考了既安的https://www.zhihu.com/question/337886108/answer/893002189这篇文章&#xff0c;个人认为写的很清晰&#xff0c;此外补充了一些自己的笔记。 弄清楚Decoder的输入输出&#xff0c;关键在于图示三个箭头的位置&#xff1a; 以翻译为例&#xff1a; 输…

支持向量机(SVM):高效分类的强大工具

文章目录 前言1. SVM的基本原理1.1 核心思想1.2 支持向量1.3 最大化建模1.4 松弛变量1.5 核函数 2. SVM与逻辑回归的区别和联系2.1 区别2.2 联系 3. SVM的应用领域3.1 图像分类3.2 文本分类3.3 生物信息学3.4 金融领域3.5 医学诊断 4. SVM的优势与挑战4.1 优势4.1.1 非线性分类…

分布式理论 | RPC | Spring Boot 整合 Dubbo + ZooKeeper

一、基础 分布式理论 什么是分布式系统&#xff1f; 在《分布式系统原理与范型》一书中有如下定义&#xff1a;“分布式系统是若干独立计算机的集合&#xff0c;这些计算机对于用户来说就像单个相关系统”&#xff1b; 分布式系统是由一组通过网络进行通信、为了完成共同的…

【02】GeoScene海图生产环境创建

1.1 海图生产环境 GeoScene中的企业级海事制图由中央航海信息系统数据库&#xff08;NIS库&#xff09;来处理&#xff0c;将之前传统桌面产品库&#xff08;PL库&#xff09;产品管理方面的能力已经移植到NIS数据库&#xff0c;以ProductDefinitions、ProductCoverage、Produ…

主从reactor多线程实现

现场模型图片&#xff0c;从网上找的 出于学习的目的实现的&#xff0c;如有不对的地方欢迎留言知道&#xff0c;简单实现了http的请求&#xff0c;可通过postman进行访问 启动项目&#xff1a; 返回数据示例 postman请求 附上源码&#xff0c;有问题直接看源码吧

低代码工作流,在业务场景下启动流程节点绑定的具体步骤与注意事项

在业务管理的场景下&#xff0c;存在先做了对应的数据管理&#xff0c;后续增加管理的规范度&#xff0c;“在业务数据变化时发起流程”的需求&#xff0c;那么这种情况下就需要在业务管理&#xff08;列表页、表单&#xff09;中发起流程&#xff0c;让业务模型使用流程配置&a…

[23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians

[paper | proj] 给定FLAME&#xff0c;基于每个三角面片中心初始化一个3D Gaussian&#xff08;3DGS&#xff09;&#xff1b;当FLAME mesh被驱动时&#xff0c;3DGS根据它的父亲三角面片&#xff0c;做平移、旋转和缩放变化&#xff1b;3DGS可以视作mesh上的辐射场&#xff1…

Python3中_和__的用途和区别

目录 一、_&#xff08;下划线&#xff09; 1、临时变量&#xff1a; 2、未使用的变量&#xff1a; 二、__&#xff08;双下划线&#xff09; 1、私有属性&#xff1a; 2、私有方法&#xff1a; 三、__的一些特殊用途。 总结 Python3中的_和__是两个特殊的标识符&#…

大语言模型加速信创软件 IDE 技术革新

QCon 全球软件开发大会&#xff08;上海站&#xff09;将于 12 月 28-29 日举办&#xff0c;会议特别策划「智能化信创软件 IDE」专题&#xff0c;邀请到华为云开发工具和效率领域首席专家、华为软件开发生产线 CodeArts 首席技术总监王亚伟担任专题出品人&#xff0c;为专题质…

云原生之深入解析减少Docker镜像大小的优化技巧

一、什么是 Docker&#xff1f; Docker 是一种容器引擎&#xff0c;可以在容器内运行一段代码&#xff0c;Docker 镜像是在任何地方运行应用程序而无需担心应用程序依赖性的方式。要构建镜像&#xff0c;docker 使用一个名为 Dockerfile 的文件&#xff0c;Dockerfile 是一个包…

linux系统和网络(一):文件IO

本文主要探讨linux系统编程的文件IO相关知识。 文件IO 文件存在块设备中为静态文件,open打开文件,内核在进程中建立打开文件的数据结构在内存中用于记录文件的文件参数,开辟一段内存用于存放内容,将静态文件转为动态文件 打开文件后对文件的读写操作都为对动态…

Windows下配置最新ChromeDriver

1、问题 在使用代码调用谷歌浏览器时会出错&#xff1a; from selenium import webdriver driver webdriver.Chrome() SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 114 Current browser versi…

网络空间搜索引擎- FOFA的使用技巧总结

简介 FOFA是一款网络空间测绘的搜索引擎&#xff0c;旨在帮助用户以搜索的方式查找公网上的互联网资产。 FOFA的查询方式类似于谷歌或百度&#xff0c;用户可以输入关键词来匹配包含该关键词的数据。不同的是&#xff0c;这些数据不仅包括像谷歌或百度一样的网页&#xff0c;还…

网神防火墙后台用户敏感信息泄露漏洞复现

简介 网神防火墙是一款由中国知名网络安全公司启明星辰开发的防火墙产品。它提供了全面的网络安全防护功能,旨在保护企业网络免受各种网络威胁和攻击。 该产品存在用户账号信息泄露漏洞,通过构造特定数据包,获取防火墙管理员登录的账号密码。 漏洞复现 FOFA语法: body=&…

A01、关于JVM的GC回收

引用类型 对象引用类型分为强引用、软引用、弱引用&#xff0c;具体差别详见下文描述&#xff1a; 强引用&#xff1a;就是我们一般声明对象是时虚拟机生成的引用&#xff0c;强引用环境下&#xff0c;垃圾回收时需要严格判断当前对象是否被强引用&#xff0c;如果被强引用&am…

KSP音频抓包

1. 按照网上其他教程&#xff0c;安装KSP抓音频 Biu~笔记&#xff1a;高通蓝牙ADK&#xff08;38&#xff09;-- KSP in MDE - 大大通(简体站) Biu~笔记&#xff1a;高通蓝牙ADK&#xff08;22&#xff09;--DSP音频链路监听 - 大大通(简体站) <<Biu~笔记&#xff1a;高…

docker 与 ffmpeg

创建容器 docker run -it -v /mnt/f/ffmpeg:/mnt/f/ffmpeg --name ffmpeg 49a981f2b85f /bin/bash 在 Linux 上编译 FFmpeg&#xff1a; 安装依赖库&#xff1a; sudo apt-get update sudo apt-get install build-essential yasm cmake libtool libc6 libc6-dev unzip wget下…

电脑操作系统深度剖析:Windows、macOS和Linux的独特特性及应用场景

导言 电脑操作系统是计算机硬件和应用软件之间的桥梁&#xff0c;不同的操作系统在用户体验、性能和安全性方面有着独特的特色。电脑操作系统是计算机系统中的核心组件&#xff0c;不同的操作系统在设计理念、用户体验和应用领域上存在显著差异。本文将深入探讨几种常见的电脑操…