跳到主要内容
Python 内存泄漏追踪实战:tracemalloc 与 objgraph 深度解析 | 极客日志
Python 算法
Python 内存泄漏追踪实战:tracemalloc 与 objgraph 深度解析 综述由AI生成 Python 内存泄漏的诊断与解决方案。内容涵盖内存泄漏原理、tracemalloc 和 objgraph 两大工具的基础使用与高级技巧。通过 Web 应用、Django 等实战案例,展示了如何定位循环引用、缓存无限增长等问题。此外,还提供了标准调试流程、生产环境低开销监控方案以及防御性编程建议,帮助开发者有效识别并修复内存问题,保障服务稳定性。
RedisGeek 发布于 2026/3/23 更新于 2026/5/10 2.6K 浏览Python 内存泄漏追踪实战:tracemalloc 与 objgraph 深度解析
引言:当程序变成'内存黑洞'
凌晨三点,我被运维的电话吵醒:'你们的数据处理服务又崩了!内存占用从 2GB 飙到 32GB,服务器直接 OOM 重启!'这已经是本月第三次了。
那是我职业生涯中最难熬的一周。白天正常运行的服务,到了晚上就像失控的野兽,疯狂吞噬内存。我尝试了所有能想到的方法:检查日志、审查代码、增加内存限制……问题依旧。直到我掌握了 tracemalloc 和 objgraph 这两大利器,才终于揪出了隐藏在缓存层中的内存泄漏元凶。
今天,我将通过真实案例,带你系统掌握 Python 内存泄漏的诊断与解决方案。无论你是刚遇到内存问题的新手,还是想深化调优技能的资深开发者,这篇文章都将成为你的实战手册。
一、内存泄漏基础:理解问题本质
1.1 什么是内存泄漏?
在 Python 中,内存泄漏指的是:程序持续分配内存但无法释放已不再使用的对象,导致可用内存逐渐减少 。
class DataCache :
def __init__ (self ):
self ._cache = {}
def add_data (self, key, value ):
self ._cache[key] = value
def process_request (self, request_id, data ):
self .add_data(request_id, data)
return f"Processed {request_id} "
cache = DataCache()
for i in range (1000000 ):
cache.process_request(f"req_{i} " , "x" *1000 )
1.2 Python 的内存管理机制
Python 使用**引用计数 + 垃圾回收(GC)**机制管理内存:
import sys
obj = [ , , ]
( )
ref1 = obj
( )
ref1
( )
:
( ):
.value = value
. =
node1 = Node( )
node2 = Node( )
node1. = node2
node2. = node1
node1, node2
1
2
3
print
f"初始引用计数:{sys.getrefcount(obj)-1 } "
print
f"增加引用后:{sys.getrefcount(obj)-1 } "
del
print
f"删除引用后:{sys.getrefcount(obj)-1 } "
class
Node
def
__init__
self, value
self
self
next
None
1
2
next
next
del
1.3 常见内存泄漏场景
global_logs = []
def log_event (event ):
global_logs.append(event)
def create_handler (large_data ):
def handler ():
return len (large_data)
return handler
class FileProcessor :
def __init__ (self, filename ):
self .file = open (filename)
def process (self ):
return self .file.read()
cache = {}
def get_or_compute (key ):
if key not in cache:
cache[key] = expensive_computation(key)
return cache[key]
def expensive_computation (key ):
return [0 ]*1000000
二、tracemalloc:Python 内置的内存追踪器
2.1 基础使用与快照对比 import tracemalloc
import linecache
def display_top_memory (snapshot, key_type='lineno' , limit=10 ):
"""显示内存占用 Top N"""
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False , "<frozen importlib._bootstrap>" ),
tracemalloc.Filter(False , "<unknown>" ),
))
top_stats = snapshot.statistics(key_type)
print (f"\n{'=' *70 } " )
print (f"Top {limit} 内存占用(按 {key_type} 排序)" )
print (f"{'=' *70 } " )
for index, stat in enumerate (top_stats[:limit], 1 ):
frame = stat.traceback[0 ]
filename = frame.filename
lineno = frame.lineno
line = linecache.getline(filename, lineno).strip()
print (f"\n#{index} : {filename} :{lineno} " )
print (f" {line} " )
print (f" 大小:{stat.size / 1024 /1024 :.1 f} MB" )
print (f" 数量:{stat.count} 个对象" )
def memory_leak_example ():
"""模拟内存泄漏"""
tracemalloc.start()
snapshot1 = tracemalloc.take_snapshot()
leaked_objects = []
for i in range (10000 ):
leaked_objects.append([0 ]*1000 )
snapshot2 = tracemalloc.take_snapshot()
print ("\n初始状态内存占用:" )
display_top_memory(snapshot1, limit=5 )
print ("\n执行后内存占用:" )
display_top_memory(snapshot2, limit=5 )
top_stats = snapshot2.compare_to(snapshot1, 'lineno' )
print (f"\n{'=' *70 } " )
print ("内存增量分析(Top 10)" )
print (f"{'=' *70 } " )
for stat in top_stats[:10 ]:
print (f"\n{stat} " )
if stat.count_diff > 0 :
print (f" ⚠️ 新增对象:{stat.count_diff} 个" )
print (f" ⚠️ 内存增加:{stat.size_diff / 1024 /1024 :.2 f} MB" )
tracemalloc.stop()
memory_leak_example()
2.2 实战案例:Web 应用内存泄漏诊断 import tracemalloc
from flask import Flask, request
import time
app = Flask(__name__)
request_cache = {}
class MemoryMonitor :
"""内存监控装饰器"""
def __init__ (self ):
self .snapshots = []
tracemalloc.start()
def capture_snapshot (self, label ):
"""捕获内存快照"""
snapshot = tracemalloc.take_snapshot()
self .snapshots.append((label, snapshot, time.time()))
def analyze_leak (self, threshold_mb=10 ):
"""分析内存泄漏"""
if len (self .snapshots) < 2 :
print ("需要至少两个快照进行对比" )
return
for i in range (1 , len (self .snapshots)):
label1, snapshot1, time1 = self .snapshots[i-1 ]
label2, snapshot2, time2 = self .snapshots[i]
top_stats = snapshot2.compare_to(snapshot1, 'lineno' )
total_increase = sum (stat.size_diff for stat in top_stats if stat.size_diff > 0 )
increase_mb = total_increase / 1024 /1024
print (f"\n{'=' *70 } " )
print (f"对比:{label1} -> {label2} " )
print (f"时间差:{time2 - time1:.2 f} 秒" )
print (f"内存增加:{increase_mb:.2 f} MB" )
print (f"{'=' *70 } " )
if increase_mb > threshold_mb:
print ("⚠️ 检测到可能的内存泄漏!" )
print ("\n内存增长最多的代码位置:" )
for stat in top_stats[:5 ]:
if stat.size_diff > 0 :
print (f"\n{stat.traceback.format ()[0 ]} " )
print (f" 增加:{stat.size_diff / 1024 /1024 :.2 f} MB" )
print (f" 新对象:{stat.count_diff} 个" )
monitor = MemoryMonitor()
@app.before_request
def before_request ():
"""请求前捕获快照"""
request.start_time = time.time()
@app.after_request
def after_request (response ):
"""请求后分析内存"""
if hasattr (request, 'start_time' ):
elapsed = time.time() - request.start_time
if elapsed > 0.1 :
monitor.capture_snapshot(f"After {request.path} " )
return response
@app.route('/api/process' )
def process_data ():
"""模拟处理请求(有内存泄漏)"""
request_id = request.args.get('id' , 'unknown' )
large_data = [0 ]*100000
request_cache[request_id] = large_data
return {'status' : 'ok' , 'cached_requests' : len (request_cache)}
@app.route('/api/analyze' )
def analyze_memory ():
"""触发内存分析"""
monitor.analyze_leak(threshold_mb=5 )
return {'status' : 'analysis_complete' }
if __name__ == '__main__' :
with app.test_client() as client:
monitor.capture_snapshot("Initial" )
for i in range (100 ):
client.get(f'/api/process?id={i} ' )
monitor.capture_snapshot("After 100 requests" )
for i in range (100 , 200 ):
client.get(f'/api/process?id={i} ' )
monitor.capture_snapshot("After 200 requests" )
client.get('/api/analyze' )
2.3 高级技巧:追踪特定对象 import tracemalloc
import gc
class ObjectTracker :
"""追踪特定类型对象的内存分配"""
@staticmethod
def track_allocations (target_type, duration_seconds=10 ):
"""追踪指定时间内的对象分配"""
tracemalloc.start()
initial_snapshot = tracemalloc.take_snapshot()
print (f"开始追踪 {target_type.__name__} 对象,持续 {duration_seconds} 秒..." )
time.sleep(duration_seconds)
final_snapshot = tracemalloc.take_snapshot()
tracemalloc.stop()
top_stats = final_snapshot.compare_to(initial_snapshot, 'lineno' )
print (f"\n{target_type.__name__} 对象内存分配分析:" )
for stat in top_stats[:10 ]:
if target_type.__name__ in str (stat):
print (f"\n{stat} " )
@staticmethod
def find_object_sources (obj ):
"""查找对象的引用来源"""
print (f"\n{'=' *70 } " )
print (f"分析对象:{type (obj).__name__} at {hex (id (obj))} " )
print (f"{'=' *70 } " )
referrers = gc.get_referrers(obj)
print (f"\n找到 {len (referrers)} 个引用者:" )
for i, ref in enumerate (referrers[:10 ], 1 ):
ref_type = type (ref).__name__
print (f"\n#{i} 引用者类型:{ref_type} " )
if isinstance (ref, dict ):
for key, value in ref.items():
if value is obj:
print (f" 字典键:{key} " )
break
elif isinstance (ref, (list , tuple )):
print (f" 容器长度:{len (ref)} " )
second_level = gc.get_referrers(ref)
if second_level:
print (f" 被 {len (second_level)} 个对象引用" )
class LeakyCache :
def __init__ (self ):
self .data = {}
def add (self, key, value ):
self .data[key] = value
cache = LeakyCache()
for i in range (1000 ):
cache.add(f"key_{i} " , [0 ]*10000 )
ObjectTracker.find_object_sources(cache.data)
三、objgraph:可视化对象关系图谱
3.1 安装与基础使用
pip install objgraph
sudo apt-get install graphviz
brew install graphviz
import objgraph
import gc
def analyze_object_types ():
"""分析当前内存中的对象类型"""
print ("\n内存中最多的对象类型(Top 20):" )
objgraph.show_most_common_types(limit=20 )
def track_object_growth ():
"""追踪对象数量增长"""
gc.collect()
objgraph.show_growth(limit=10 )
leaked_list = []
for i in range (10000 ):
leaked_list.append({'data' : [0 ]*100 })
print ("\n执行操作后的对象增长:" )
objgraph.show_growth(limit=10 )
analyze_object_types()
track_object_growth()
3.2 实战案例:追踪循环引用 import objgraph
import os
class Node :
"""链表节点(可能产生循环引用)"""
def __init__ (self, value ):
self .value = value
self .next = None
self .prev = None
class CircularList :
"""循环链表(演示内存泄漏)"""
def __init__ (self ):
self .head = None
self .size = 0
def add (self, value ):
new_node = Node(value)
if not self .head:
self .head = new_node
new_node.next = new_node
new_node.prev = new_node
else :
tail = self .head.prev
tail.next = new_node
new_node.prev = tail
new_node.next = self .head
self .head.prev = new_node
self .size += 1
def create_circular_references ():
"""创建包含循环引用的对象"""
lists = []
for i in range (10 ):
circular_list = CircularList()
for j in range (100 ):
circular_list.add(f"data_{i} _{j} " )
lists.append(circular_list)
return lists
def visualize_references ():
"""生成对象引用关系图"""
leaked_lists = create_circular_references()
target = leaked_lists[0 ]
print ("\n生成对象引用关系图..." )
output_file = '/tmp/backrefs.png'
objgraph.show_backrefs([target], max_depth=3 , filename=output_file, refcounts=True )
print (f"反向引用图已保存:{output_file} " )
output_file = '/tmp/refs.png'
objgraph.show_refs([target.head], max_depth=3 , filename=output_file, refcounts=True )
print (f"前向引用图已保存:{output_file} " )
return leaked_lists
leaked = visualize_references()
print ("\n详细引用链分析:" )
objgraph.show_chain(
objgraph.find_backref_chain(
leaked[0 ], objgraph.is_proper_module
),
filename='/tmp/chain.png'
)
3.3 综合案例:Django 应用内存泄漏诊断 import objgraph
import tracemalloc
import gc
from functools import wraps
class MemoryLeakDetector :
"""内存泄漏检测器(生产环境友好)"""
def __init__ (self, threshold_mb=50 ):
self .threshold_mb = threshold_mb
self .baseline = None
self .snapshots = []
def start_monitoring (self ):
"""开始监控"""
gc.collect()
tracemalloc.start()
self .baseline = tracemalloc.take_snapshot()
print ("✅ 内存监控已启动" )
def check_memory (self, label="checkpoint" ):
"""检查内存状态"""
if not self .baseline:
print ("⚠️ 请先调用 start_monitoring()" )
return
gc.collect()
current = tracemalloc.take_snapshot()
self .snapshots.append((label, current))
stats = current.compare_to(self .baseline, 'lineno' )
total_increase = sum (s.size_diff for s in stats if s.size_diff > 0 )
increase_mb = total_increase / 1024 /1024
print (f"\n{'=' *70 } " )
print (f"检查点:{label} " )
print (f"内存增长:{increase_mb:.2 f} MB" )
if increase_mb > self .threshold_mb:
print ("🚨 检测到内存泄漏!" )
self ._analyze_leak(stats)
else :
print ("✅ 内存使用正常" )
print (f"{'=' *70 } " )
def _analyze_leak (self, stats ):
"""详细分析泄漏"""
print ("\n内存增长最多的位置(Top 10):" )
for i, stat in enumerate (stats[:10 ], 1 ):
if stat.size_diff > 0 :
print (f"\n#{i} : {stat.traceback.format ()[0 ]} " )
print (f" 增长:{stat.size_diff / 1024 /1024 :.2 f} MB" )
print (f" 对象:+{stat.count_diff} " )
print ("\n对象类型增长分析:" )
objgraph.show_growth(limit=10 )
def generate_report (self, output_dir='/tmp' ):
"""生成完整报告"""
print (f"\n生成内存泄漏报告..." )
print ("\n1. 当前内存对象类型分布:" )
objgraph.show_most_common_types(limit=15 )
print ("\n2. 查找可疑对象..." )
suspicious_types = ['dict' , 'list' , 'tuple' , 'set' ]
for obj_type in suspicious_types:
objects = objgraph.by_type(obj_type)
if len (objects) > 10000 :
print (f"\n⚠️ {obj_type} 对象数量异常:{len (objects)} " )
sample = objects[0 ] if objects else None
if sample:
output_file = os.path.join(output_dir, f'{obj_type} _refs.png' )
objgraph.show_refs([sample], filename=output_file, max_depth=2 )
print (f" 引用图已保存:{output_file} " )
if self .snapshots:
latest_label, latest_snapshot = self .snapshots[-1 ]
print (f"\n3. 最新快照分析 ({latest_label} ):" )
top_stats = latest_snapshot.statistics('lineno' )
print ("\n内存占用 Top 10:" )
for i, stat in enumerate (top_stats[:10 ], 1 ):
frame = stat.traceback[0 ]
print (f"\n#{i} : {frame.filename} :{frame.lineno} " )
print (f" 大小:{stat.size / 1024 /1024 :.2 f} MB" )
print (f" 对象数:{stat.count} " )
def detect_leak (detector ):
"""装饰器:自动检测函数执行后的内存变化"""
def decorator (func ):
@wraps(func )
def wrapper (*args, **kwargs ):
gc.collect()
before = tracemalloc.take_snapshot()
result = func(*args, **kwargs)
gc.collect()
after = tracemalloc.take_snapshot()
stats = after.compare_to(before, 'lineno' )
total_increase = sum (s.size_diff for s in stats if s.size_diff > 0 )
increase_mb = total_increase / 1024 /1024
if increase_mb > 1 :
print (f"\n⚠️ {func.__name__} 可能存在内存泄漏" )
print (f" 内存增长:{increase_mb:.2 f} MB" )
for stat in stats[:3 ]:
if stat.size_diff > 0 :
print (f" {stat} " )
return result
return wrapper
return decorator
detector = MemoryLeakDetector(threshold_mb=10 )
detector.start_monitoring()
@detect_leak(detector )
def process_large_dataset ():
"""模拟数据处理(有泄漏)"""
cache = {}
for i in range (50000 ):
cache[f"key_{i} " ] = [0 ]*1000
return len (cache)
result = process_large_dataset()
detector.check_memory("After processing" )
detector.generate_report()
四、实战调试流程与最佳实践
4.1 标准诊断流程 import tracemalloc
import objgraph
import gc
import psutil
import os
class MemoryDebugger :
"""内存调试完整工作流"""
@staticmethod
def step1_confirm_leak ():
"""步骤 1:确认是否真的有内存泄漏"""
print ("=" *70 )
print ("步骤 1: 确认内存泄漏" )
print ("=" *70 )
process = psutil.Process(os.getpid())
baseline = process.memory_info().rss / 1024 /1024
print (f"基线内存:{baseline:.2 f} MB" )
for iteration in range (5 ):
_ = [0 ]*1000000
gc.collect()
current = process.memory_info().rss / 1024 /1024
increase = current - baseline
print (f"迭代 {iteration +1 } : {current:.2 f} MB (+{increase:.2 f} MB)" )
if increase > 100 :
print ("⚠️ 确认内存持续增长,可能存在泄漏!" )
return True
print ("✅ 内存使用正常" )
return False
@staticmethod
def step2_locate_source ():
"""步骤 2:使用 tracemalloc 定位泄漏源"""
print ("\n" +"=" *70 )
print ("步骤 2: 定位泄漏源" )
print ("=" *70 )
tracemalloc.start()
snapshot1 = tracemalloc.take_snapshot()
leaked_data = []
for i in range (10000 ):
leaked_data.append([0 ]*1000 )
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno' )
print ("\n内存增长最多的代码位置:" )
for stat in top_stats[:5 ]:
if stat.size_diff > 0 :
print (f"\n{stat.traceback.format ()[0 ]} " )
print (f"增长:{stat.size_diff / 1024 /1024 :.2 f} MB" )
tracemalloc.stop()
@staticmethod
def step3_analyze_objects ():
"""步骤 3:使用 objgraph 分析对象关系"""
print ("\n" +"=" *70 )
print ("步骤 3: 分析对象关系" )
print ("=" *70 )
gc.collect()
print ("\n初始对象统计:" )
objgraph.show_growth(limit=10 )
global leaked_cache
leaked_cache = {}
for i in range (5000 ):
leaked_cache[i] = [0 ]*1000
print ("\n操作后对象增长:" )
objgraph.show_growth(limit=10 )
if leaked_cache:
sample_obj = list (leaked_cache.values())[0 ]
objgraph.show_backrefs([sample_obj], filename='/tmp/leak_backrefs.png' , max_depth=3 )
print ("\n引用图已生成:/tmp/leak_backrefs.png" )
@staticmethod
def step4_verify_fix ():
"""步骤 4:验证修复效果"""
print ("\n" +"=" *70 )
print ("步骤 4: 验证修复" )
print ("=" *70 )
tracemalloc.start()
before = tracemalloc.take_snapshot()
from collections import OrderedDict
class LRUCache :
def __init__ (self, max_size=1000 ):
self .cache = OrderedDict()
self .max_size = max_size
def set (self, key, value ):
if key in self .cache:
self .cache.move_to_end(key)
self .cache[key] = value
if len (self .cache) > self .max_size:
self .cache.popitem(last=False )
cache = LRUCache(max_size=1000 )
for i in range (10000 ):
cache.set (i, [0 ]*1000 )
after = tracemalloc.take_snapshot()
stats = after.compare_to(before, 'lineno' )
total_increase = sum (s.size_diff for s in stats if s.size_diff > 0 )
print (f"\n修复后内存增长:{total_increase / 1024 /1024 :.2 f} MB" )
if total_increase / 1024 /1024 < 10 :
print ("✅ 修复有效,内存控制在合理范围" )
else :
print ("⚠️ 仍需进一步优化" )
tracemalloc.stop()
if __name__ == '__main__' :
debugger = MemoryDebugger()
if debugger.step1_confirm_leak():
debugger.step2_locate_source()
debugger.step3_analyze_objects()
debugger.step4_verify_fix()
4.2 生产环境监控方案 import tracemalloc
import threading
import time
from datetime import datetime
class ProductionMemoryMonitor :
"""生产环境内存监控(低开销)"""
def __init__ (self, check_interval=300 , alert_threshold_mb=500 ):
self .check_interval = check_interval
self .alert_threshold_mb = alert_threshold_mb
self .running = False
self .thread = None
def start (self ):
"""启动监控线程"""
if self .running:
return
self .running = True
tracemalloc.start()
self .thread = threading.Thread(target=self ._monitor_loop, daemon=True )
self .thread.start()
print (f"✅ 内存监控已启动(每 {self.check_interval} 秒检查一次)" )
def stop (self ):
"""停止监控"""
self .running = False
if self .thread:
self .thread.join()
tracemalloc.stop()
print ("⏹ 内存监控已停止" )
def _monitor_loop (self ):
"""监控循环"""
baseline = None
while self .running:
try :
snapshot = tracemalloc.take_snapshot()
if baseline is None :
baseline = snapshot
else :
self ._check_memory(baseline, snapshot)
time.sleep(self .check_interval)
except Exception as e:
print (f"监控出错:{e} " )
def _check_memory (self, baseline, current ):
"""检查内存状态"""
stats = current.compare_to(baseline, 'lineno' )
total_increase = sum (s.size_diff for s in stats if s.size_diff > 0 )
increase_mb = total_increase / 1024 /1024
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S' )
if increase_mb > self .alert_threshold_mb:
print (f"\n🚨 [{timestamp} ] 内存告警!" )
print (f" 增长:{increase_mb:.2 f} MB" )
print (f" Top 3 增长位置:" )
for i, stat in enumerate (stats[:3 ], 1 ):
if stat.size_diff > 0 :
print (f" #{i} : {stat.traceback.format ()[0 ]} " )
print (f" +{stat.size_diff / 1024 /1024 :.2 f} MB" )
else :
print (f"✅ [{timestamp} ] 内存正常 (+{increase_mb:.2 f} MB)" )
monitor = ProductionMemoryMonitor(check_interval=10 , alert_threshold_mb=50 )
monitor.start()
try :
leaked = []
for i in range (100 ):
leaked.append([0 ]*100000 )
time.sleep(1 )
except KeyboardInterrupt:
pass
finally :
monitor.stop()
五、总结与最佳实践
5.1 工具选择决策树 发现内存持续增长 ↓
使用 psutil 确认物理内存增长 ↓
tracemalloc 定位代码位置
├─ 找到明确位置 → 修复代码
└─ 位置不明确 ↓
objgraph 分析对象关系
├─ 发现循环引用 → 使用弱引用或手动打破
├─ 发现缓存无限增长 → 添加 LRU 或 TTL
└─ 发现资源未关闭 → 使用上下文管理器
5.2 防御性编程建议
with open ('file.txt' ) as f:
data = f.read()
from functools import lru_cache
@lru_cache(maxsize=1000 )
def expensive_function (arg ):
return arg ** 2
import weakref
class Cache :
def __init__ (self ):
self ._cache = weakref.WeakValueDictionary()
def cleanup_old_data (cache, max_age_seconds=3600 ):
now = time.time()
to_delete = [
k for k, v in cache.items()
if now - v['timestamp' ] > max_age_seconds
]
for k in to_delete:
del cache[k]
def process_large_file (filename ):
with open (filename) as f:
for line in f:
yield process_line(line)
本文介绍了 Python 内存泄漏的基础概念及两种主流追踪工具的使用。通过 tracemalloc 可精确定位代码行级的内存增量,配合 objgraph 则能可视化对象间的引用关系,尤其适用于解决循环引用问题。文章提供了从开发环境诊断到生产环境监控的完整方案,并给出了防御性编程的最佳实践,帮助开发者构建更稳定的 Python 应用。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online