当前位置: 首页 > ops >正文

Swift实现股票图:从基础到高级

目录

    • 一、核心实现方案
      • 1. 原生方案:使用 Core Graphics 绘制
      • 2. 使用第三方库:Charts
      • 3. 跨平台方案:使用 SwiftUI + Canvas
    • 二、技术指标实现
      • 1. 移动平均线 (MA)
      • 2. 布林带 (Bollinger Bands)
      • 3. MACD (Moving Average Convergence Divergence)
    • 三、性能优化策略
      • 1. 数据分页与懒加载
      • 2. 离屏渲染优化
      • 3. 手势交互优化
    • 四、高级功能实现
      • 1. 十字线光标
      • 2. 多时间周期切换
    • 五、架构设计建议
      • 1、分层架构设计:
      • 2、性能优化总结表:
      • 3、跨平台适配方案:
    • 六、推荐方案选择
      • 1、根据需求选择方案
      • 2、性能对比表

股票图(尤其是K线图)是金融应用的核心功能。下面我将详细介绍多种实现方案,并提供完整的代码示例和优化建议。

一、核心实现方案

1. 原生方案:使用 Core Graphics 绘制

优势:

  • 完全可控,无依赖
  • 高性能,适合大数据量
  • 定制化程度高

实现代码:

import UIKitstruct Candle {let date: Datelet open: Doublelet high: Doublelet low: Doublelet close: Doublelet volume: Double
}class StockChartView: UIView {var candles: [Candle] = [] {didSet {calculateMetrics()setNeedsDisplay()}}// 计算指标private var minPrice: Double = 0private var maxPrice: Double = 0private var maxVolume: Double = 0private var candleWidth: CGFloat = 0private var candleSpacing: CGFloat = 2override func draw(_ rect: CGRect) {super.draw(rect)guard let context = UIGraphicsGetCurrentContext() else { return }guard !candles.isEmpty else { return }drawGrid(context: context)drawCandles(context: context)drawVolume(context: context)drawIndicators(context: context)}private func calculateMetrics() {minPrice = candles.map { $0.low }.min() ?? 0maxPrice = candles.map { $0.high }.max() ?? 0// 添加10%的上下空间let priceRange = maxPrice - minPriceminPrice = minPrice - priceRange * 0.1maxPrice = maxPrice + priceRange * 0.1maxVolume = candles.map { $0.volume }.max() ?? 0// 计算蜡烛宽度let availableWidth = bounds.width - 40 // 左右边距candleWidth = (availableWidth / CGFloat(candles.count)) - candleSpacing}private func drawGrid(context: CGContext) {context.setStrokeColor(UIColor.systemGray4.cgColor)context.setLineWidth(0.5)// 水平网格线let horizontalLines = 5for i in 0...horizontalLines {let y = bounds.height * CGFloat(i) / CGFloat(horizontalLines)context.move(to: CGPoint(x: 0, y: y))context.addLine(to: CGPoint(x: bounds.width, y: y))}// 垂直网格线let verticalLines = 6for i in 0...verticalLines {let x = bounds.width * CGFloat(i) / CGFloat(verticalLines)context.move(to: CGPoint(x: x, y: 0))context.addLine(to: CGPoint(x: x, y: bounds.height))}context.strokePath()}private func drawCandles(context: CGContext) {let chartHeight = bounds.height * 0.7 // 70%高度用于K线let topMargin: CGFloat = 20for (index, candle) in candles.enumerated() {let x = 20 + CGFloat(index) * (candleWidth + candleSpacing)// 计算价格对应的Y坐标func priceToY(_ price: Double) -> CGFloat {let priceRatio = (price - minPrice) / (maxPrice - minPrice)return topMargin + chartHeight * (1 - CGFloat(priceRatio))}let highY = priceToY(candle.high)let lowY = priceToY(candle.low)let openY = priceToY(candle.open)let closeY = priceToY(candle.close)// 绘制影线context.setStrokeColor(UIColor.gray.cgColor)context.setLineWidth(1)context.move(to: CGPoint(x: x + candleWidth/2, y: highY))context.addLine(to: CGPoint(x: x + candleWidth/2, y: lowY))context.strokePath()// 绘制实体let bodyHeight = max(1, abs(openY - closeY))let bodyY = min(openY, closeY)if candle.close > candle.open {context.setFillColor(UIColor.systemRed.cgColor) // 阳线} else {context.setFillColor(UIColor.systemGreen.cgColor) // 阴线}context.fill(CGRect(x: x, y: bodyY, width: candleWidth, height: bodyHeight))}}private func drawVolume(context: CGContext) {let volumeHeight = bounds.height * 0.3 // 30%高度用于成交量let volumeTop = bounds.height * 0.7 + 10for (index, candle) in candles.enumerated() {let x = 20 + CGFloat(index) * (candleWidth + candleSpacing)// 计算成交量高度let volumeRatio = CGFloat(candle.volume / maxVolume)let barHeight = volumeHeight * volumeRatio// 设置颜色(与K线一致)if candle.close > candle.open {context.setFillColor(UIColor.systemRed.cgColor)} else {context.setFillColor(UIColor.systemGreen.cgColor)}context.fill(CGRect(x: x, y: volumeTop + volumeHeight - barHeight, width: candleWidth, height: barHeight))}}private func drawIndicators(context: CGContext) {// 绘制移动平均线示例let movingAverage = calculateMovingAverage(period: 5)context.setStrokeColor(UIColor.blue.cgColor)context.setLineWidth(1.5)for (index, value) in movingAverage.enumerated() {let x = 20 + CGFloat(index) * (candleWidth + candleSpacing) + candleWidth/2let y = bounds.height * 0.7 * (1 - CGFloat((value - minPrice) / (maxPrice - minPrice))) + 20if index == 0 {context.move(to: CGPoint(x: x, y: y))} else {context.addLine(to: CGPoint(x: x, y: y))}}context.strokePath()}private func calculateMovingAverage(period: Int) -> [Double] {guard period > 0, candles.count >= period else { return [] }var averages: [Double] = []for i in 0..<candles.count {let start = max(0, i - period + 1)let end = ilet range = candles[start...end]let sum = range.reduce(0) { $0 + $1.close }averages.append(sum / Double(range.count))}return averages}
}

2. 使用第三方库:Charts

优势

  • 快速实现复杂图表
  • 内置多种技术指标
  • 支持交互功能

实现步骤:

  1. 安装 Charts 库(通过 CocoaPods 或 SPM)
  2. 创建 K 线图视图
import UIKit
import Chartsclass StockChartViewController: UIViewController {@IBOutlet weak var candleStickChartView: CandleStickChartView!@IBOutlet weak var volumeChartView: BarChartView!override func viewDidLoad() {super.viewDidLoad()setupCharts()loadData()}private func setupCharts() {// 配置K线图candleStickChartView.dragEnabled = truecandleStickChartView.setScaleEnabled(true)candleStickChartView.pinchZoomEnabled = truecandleStickChartView.xAxis.labelPosition = .bottomcandleStickChartView.legend.enabled = false// 配置成交量图volumeChartView.dragEnabled = falsevolumeChartView.setScaleEnabled(false)volumeChartView.pinchZoomEnabled = falsevolumeChartView.xAxis.labelPosition = .bottomvolumeChartView.legend.enabled = falsevolumeChartView.leftAxis.enabled = false}private func loadData() {let candles = generateSampleData()// K线数据var candleEntries = [CandleChartDataEntry]()for (i, candle) in candles.enumerated() {let entry = CandleChartDataEntry(x: Double(i),shadowH: candle.high,shadowL: candle.low,open: candle.open,close: candle.close)candleEntries.append(entry)}let candleDataSet = CandleChartDataSet(entries: candleEntries, label: "K线")candleDataSet.increasingColor = .systemRedcandleDataSet.increasingFilled = truecandleDataSet.decreasingColor = .systemGreencandleDataSet.decreasingFilled = truecandleDataSet.shadowColor = .darkGraycandleDataSet.shadowWidth = 1candleStickChartView.data = CandleChartData(dataSet: candleDataSet)// 成交量数据var volumeEntries = [BarChartDataEntry]()for (i, candle) in candles.enumerated() {volumeEntries.append(BarChartDataEntry(x: Double(i), y: candle.volume))}let volumeDataSet = BarChartDataSet(entries: volumeEntries, label: "成交量")volumeDataSet.colors = candles.map { $0.close > $0.open ? .systemRed : .systemGreen }volumeChartView.data = BarChartData(dataSet: volumeDataSet)// 添加移动平均线let movingAverage = calculateMovingAverage(data: candles.map { $0.close }, period: 5)var lineEntries = [ChartDataEntry]()for (i, value) in movingAverage.enumerated() {lineEntries.append(ChartDataEntry(x: Double(i), y: value))}let lineDataSet = LineChartDataSet(entries: lineEntries, label: "5日均线")lineDataSet.colors = [.blue]lineDataSet.drawCirclesEnabled = falselineDataSet.lineWidth = 2candleStickChartView.data?.addDataSet(lineDataSet)}private func generateSampleData() -> [Candle] {// 生成示例数据...}private func calculateMovingAverage(data: [Double], period: Int) -> [Double] {// 计算移动平均...}
}

3. 跨平台方案:使用 SwiftUI + Canvas

import SwiftUIstruct CandleChart: View {let candles: [Candle]@State private var visibleRange: ClosedRange<Int> = 0...50var body: some View {GeometryReader { geometry inZStack(alignment: .topLeading) {// 网格背景GridBackground()// K线图CandlesView(candles: Array(candles[visibleRange]),width: geometry.size.width,height: geometry.size.height * 0.7).offset(y: 20)// 成交量图VolumeView(candles: Array(candles[visibleRange]),width: geometry.size.width,height: geometry.size.height * 0.3).offset(y: geometry.size.height * 0.7 + 10)// 技术指标MovingAverageView(candles: Array(candles[visibleRange]),width: geometry.size.width,height: geometry.size.height * 0.7).offset(y: 20)}.gesture(DragGesture().onChanged { value in// 处理拖动逻辑})}}
}struct CandlesView: View {let candles: [Candle]let width: CGFloatlet height: CGFloatprivate var minPrice: Double { candles.map { $0.low }.min() ?? 0 }private var maxPrice: Double { candles.map { $0.high }.max() ?? 0 }private var candleWidth: CGFloat { (width - 40) / CGFloat(candles.count) - 2 }var body: some View {Canvas { context, size infor (index, candle) in candles.enumerated() {let x = 20 + CGFloat(index) * (candleWidth + 2) + candleWidth/2// 影线context.stroke(Path { path inpath.move(to: CGPoint(x: x, y: priceToY(candle.high)))path.addLine(to: CGPoint(x: x, y: priceToY(candle.low)))},with: .color(.gray),lineWidth: 1)// 实体let bodyRect = CGRect(x: 20 + CGFloat(index) * (candleWidth + 2),y: priceToY(max(candle.open, candle.close)),width: candleWidth,height: abs(priceToY(candle.open) - priceToY(candle.close)))context.fill(Path(bodyRect),with: .color(candle.close > candle.open ? .red : .green))}}.frame(height: height)}private func priceToY(_ price: Double) -> CGFloat {let priceRatio = (price - minPrice) / (maxPrice - minPrice)return height * (1 - CGFloat(priceRatio))}
}

二、技术指标实现

1. 移动平均线 (MA)

extension Array where Element == Candle {func movingAverage(period: Int) -> [Double] {guard period > 0, count >= period else { return [] }return self.indices.map { index inlet start = max(0, index - period + 1)let end = indexlet range = self[start...end]return range.reduce(0) { $0 + $1.close } / Double(range.count)}}
}

2. 布林带 (Bollinger Bands)

struct BollingerBands {let upper: [Double]let middle: [Double] // 即MAlet lower: [Double]
}extension Array where Element == Candle {func bollingerBands(period: Int, multiplier: Double = 2) -> BollingerBands {let ma = movingAverage(period: period)guard ma.count == count else { return BollingerBands(upper: [], middle: [], lower: []) }let stdDev: [Double] = indices.map { index inlet start = max(0, index - period + 1)let end = indexlet range = self[start...end].map { $0.close }let average = ma[index]let variance = range.reduce(0) { $0 + pow($1 - average, 2) } / Double(range.count)return sqrt(variance)}let upper = zip(ma, stdDev).map { $0 + $1 * multiplier }let lower = zip(ma, stdDev).map { $0 - $1 * multiplier }return BollingerBands(upper: upper, middle: ma, lower: lower)}
}

3. MACD (Moving Average Convergence Divergence)

struct MACD {let macdLine: [Double]let signalLine: [Double]let histogram: [Double]
}extension Array where Element == Candle {func macd(fastPeriod: Int = 12, slowPeriod: Int = 26, signalPeriod: Int = 9) -> MACD {let fastEMA = exponentialMovingAverage(period: fastPeriod)let slowEMA = exponentialMovingAverage(period: slowPeriod)let macdLine = zip(fastEMA, slowEMA).map { $0 - $1 }let signalLine = macdLine.exponentialMovingAverage(period: signalPeriod)let histogram = zip(macdLine, signalLine).map { $0 - $1 }return MACD(macdLine: macdLine, signalLine: signalLine, histogram: histogram)}private func exponentialMovingAverage(period: Int) -> [Double] {guard count >= period else { return [] }var ema: [Double] = []let multiplier = 2.0 / Double(period + 1)// 第一个EMA是简单移动平均let firstSMA = prefix(period).reduce(0) { $0 + $1.close } / Double(period)ema.append(firstSMA)// 计算后续EMAfor i in period..<count {let value = self[i].closelet prevEMA = ema[i - period]let currentEMA = (value - prevEMA) * multiplier + prevEMAema.append(currentEMA)}// 补齐前面的空值return Array(repeating: Double.nan, count: period - 1) + ema}
}

三、性能优化策略

1. 数据分页与懒加载

class StockDataLoader {private let allCandles: [Candle]private var loadedCandles: [Candle] = []private let pageSize = 100init(candles: [Candle]) {self.allCandles = candles}func loadMore(completion: @escaping ([Candle]) -> Void) {let startIndex = loadedCandles.countguard startIndex < allCandles.count else { return }let endIndex = min(startIndex + pageSize, allCandles.count)let newCandles = Array(allCandles[startIndex..<endIndex])// 模拟网络请求延迟DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) {DispatchQueue.main.async {self.loadedCandles.append(contentsOf: newCandles)completion(self.loadedCandles)}}}func visibleCandles(for range: ClosedRange<Int>) -> [Candle] {guard !loadedCandles.isEmpty else { return [] }let start = max(range.lowerBound, 0)let end = min(range.upperBound, loadedCandles.count - 1)return Array(loadedCandles[start...end])}
}

2. 离屏渲染优化

class StockChartView: UIView {private var renderLayer: CALayer?override func draw(_ rect: CGRect) {if renderLayer == nil {renderOffscreen()}}private func renderOffscreen() {DispatchQueue.global(qos: .userInitiated).async {let format = UIGraphicsImageRendererFormat()format.scale = UIScreen.main.scalelet renderer = UIGraphicsImageRenderer(size: self.bounds.size, format: format)let image = renderer.image { context inself.drawContent(context: context.cgContext)}DispatchQueue.main.async {let layer = CALayer()layer.contents = image.cgImagelayer.frame = self.boundsself.layer.addSublayer(layer)self.renderLayer = layer}}}private func drawContent(context: CGContext) {// 绘制逻辑...}
}

3. 手势交互优化

class InteractiveStockChartView: StockChartView {private var panStart: CGPoint = .zeroprivate var visibleCandleCount: Int = 50override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {super.touchesBegan(touches, with: event)panStart = touches.first?.location(in: self) ?? .zero}override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {guard let touchPoint = touches.first?.location(in: self) else { return }let deltaX = panStart.x - touchPoint.xlet candleDelta = Int(deltaX / (candleWidth + candleSpacing))if abs(candleDelta) > 0 {// 更新可见范围visibleCandleCount = max(10, min(200, visibleCandleCount + candleDelta))setNeedsDisplay()panStart = touchPoint}}@objc func handlePinch(_ gesture: UIPinchGestureRecognizer) {let scale = gesture.scalelet newCount = Int(Double(visibleCandleCount) / scaleif gesture.state == .changed {visibleCandleCount = max(10, min(500, newCount))setNeedsDisplay()}}
}

四、高级功能实现

1. 十字线光标

class CrosshairView: UIView {var currentPosition: CGPoint? {didSet { setNeedsDisplay() }}override func draw(_ rect: CGRect) {guard let position = currentPosition else { return }guard let context = UIGraphicsGetCurrentContext() else { return }// 水平线context.setStrokeColor(UIColor.white.withAlphaComponent(0.7).cgColor)context.setLineWidth(0.5)context.setLineDash(phase: 0, lengths: [5, 5])context.move(to: CGPoint(x: 0, y: position.y))context.addLine(to: CGPoint(x: bounds.width, y: position.y))// 垂直线context.move(to: CGPoint(x: position.x, y: 0))context.addLine(to: CGPoint(x: position.x, y: bounds.height))context.strokePath()// 信息框drawInfoBox(at: position, in: context)}private func drawInfoBox(at position: CGPoint, in context: CGContext) {let boxWidth: CGFloat = 120let boxHeight: CGFloat = 80let boxX = position.x > bounds.width / 2 ? position.x - boxWidth - 10 : position.x + 10let boxY = position.y > bounds.height / 2 ? position.y - boxHeight : position.ylet boxRect = CGRect(x: boxX, y: boxY, width: boxWidth, height: boxHeight)// 背景context.setFillColor(UIColor.systemBackground.withAlphaComponent(0.9).cgColor)context.fill(boxRect)// 边框context.setStrokeColor(UIColor.systemGray.cgColor)context.setLineWidth(1)context.stroke(boxRect)// 绘制文本信息let paragraphStyle = NSMutableParagraphStyle()paragraphStyle.alignment = .leftlet attributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 12),.paragraphStyle: paragraphStyle,.foregroundColor: UIColor.label]let dateText = "日期: 2023-06-15"let priceText = "价格: 150.25"let volumeText = "成交量: 1.2M"dateText.draw(at: CGPoint(x: boxX + 8, y: boxY + 8), withAttributes: attributes)priceText.draw(at: CGPoint(x: boxX + 8, y: boxY + 28), withAttributes: attributes)volumeText.draw(at: CGPoint(x: boxX + 8, y: boxY + 48), withAttributes: attributes)}
}

2. 多时间周期切换

enum ChartTimeframe: String, CaseIterable {case oneMinute = "1分"case fiveMinutes = "5分"case fifteenMinutes = "15分"case thirtyMinutes = "30分"case oneHour = "1小时"case fourHours = "4小时"case oneDay = "日线"case oneWeek = "周线"case oneMonth = "月线"var interval: TimeInterval {switch self {case .oneMinute: return 60case .fiveMinutes: return 300case .fifteenMinutes: return 900case .thirtyMinutes: return 1800case .oneHour: return 3600case .fourHours: return 14400case .oneDay: return 86400case .oneWeek: return 604800case .oneMonth: return 2592000 // 近似值}}
}class TimeframeSelector: UIView {var selectedTimeframe: ChartTimeframe = .oneDay {didSet { updateSelection() }}private var buttons: [UIButton] = []override init(frame: CGRect) {super.init(frame: frame)setupView()}private func setupView() {let stackView = UIStackView()stackView.axis = .horizontalstackView.distribution = .fillEquallystackView.spacing = 8for timeframe in ChartTimeframe.allCases {let button = UIButton(type: .system)button.setTitle(timeframe.rawValue, for: .normal)button.titleLabel?.font = UIFont.systemFont(ofSize: 14)button.addTarget(self, action: #selector(timeframeTapped(_:)), for: .touchUpInside)button.tag = ChartTimeframe.allCases.firstIndex(of: timeframe) ?? 0buttons.append(button)stackView.addArrangedSubview(button)}addSubview(stackView)stackView.translatesAutoresizingMaskIntoConstraints = falseNSLayoutConstraint.activate([stackView.topAnchor.constraint(equalTo: topAnchor),stackView.bottomAnchor.constraint(equalTo: bottomAnchor),stackView.leadingAnchor.constraint(equalTo: leadingAnchor),stackView.trailingAnchor.constraint(equalTo: trailingAnchor)])updateSelection()}private func updateSelection() {for (index, button) in buttons.enumerated() {let isSelected = ChartTimeframe.allCases[index] == selectedTimeframebutton.tintColor = isSelected ? .systemBlue : .systemGraybutton.backgroundColor = isSelected ? .systemBlue.withAlphaComponent(0.1) : .clearbutton.layer.cornerRadius = 4}}@objc private func timeframeTapped(_ sender: UIButton) {selectedTimeframe = ChartTimeframe.allCases[sender.tag]}
}

五、架构设计建议

1、分层架构设计:

┌───────────────────────────────┐
│           UI Layer            │
│  - Chart Views (UIKit/SwiftUI)│
│  - Gesture Handlers           │
│  - View Controllers           │
└──────────────┬────────────────┘│
┌──────────────▼───────────────┐
│         Domain Layer         │
│  - Technical Indicators (MA, │
│    MACD, Bollinger Bands)    │
│  - Data Transformations      │
└──────────────┬───────────────┘│
┌──────────────▼───────────────┐
│         Data Layer           │
│  - Network Service (API)     │
│  - Database (CoreData/Realm) │
│  - Caching Mechanism         │
└──────────────────────────────┘

UI Layer:包含图表视图(UIKit/SwiftUI)、手势处理程序和视图控制器。
Domain Layer:包含技术指标(如MA、MACD、布林带)和数据转换。
Data Layer:包含网络服务(API)、数据库(CoreData/Realm)和缓存机制。
它们之间的关系是:UI层依赖于领域层,领域层依赖于数据层。

2、性能优化总结表:

优化点技术方案适用场景
大数据量分页加载 + 增量渲染历史数据加载
实时更新差异更新 + 增量绘制实时行情
复杂指标后台计算 + 缓存结果MACD/布林带等
流畅交互离屏渲染 + GPU加速手势缩放平移
内存优化对象复用 + 惰性加载移动设备限制

3、跨平台适配方案:

#if os(iOS)
import UIKit
typealias ViewRepresentable = UIView
#elseif os(macOS)
import AppKit
typealias ViewRepresentable = NSView
#endifclass CrossPlatformChartView: ViewRepresentable {#if os(iOS)override init(frame: CGRect) {super.init(frame: frame)commonInit()}#elseif os(macOS)override init(frame frameRect: NSRect) {super.init(frame: frameRect)commonInit()}#endifprivate func commonInit() {// 共享初始化代码}#if os(iOS)override func draw(_ rect: CGRect) {super.draw(rect)drawContent()}#elseif os(macOS)override func draw(_ dirtyRect: NSRect) {super.draw(dirtyRect)drawContent()}#endifprivate func drawContent() {// 共享绘制逻辑}
}

六、推荐方案选择

1、根据需求选择方案

需求场景推荐方案理由
简单展示SwiftUI + Canvas开发快,代码简洁
复杂交互UIKit + Core Graphics性能好,控制精细
多技术指标Charts 第三方库内置多种指标实现
跨平台SwiftUI + 条件编译支持iOS/macOS
实时行情Core Animation高效增量更新

2、性能对比表

方案10K数据点渲染手势流畅度内存占用开发效率
Core Graphics300ms⭐⭐⭐⭐⭐⭐⭐
SwiftUI Canvas800ms⭐⭐⭐⭐⭐⭐⭐⭐
Charts 库500ms⭐⭐⭐⭐中高⭐⭐⭐⭐⭐
Core Animation100ms⭐⭐⭐⭐⭐⭐⭐⭐

最终建议

  1. 对于专业交易应用,使用 Core Graphics + 自定义手势 实现最佳性能和体验
  2. 对于普通金融应用,使用 Charts 库 快速实现丰富功能
  3. 对于 SwiftUI 项目,使用 Canvas + 异步绘制 平衡性能与开发效率

股票图的实现需要平衡性能、功能和用户体验。建议从简单实现开始,逐步添加技术指标和交互功能,同时做好性能监控和优化。

http://www.xdnf.cn/news/15441.html

相关文章:

  • python的形成性考核管理系统
  • 并发-原子变量类
  • 【MCU控制 初级手札】1.1 电阻
  • 现代CSS实战:用变量与嵌套重构可维护的前端样式
  • 使用 Java 获取 PDF 页面信息(页数、尺寸、旋转角度、方向、标签与边框)
  • Flink双流实时对账
  • 大语言模型零样本情感分析实战:无需机器学习训练,96%准确率实现指南
  • 云手机隐私保护指南:如何保障账号与数据的云端安全?
  • 虚拟机删除操作
  • IELTS 阅读C15-test1-passage 2 复盘
  • React源码6 三大核心模块之一:commit, finishConcurrentRender函数
  • 24.找到列表中最大或最小值的索引
  • Pitaya 是一个简单、快速、轻量级的游戏服务器框架,它为分布式多人游戏和服务器端应用程序提供了一个基本的开发框架
  • 优雅的Java:01.数据更新如何更优雅
  • Python学习之路(十二)-开发和优化处理大数据量接口
  • 从springcloud-gateway了解同步和异步,webflux webMvc、共享变量
  • S7-200 SMART PLC:不同CPU及数字量 IO 接线全解析
  • 构建强大的物联网架构所需了解的一切
  • Janitor AI重塑人机交互的沉浸式智能体验
  • 大型语言模型(LLM)的技术面试题
  • 【机器人】REGNav 具身导航 | 跨房间引导 | 图像目标导航 AAAI 2025
  • 【算法-BFS 解决最短路问题】探索BFS在图论中的应用:最短路径问题的高效解法
  • docker停止所有容器和删除所有镜像
  • 【Docker基础】Dockerfile指令速览:高级构建指令详解
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十六课——图像五行缓存的FPGA实现
  • 常见的 Commit 描述 分类及示例
  • 2025-07-15通过边缘线检测图像里的主体有没有出血
  • 2025-07-15 李沐深度学习6——Softmax回归
  • 实测两款效率工具:驾考刷题和证件照处理的免费方案
  • vscode里面怎么配置ssh步骤