【问题标题】:Writing on existing files in a filesystem database写入文件系统数据库中的现有文件
【发布时间】:2019-09-27 16:09:04
【问题描述】:

我有一个函数可以每隔几分钟在 ~8000 个具有固定名称的 .md 文件上写入 ~120Kb-150Kb HTML 和元数据:

a-agilent-technologies-healthcare-nyse-us-39d4
aa-alcoa-basic-materials-nyse-us-159a
aaau-perth-mint-physical-gold--nyse-us-8ed9
aaba-altaba-financial-services-nasdaq-us-26f5
aac-healthcare-nyse-us-e92a
aadr-advisorshares-dorsey-wright-adr--nyse-us-d842
aal-airlines-industrials-nasdaq-us-29eb
  • 如果文件不存在,它会很快生成/写入。
  • 但是,如果文件存在,它的运行速度也会慢得多,因为现有文件携带约 150KB 的数据。

我该如何解决这个问题?

我是否在同一目录中生成一个具有新名称的新文件,并在 for 循环中取消链接旧文件?

或者我是否生成一个新文件夹并写入所有文件然后我取消链接以前的目录?这种方法的问题在于,有时 90% 的文件都被重写,而有些文件保持不变。


代码

这个函数在for循环中被调用,你可以在link看到它

public static function writeFinalStringOnDatabase($equity_symbol, $md_file_content, $no_extension_filename)
{
    /**
     *@var is the MD file content with meta and entire HTML
     */
    $md_file_content = $md_file_content . ConfigConstants::NEW_LINE . ConfigConstants::NEW_LINE;
    $dir = __DIR__ . ConfigConstants::DIR_FRONT_SYMBOLS_MD_FILES; // symbols front directory
    $new_filename = EQ::generateFileNameFromLeadingURL($no_extension_filename, $dir);

    if (file_exists($new_filename)) {
        if (is_writable($new_filename)) {
            file_put_contents($new_filename, $md_file_content);
            if (EQ::isLocalServer()) {
                echo $equity_symbol . " ???? " . ConfigConstants::NEW_LINE;
            }

        } else {
            if (EQ::isLocalServer()) {
                echo $equity_symbol . " symbol MD file is not writable in " . __METHOD__ . " ???? Maybe, check permissions!" . ConfigConstants::NEW_LINE;
            }
        }
    } else {
        $fh = fopen($new_filename, 'wb');
        fwrite($fh, $md_file_content);
        fclose($fh);
        if (EQ::isLocalServer()) {
            echo $equity_symbol . " front md file does not exit in " . __METHOD__ . " It's writing on the database now ????" . ConfigConstants::NEW_LINE;
        }

    }

}

【问题讨论】:

  • 如果file_put_contentsfwrite 作用相同,为什么还要使用它们?或者让我这样说:你为什么有if (file_exists($new_filename))
  • 如果您每隔几分钟生成(大部分)相同的 8000 个文件,在我看来,更好的解决方案是根据要求即时生成它们。 (或者他们不是通过网络请求的?)
  • 在 Total 中听起来是个坏主意,因为磁盘分区可能会变慢,然后一个目录中存在许多文件,请参阅 stackoverflow.com/questions/2994544/…

标签: php database file database-design filesystems


【解决方案1】:

我已经多年没有使用 PHP 编程了,但是今天这个问题引起了我的兴趣。 :D

建议

我该如何解决这个问题? 我是否在同一目录中生成一个具有新名称的新文件,并在 for 循环中取消链接旧文件?

再次使用 3 个朋友 fopen()fwrite()fclose(),因为 fwrite 也会覆盖现有文件的全部内容。

if (file_exists($new_filename)) {
    if (is_writable($new_filename)) {
        $fh = fopen($new_filename,'wb');
        fwrite($fh, $md_file_content);
        fclose($fh);

        if (EQ::isLocalServer()) {
            echo $equity_symbol . " ? " . ConfigConstants::NEW_LINE;
        }
    } else {
        if (EQ::isLocalServer()) {
            echo $equity_symbol . " symbol MD file is not writable in " . __METHOD__ . " ? Maybe, check permissions!" . ConfigConstants::NEW_LINE;
        }
    }
} else {
    $fh = fopen($new_filename, 'wb');
    fwrite($fh, $md_file_content);
    fclose($fh);
    if (EQ::isLocalServer()) {
        echo $equity_symbol . " front md file does not exit in " . __METHOD__ . " It's writing on the database now ?" . ConfigConstants::NEW_LINE;
    }
}

为了DRY原则:

// It's smart to put the logging and similar tasks in a separate function, 
// after you end up writing the same thing over and over again.
public static function log($content)
{
    if (EQ::isLocalServer()) {
        echo $content;
    }
}

public static function writeFinalStringOnDatabase($equity_symbol, $md_file_content, $no_extension_filename)
{
    $md_file_content = $md_file_content . ConfigConstants::NEW_LINE . ConfigConstants::NEW_LINE;
    $dir = __DIR__ . ConfigConstants::DIR_FRONT_SYMBOLS_MD_FILES; // symbols front directory
    $new_filename = EQ::generateFileNameFromLeadingURL($no_extension_filename, $dir);
    $file_already_exists = file_exists($new_filename);

    if ($file_already_exists && !is_writable($new_filename)) {
        EQ::log($equity_symbol . " symbol MD file is not writable in " . __METHOD__ . " ? Maybe, check permissions!" . ConfigConstants::NEW_LINE);
    } else {
        $fh = fopen($new_filename,'wb'); // you should also check whether fopen succeeded
        fwrite($fh, $md_file_content); // you should also check whether fwrite succeeded

        if ($file_already_exists) {
            EQ::log($equity_symbol . " ? " . ConfigConstants::NEW_LINE);
        } else {
            EQ::log($equity_symbol . " front md file does not exit in " . __METHOD__ . " It's writing on the database now ?" . ConfigConstants::NEW_LINE);
        }

        fclose($fh);
    }
}

可能的原因

tl;dr 由于使用了the Zend string API,因此开销很大。

官方PHP manual说:

file_put_contents() 等同于依次调用fopen()fwrite()fclose() 将数据写入文件。

但是,如果您查看source code of PHP on GitHub,您会发现file_put_contents()fwrite() 中的“写入数据”部分略有不同。

  • fwrite 函数中,直接访问原始输入数据 (= $md_file_content) 以便将缓冲区数据写入流上下文:

    Line 1171:

ret = php_stream_write(stream, input, num_bytes);
  • 另一方面,在file_put_contents 函数中使用了the Zend string API(我以前从未听说过)。 由于某种原因,这里封装了输入数据和长度。

    Line 662

numbytes = php_stream_write(stream, Z_STRVAL_P(data), Z_STRLEN_P(data));

(如果您有兴趣,Z_STR.... 宏定义为 here)。

所以,我怀疑可能是 Zend 字符串 API 在使用 file_put_contents 时造成了开销。


旁注

起初我以为每个file_put_contents() 调用都会创建一个新的流上下文,因为与创建上下文相关的行也略有不同:

PHP_NAMED_FUNCTION(php_if_fopen)(Reference):

context = php_stream_context_from_zval(zcontext, 0);

PHP_FUNCTION(file_put_contents)(Reference):

context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);

但是,仔细观察,php_stream_context_from_zval 调用是使用相同的参数有效地进行的,即第一个参数zcontextnull,并且由于您没有将任何flags 传递给file_put_contents , flags & PHP_FILE_NO_DEFAULT_CONTEXT 也变为 0 并作为第二个参数传递。

所以,我猜default stream context 在每次通话时都会在这里重复使用。由于它显然是 persistent 类型的流,因此在 php_stream_close() 调用之后不会释放它。 因此,正如德国人所说,Fazit 显然没有额外的开销,或者在这两种情况下创建或重用流上下文的开销相同。

感谢您的阅读。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 1970-01-01
    • 2020-02-17
    • 2013-03-28
    • 2012-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多