转载:TableView性能优化

转载:TableView性能优化

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

tableView性能优化

Cell重用、标识重用

使用 static 修饰重用标识名称能够保证这个标识只会创建一次,提高性能。接着调用dequeueReusableCellWithIdentifier:方法 获取缓存池中的Cell。如果没有就调用 initWithStyle:ReusIdentifier:方法 创建一个新的Cell。注意事先需要调用registerNib/registerClass方法TableView 注册一下重用标识。

动态高度

我们需要实现它的代理,来给出高度:

objectivec复制代码- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // return xxx
}

这个代理方法实现后,上面的 rowHeight 的设置将会变成无效。在这个方法中,我们需要提高cell高度的计算效率,来节省时间。

自从iOS8之后有了 self-sizing cell的概念,cell可以自己算出高度,使用self-sizing cell需要满足以下三个条件:

(1)使用 Autolayout 进行 UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)。

(2)指定 TableViewestimatedRowHeight属性 的默认值。

(3)指定 TableView的rowHeight 属性为 UITableViewAutomaticDimension

ini复制代码- (void)viewDidload {
    self.myTableView.estimatedRowHeight = 44.0;
    self.myTableView.rowHeight = UITableViewAutomaticDimension;
}

除了提高cell高度的计算效率之外,对于已经计算出的高度,我们需要进行缓存,对于已经计算过的高度,没有必要进行计算第二次。

减少视图的数目

我们在 cell 上添加系统控件的时候,实际上系统都会调用底层的接口进行绘制,大量添加控件时,会消耗很大的资源并且也会影响渲染的性能。当使用默认的 UITableViewCell 并且在它的 ContentView 上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承 UITableViewCell,并重写drawRect方法

重绘操作仍然在 drawRect方法 中完成,但是苹果不建议直接调用 drawRect方法,当然如果你强直直接调用此方法,当然是没有效果的。苹果要求我们调用UIView类中的 setNeedsDisplay方法,则程序会自动调用 drawRect方法 进行重绘。(调用 setNeedsDisplay 会自动调用 drawRect)。

使用hidden隐藏图层

避免动态添加图层。在初始化cell的时候一并将所有图层预先创建好,通过hidden属性控制子图层的显示或隐藏,因为单纯的显示操作要比创建快的多。

在快速滚动时考虑使用界面外壳

使用有外壳的界面.png

当用户快速滚动列表视图时,虽然使用了所有的优化,但视图的重用和渲染仍然需要超过 16 毫秒,还有可能出现偶发的丢帧现象,从而导致不流畅的体验。

在这些情况下,使用一个界面外壳是一个较好的选择,外壳可以被预先定义,它的唯一目的就是告诉终端用户这些部分即将展示一些数据。当滚动速度降低,并低于阈值时,刷新最终的视图并填充数据。

你可以使用与列表视图相关联的 panGestureRecognizer 属性获取速度值。

// 列表视图的速度
-(void)scrollViewDidScroll:(UIScrollView *)scrollView { 
    CGPoint velocity = [tableView.panGestureRecognizer 
                        velocityInView:self.view]; 
    self.velocity = velocity;
}
-(UITableViewCell *)tableView:(UITableView *)tableView 
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(fabs(self.velocity.y) > 2000) { 
        //返回界面外壳
    } else {
        //返回真正的单元格 
    }
}

避免离屛渲染。

开启离屛渲染的代价就是需要新开辟一块新的缓冲区,在渲染的过程中还会多次的切换上下文,这些都是很消耗性能的。以下情况均会造成离屛渲染:

1. shadows(阴影)

其原因在于需要显示在所有layer内容的下方,因此必须被渲染在先。但此时阴影的本体(layer和其子layer)都还没有被组合到一起,只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到帧缓冲区frame buffer,最后把内容画上去。不过如果我们能够预先告诉CoreAnmation(通过 shadowPath属性 )阴影的几何形状,那么阴影当然可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染了。

2. 设置了组透明度为YES,并且透明度不为1的layer(不透明)

产生离屏渲染的条件是layer.opacity != 1.0并且有 子layer 或者背景图。alpha并不是分别应用在每一层之上,而是只有到 整个layer树 画完之后,再统一加上alpha,最后和底下其他layer的像素进行组合。显然也无法通过一次遍历就得到最终结果。

3. masks(遮罩)

我们知道mask是应用在layer和其所有子layer的组合之上的,而且可能带有透明度,那么其实和group opacity的原理类似,不得不在离屏渲染中完成。

4. cornerRadius+clipsToBounds

容器的子layer因为父容器有圆角,那么也会需要被裁剪,而这时它们还在渲染队列中排队,尚未被组合到一块画布上,自然也无法统一裁剪,不得已只能另开一块内存来操作。而如果只是设置cornerRadius,并不会触发离屏渲染。

5. shouldRasterize(光栅化)

如果layer不是静态的,我们更新已光栅化的layer,会造成大量的离屏渲染。 例如UITableViewCell因为复用的原因,重绘是很频繁的。如果此时设置了光栅化,会造成大量离屏渲染,降低性能。 不要过度使用,系统限制了缓存的大小为 2.5 * Screen Size。超出缓存之后,同样会造成大量的离屏渲染。 离屏渲染缓存内容有时间限制,被光栅化的图片(即缓存内容)如果超过100ms没有被使用,那么它就会丢弃,无法进行复用。(所以光栅化只能用在图像内容不变的前提下,且只对连续不断使用的图片进行缓存:用于避免静态内容的复杂特效的重绘,例如UIBlurEffect用于避免多个View嵌套的复杂View的重绘。)

6. edge antialiasing(抗锯齿)

分页加载数据,预先异步请求数据 - Prefetching API

viewDidLoad 中先请求网络数据来获取一些初始化数据,然后再利用 UITableViewPrefetching API 来对数据进行预加载,从而来实现数据的无缝加载。

UITableViewDataSourcePrefetching 协议
// 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)
            }
        }
    }
}

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

   // 用于计算 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)
    }

滑动TableView时,按需加载内容

有些情况下我们可能会去快速的滑动列表,这时候其实会有大量的cell对象被创建、被重用,但其实我们可能只是去浏览列表停止的那一页的上下一定范围内的信息,前面快速划过的那些信息对我们来说都是无用的。此时我们可以通过ScrollView的代理方法

scrollViewWillEndDragging: withVelocity: targetContentoffset:

来按需加载内容。

#pragma mark - UIScrollViewDelegate  
//按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。  
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {  

    NSIndexPath *targetPath = [_myTableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];  
    NSIndexPath *firstVisiblePath = [[_myTableView indexPathsForVisibleRows] firstObject];  
    NSInteger skipCount = 8;  
    if (labs(firstVisiblePath.row - targetPath.row)>  skipCount) {  
        NSArray *temp = [_myTableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, _myTableView.frame.size.width, _myTableView.frame.size.height)];  
        NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];  
        if (velocity.y<0) {  
            NSIndexPath *indexPath = [temp lastObject];  
            if (indexPath.row+33) {  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];  
            }  
        }  
        [_dataList addObjectsFromArray:arr];  
    }  
}  

targetContentOffsetTableView 减速到停止的地方, velocity 表示速度向量。

如何避免滚动时的卡顿: 异步化UI,不要阻塞主线程

当你遇到滚动卡顿的应用程序时,通常是由于任务长时间运行阻碍了 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()
    }
}

在willDisplayCell:forRowAtIndexPath:代理方法中绑定数据

那具体如何使用呢!别急,听我娓娓道来,这里我再给大家一个小建议,大家都知道 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)
            }
        }
    }

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

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

相关文章

超详细的80个Python入门实例,附源码,大学装逼必备!

对于大部分Python学习者来说&#xff0c;核心知识基本已经掌握了&#xff0c;但"纸上得来终觉浅,绝知此事要躬行"&#xff0c;要想完全掌握Python&#xff0c;还得靠实践应用。 今天给大家分享80个Python入门实例&#xff0c;都是基础实例&#xff0c;经典实用&…

人工智能深度学习100种网络模型,精心整理,全网最全,PyTorch框架逐一搭建

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能深度学习100种网络模型&#xff0c;这些模型可以用PyTorch深度学习框架搭建。模型按照个人学习顺序进行排序&#xff1a; 深度学习模型 ANN (Artificial Neural Network) - 人工神经网络&#xff1a;基本…

JavaSE语法之七:封装

文章目录 一、封装的概念二、访问限定符三、封装扩展之包1. 包的概念2. 导入包中的类3. 自定义包4. 常见的包 四、实现封装五、static成员1. 再谈学生类成员变量2. static修饰成员变量3. static修饰成员方法4. static成员变量初始化 六、代码块1. 代码块概念及其分类2. 普通代码…

探索Linux服务器配置信息的命令

目录 前言1 uname2 lscpu3 free4 df5 lspci6 lsusb7 lshw结语 前言 Linux系统提供了许多命令&#xff0c;用于获取和查看服务器的软硬件配置信息。这些命令可以帮助管理员和用户了解系统的状态、资源使用情况以及硬件设备的相关信息。以下是一些常用的命令以及它们的作用、使用…

windows安装conda小环境 windows安装anaconda python jupyter

1 如果想体验在线版的jupyter&#xff0c;可以访问anaconda在Anaconda Cloud&#xff0c;需要注册github&#xff1a; 1 下载anaconda &#xff0c;并安装 1.1 下载 或者去清华镜像下载 Free Download | Anacondahttps://www.anaconda.com/downloadIndex of /anaconda/archi…

Logistic Regression——逻辑回归

1. 为什么需要逻辑回归 在前面学习的线性回归中&#xff0c;我们的预测值都是任意的连续值&#xff0c;例如预测房价。除此之外&#xff0c;还有一个常见的问题就是分类问题&#xff0c;而逻辑回归是一个解决分类问题的模型&#xff0c;其预测值是离散的。 分类问题又包括…

Module ‘app‘: platform ‘android-33‘ not found.

目录 一、报错信息 二、解决方法 一、报错信息 Module app: platform android-33 not found. 检查你的应用程序的build.gradle文件中的targetSdkVersion和compileSdkVersion是否正确设置为已安装的Android SDK版本。 确保你的Android Studio已正确安装并配置了所需的Android …

香槟过了保质期还能喝吗?

香槟是起泡酒的高级代表&#xff0c;是浪漫和喜庆的化身&#xff0c;它浑身上下都散发着无穷的魅力。那么&#xff0c;这么精贵的葡萄酒有没有保质期&#xff0c;会不会变质呢&#xff1f;云仓酒庄的品牌雷盛红酒分享当然会。一瓶酒的生命离不开它的保存期限&#xff0c;酒的质…

力扣第一题-两数之和[简单]

题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任…

这种形状的土堆,用DasViewer土方计算时该选择哪种模式?

答&#xff1a;推荐拟合平面&#xff1b;当堆料的整个边界可见并且基面是具有相同高度的坚硬表面、斜坡或平坦时&#xff0c;推荐该选项。 DasViewer是由大势智慧自主研发的免费的实景三维模型浏览器,采用多细节层次模型逐步自适应加载技术,让用户在极低的电脑配置下,也能流畅…

命令执行 [BUUCTF 2018]Online Tool1

打开题目 我们代码审计一下 if (isset($_SERVER[HTTP_X_FORWARDED_FOR])) { $_SERVER[REMOTE_ADDR] $_SERVER[HTTP_X_FORWARDED_FOR]; } 如果存在xxf头且不为空&#xff0c;则将xxf头内容&#xff08;真实的客户端ip&#xff09;赋给ROMOTE_ADDR&#xff08;代理服务器传过…

11.jvm第三方工具使用实践

目录 概述GCEasy官网jvm内存占用情况关键性能指标堆内存与元空间优化 MAT安装MAT相关概念说明内存泄漏与内存溢出shallow heap及retained heapoutgoing references与incoming referencesDominator Tree GCViewerArthas下载安装与启动jdk8jdk 11jdk11自定义boot jarjdk17 常用命…

PHPRunner 10.91 Crack

PHPRunner是一款非常好用的网页制作工具&#xff0c;界面简洁美观&#xff0c;支持处理多个数据库连接并添加设计页面&#xff0c;页面中可以显示不同的不相关对象&#xff0c;如网格&#xff0c;单个记录&#xff0c;图表&#xff0c;报告等。PHPRunner支持多个操作系统&#…

OxLint 发布了,Eslint 何去何从?

由于最近的rust在前端领域的崛起&#xff0c;基于rust的前端生态链遭到rust底层重构&#xff0c;最近又爆出OxLint&#xff0c;是一款基于Rust的linter工具Oxlint在国外前端圈引起热烈讨论&#xff0c;很多大佬给出了高度评价&#xff1b;你或许不知道OxLint&#xff0c;相比ES…

【笔试强化】Day 3

一、单选 1. 正确答案&#xff1a;C子类继承父类&#xff0c;但是 name 被 private 修饰&#xff0c;不能访问 2. 正确答案&#xff1a;D父类构造了对象&#xff0c;但是子类没有使用 super调用&#xff0c;会报错 3. 正确答案&#xff1a;B构造方法可以重载 4. 正确答案&a…

【STM32独立看门狗(IWDG) 】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、看门狗是什么&#xff1f;1.简介2. 主要功能3.独立看门狗如何工作4.寄存器写保护5.看门狗 看门时间 二、使用步骤1.开启时钟2.初始化看门狗3.开启看门狗4.喂…

面试官:你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢

一、什么是SPA SPA&#xff08;single-page application&#xff09;&#xff0c;翻译过来就是单页应用SPA是一种网络应用程序或网站的模型&#xff0c;它通过动态重写当前页面来与用户交互&#xff0c;这种方法避免了页面之间切换打断用户体验在单页应用中&#xff0c;所有必要…

mac 反编译apk记录

Mac/Linux 去release页下载&#xff0c;有中国下载地址能下载快些。 //也可以直接下载源码&#xff0c;中国下载慢&#xff0c;不推荐。 //git clone --depth1 https://github.com/tp7309/TTDeDroid.git ~/Documents/TTDeDroid//给脚本执行权限 chmod ax ~/Documents/TTDeDro…

AnythingLLM:基于RAG方案构专属私有知识库(开源|高效|可定制)

一、前言 继OpenAI和Google的产品发布会之后&#xff0c;大模型的能力进化速度之快令人惊叹&#xff0c;然而&#xff0c;对于很多个人和企业而言&#xff0c;为了数据安全不得不考虑私有化部署方案&#xff0c;从GPT-4发布以来&#xff0c;国内外的大模型就拉开了很明显的差距…

函数图形渐近线分析

文章目录 曲线的渐近线水平和垂直渐近线斜渐近线斜渐近线公式推导简便方法确定斜渐近线(一次多项式化方法) 例 曲线的渐近线 渐近线综合了极限和函数图形的知识,尤其是斜渐近线 水平和垂直渐近线 若点 M M M沿曲线 y f ( x ) yf(x) yf(x)无限远离原点时,它于某条直线 L L L之…