Function 函数中的日志文件变量会耗尽所有内存
我有一个简单的函数,它在一个脚本中多次使用,该脚本遍历一个目录并检查其中文件的年代Function 函数中的日志文件变量会耗尽所有内存,function,powershell,scope,Function,Powershell,Scope,我有一个简单的函数,它在一个脚本中多次使用,该脚本遍历一个目录并检查其中文件的年代 function log($Message) { $logFilePath = 'C:\logPath\myLog.txt' $date = Get-Date -Format 'yyyyMMddHHmmss' $logMessage = "{0}_{1}" -f $date,$Message if(Test-Path -Path $logFilePath) { $logFileConte
function log($Message) {
$logFilePath = 'C:\logPath\myLog.txt'
$date = Get-Date -Format 'yyyyMMddHHmmss'
$logMessage = "{0}_{1}" -f $date,$Message
if(Test-Path -Path $logFilePath) {
$logFileContent = Get-Content -Path $logFilePath
} else {
$logFileContent = ''
}
$logMessage,$logFileContent | Out-File -FilePath $logFilePath
}
我发现这会吃掉所有的内存。我不明白为什么。我认为一旦函数运行,函数中变量的作用域就会被破坏。我通过添加
Remove变量logMessage、logFileContent、logFilePath、,日期
到函数的最后,但我想知道如何解决这个ram问题,以及为什么函数中的变量不会自动销毁。我只想排除ram的使用是由文件的前置引起的。您是否尝试过不将日志内容存储在变量中?i、 e
$logMessage,(获取内容-路径$logFilePath)|输出文件-路径$logFilePath
编辑5/8/20-结果表明,预结束(有效完成时)并不像我想象的那么慢-它的顺序与使用-Append
的顺序相同。然而,它又长又难看,但是如果您真的需要在文件前加前缀,这是一种方法
我稍微修改了OP的代码,以便自动插入新行
函数日志前置{
param(
$content,
$filePath='C:\temp\myLogP.txt'
)
$file=获取项$filePath
如果(!$file.exists){
写入错误“$文件不存在”;
返回;
}
$filepath=$file.fullname;
$tmptoken=(获取位置).path+“\\u tmpfile”+$file.name;
详细写入“$tmptoken已创建为缓冲区”;
$tfs=[System.io.file]::创建($tmptoken);
$fs=[System.IO.File]::Open($File.fullname,[System.IO.FileMode]::Open,[System.IO.FileAccess]::ReadWrite);
试一试{
$date=获取日期-格式为“yyyyMMddHHmmss”
$logMessage=“{0}{1}`r`n”-f$date,$content
$msg=$logMessage.tochararray();
$tfs.write($msg,0,$msg.length);
$fs.position=0;
$fs.copyTo($tfs);
}
抓住{
编写详细的$\异常.Message;
}
最后{
$tfs.close();
$fs.close();
如果($error.count-等式0){
详细写入(“更新$filepath”);
[System.io.File]::删除($filepath);
[System.io.file]::Move($tmptoken,$filepath);
}
否则{
$error.clear();
[System.io.file]::删除($tmptoken);
}
}
}
这是我最初的答案,说明了如何使用秒表测试计时
当您在日志文件前加前缀时,您正在将整个日志文件读入内存,然后将其写回
您真的应该使用append-这将使脚本运行得更快
函数日志($Message){
$logFilePath='C:\logPath\myLog.txt'
$date=获取日期-格式为“yyyyMMddHHmmss”
$logMessage=“{0}{1}”-f$date,$Message
$logMessage |输出文件-文件路径$logFilePath-追加
}
编辑:为了让您相信在日志文件前加前缀是个坏主意,您可以在自己的系统上进行以下测试:
function logAppend($Message) {
$logFilePath = 'C:\temp\myLogA.txt'
$date = Get-Date -Format 'yyyyMMddHHmmss'
$logMessage = "{0}_{1}" -f $date,$Message
$logMessage | Out-File -FilePath $logFilePath -Append
}
function logPrepend($Message) {
$logFilePath = 'C:\temp\myLogP.txt'
$date = Get-Date -Format 'yyyyMMddHHmmss'
$logMessage = "{0}_{1}" -f $date,$Message
if(Test-Path -Path $logFilePath) {
$logFileContent = Get-Content -Path $logFilePath
} else {
$logFileContent = ''
}
$logMessage,$logFileContent | Out-File -FilePath $logFilePath
}
$processes = Get-Process
$stopwatch = [system.diagnostics.stopwatch]::StartNew()
foreach ($p in $processes)
{
logAppend($p.ProcessName)
}
$stopwatch.Stop()
$stopwatch.Elapsed
$stopwatch = [system.diagnostics.stopwatch]::StartNew()
foreach ($p in $processes)
{
logPrepend($p.ProcessName)
}
$stopwatch.Stop()
$stopwatch.Elapsed
我已经运行了好几次,直到日志文件中有几千行。
从1603到1925行,我的结果是:
附加:7.0167008 s
Prepend:21.7046793 s尽管如前所述,我很难相信此函数会占用您的内存,但您可以对其进行优化:
function log ([string]$Message) {
$logFilePath = 'C:\logPath\myLog.txt'
# prefix the message with the current date
$Message = "{0:yyyyMMddHHmmss}_{1}" -f (Get-Date), $Message
if (Test-Path -Path $logFilePath -PathType Leaf) {
# newest log entry on top: append current content
$Message = "{0}`r`n{1}" -f $Message, (Get-Content -Path $logFilePath -Raw)
}
Set-Content -Path $logFilePath -Value $Message
}
Powershell或.Net有一个垃圾收集器,因此释放内存不是即时的。此外,Powershell 7中的内存管理可能更好。我多次尝试重复您的函数,但内存使用率没有超过几百兆 可能有一种更有效的.net方法可以将一行前置到文件中: 我有一种奇怪的方式来写一行字。我不确定这对一个大文件有多好。对于700兆的文件,工作集内存保持在76兆
$c:file
one
two
three
$c:file = 'pre',$c:file
$c:file
pre
one
two
three
ps powershell
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
607 27 67448 76824 1.14 3652 3 powershell
函数将消息前置到日志文件(将其置于顶部),而append将其置于末尾)。日志文件为300 KB。我将尝试这个方法,但它没有回答我的问题“为什么函数中的变量没有自动销毁”。我已经更新了答案,根据js2010指出的答案,包含了一种更有效的方法来预处理文件。您是如何发现这个函数会占用所有RAM的?在我看来,这不仅仅是一个令人眼花缭乱的问题,我认为这个问题是由你的剧本中的其他地方引起的。您是否正在创建COM对象而不从内存中释放它们?或者使用.NET对象或Windows窗体而不使用.Dispose()销毁它们?虽然函数在顶部写新行,但它会消耗额外的内存,因为它必须读取文件的当前内容,然后通过将其重新创建到新数组中再次使用额外的内存,但是对于300kb的文件,这应该不会有任何问题。正如
Theo
指出的,您正在读取的文件越来越大。放弃你对内容预挂起的奇怪要求,切换到append,你的日志功能将停止使用不断增长的RAM。谢谢!我试图找到有关垃圾收集的更多信息,并找到了这篇有用的文章,其中的内容是[System.GC]::Collect()
启动垃圾收集,但不会等到垃圾收集完成,而[System.GC]::getTotalMemy($true)
将启动并等待垃圾收集