主頁 > 後端開發 > PHP 如何讀取大檔案

PHP 如何讀取大檔案

2020-09-16 12:18:57 後端開發

作為 PHP 開發人員,我們不需要擔心記憶體管理, PHP 引擎在我們背后進行了出色的清理作業,短暫執行背景關系的 web server 模型意味著即使是最草率的代碼也沒有持久的影響,

 

在極少數情況下,我們可能需要走出舒適的界限 — 例如,當我們嘗試在可以創建的最小 VPS 上為大型專案運行 Composer 時,或者需要在同樣小的服務器上讀取大檔案時,

 

這是我們將在本教程中討論的一個問題,

本教程的代碼可以在這里找到 GitHub.

衡量成功

唯一能確認我們對代碼所做改進是否有效的方式是:衡量一個糟糕的情況,然后對比我們已經應用改進后的衡量情況,換言之,除非我們知道 “解決方案” 能幫我們到什么程度 (如果有的話),否則我們并不知道它是否是一個解決方案,

我們可以關注兩個指標,首先是 CPU 使用率,我們要處理的程序運行得有多快或多慢?其次是記憶體使用率,腳本執行要占用多少記憶體?這些通常是成反比的 — 這意味著我們能夠以 CPU 使用率為代價減少記憶體的使用率,反之亦可,

在一個異步處理模型 (例如多行程或多執行緒 PHP 應用程式) 中,CPU 和記憶體使用率都是重要的考量,在傳統 PHP 架構中,任一達到服務器所限時這些通常都會成為一個麻煩,

測量 PHP 內部的 CPU 使用率是難以實作的,如果你確實關注這一塊,可用考慮在 Ubuntu 或 macOS 中使用類似于 top 的命令,對于 Windows,則可用考慮使用 Linux 子系統,這樣你就能夠在 Ubuntu 中使用 top 命令了,

在本教程中,我們將測量記憶體使用情況,我們將看一下 “傳統” 腳本會使用多少記憶體,我們也會實作一些優化策略并對它們進行度量,最后,我希望你能做一個合理的選擇,

以下是我們用于查看記憶體使用量的方法:

 

// formatBytes 方法取材于 php.net 檔案

memory_get_peak_usage();

function formatBytes($bytes, $precision = 2) {
    $units = array("b", "kb", "mb", "gb", "tb");

    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);

    $bytes /= (1 << (10 * $pow));

    return round($bytes, $precision) . " " . $units[$pow];
}

  

我們將在腳本的結尾處使用這些方法,以便于我們了解哪個腳本一次使用了最多的記憶體,

我們有什么選擇?

我們有許多方法來有效地讀取檔案,有以下兩種場景會使用到他們,我們可能希望同時讀取和處理所有資料,對處理后的資料進行輸出或者執行其他操作, 我們還可能希望對資料流進行轉換而不需要訪問到這些資料,

想象以下,對于第一種情況,如果我們希望讀取檔案并且把每 10,000 行的資料交給單獨的佇列進行處理,我們則需要至少把 10,000 行的資料加載到記憶體中,然后把它們交給佇列管理器(無論使用哪種),

對于第二種情況,假設我們想要壓縮一個 API 回應的內容,這個 API 回應特別大,雖然這里我們不關心它的內容是什么,但是我們需要確保它被以一種壓縮格式備份起來,

這兩種情況,我們都需要讀取大檔案,不同的是,第一種情況我們需要知道資料是什么,而第二種情況我們不關心資料是什么,接下來,讓我們來深入討論一下這兩種做法.

逐行讀取檔案

PHP 處理檔案的函式很多,讓我們將其中一些函式結合起來實作一個簡單的檔案閱讀器

// from memory.php

function formatBytes($bytes, $precision = 2) {
    $units = array("b", "kb", "mb", "gb", "tb");

    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);

    $bytes /= (1 << (10 * $pow));

    return round($bytes, $precision) . " " . $units[$pow];
}

print formatBytes(memory_get_peak_usage());
 

// from reading-files-line-by-line-1.php
function readTheFile($path) {
    $lines = [];
    $handle = fopen($path, "r");

    while(!feof($handle)) {
        $lines[] = trim(fgets($handle));
    }

    fclose($handle);
    return $lines;
}

readTheFile("shakespeare.txt");

require "memory.php";

  

我們正在閱讀一個包括莎士比亞全部著作的文本檔案,該檔案大小大約為 5.5 MB,記憶體使用峰值為 12.8 MB,現在,讓我們使用生成器來讀取每一行:

// from reading-files-line-by-line-2.php

function readTheFile($path) {
    $handle = fopen($path, "r");

    while(!feof($handle)) {
        yield trim(fgets($handle));
    }

    fclose($handle);
}

readTheFile("shakespeare.txt");

require "memory.php";

  

檔案大小相同,但是記憶體使用峰值為 393 KB,這個資料意義大不大,因為我們需要加入對檔案資料的處理,例如,當出現兩個空白行時,將檔案拆分為多個塊:

// from reading-files-line-by-line-3.php

$iterator = readTheFile("shakespeare.txt");

$buffer = "";

foreach ($iterator as $iteration) {
    preg_match("/\n{3}/", $buffer, $matches);

    if (count($matches)) {
        print ".";
        $buffer = "";
    } else {
        $buffer .= $iteration . PHP_EOL;
    }
}

require "memory.php";

  

有人猜測這次使用多少記憶體嗎?即使我們將文本檔案分為 126 個塊,我們仍然只使用 459 KB 的記憶體,鑒于生成器的性質,我們將使用的最大記憶體是在迭代中需要存盤最大文本塊的記憶體,在這種情況下,最大的塊是 101985 個字符,

生成器還有其他用途,但顯然它可以很好的讀取大型檔案,如果我們需要處理資料,生成器可能是最好的方法,

檔案之間的管道

在不需要處理資料的情況下,我們可以將檔案資料從一個檔案傳遞到另一個檔案,這通常稱為管道 (大概是因為除了兩端之外,我們看不到管道內的任何東西,當然,只要它是不透明的),我們可以通過流 (stream) 來實作,首先,我們撰寫一個腳本實作一個檔案到另一個檔案的傳輸,以便我們可以測量記憶體使用情況:

// from piping-files-1.php

file_put_contents(
    "piping-files-1.txt", file_get_contents("shakespeare.txt")
);

require "memory.php";

  

結果并沒有讓人感到意外,該腳本比其復制的文本檔案使用更多的記憶體來運行,這是因為腳本必須在記憶體中讀取整個檔案直到將其寫入另外一個檔案,對于小的檔案而言,這種操作是 OK 的,但是將其用于大檔案時,就不是那么回事了,

 

讓我們嘗試從一個檔案流式傳輸 (或管道傳輸) 到另一個檔案:

// from piping-files-2.php

$handle1 = fopen("shakespeare.txt", "r");
$handle2 = fopen("piping-files-2.txt", "w");

stream_copy_to_stream($handle1, $handle2);

fclose($handle1);
fclose($handle2);

require "memory.php";

  

這段代碼有點奇怪,我們打開兩個檔案的句柄,第一個處于讀取模式,第二個處于寫入模式,然后,我們從第一個復制到第二個,我們通過再次關閉兩個檔案來完成,當你知道記憶體使用為 393 KB 時,可能會感到驚訝,這個數字看起來很熟悉,這不就是利用生成器保存逐行讀取內容時所使用的記憶體嗎,這是因為 fgets 的第二個引數定義了每行要讀取的位元組數 (默認為 -1 或到達新行之前的長度),stream_copy_to_stream 的第三個引數是相同的(默認值完全相同),stream_copy_to_stream 一次從一個流讀取一行,并將其寫入另一流,由于我們不需要處理該值,因此它會跳過生成器產生值的部分

單單傳輸文字還不夠實用,所以考慮下其他例子,假設我們想從 CDN 輸出影像,可以用以下代碼來描述

// from piping-files-3.php

file_put_contents(
    "piping-files-3.jpeg", file_get_contents(
        "https://github.com/assertchris/uploads/raw/master/rick.jpg"
    )
);

// ...or write this straight to stdout, if we don't need the memory info

require "memory.php";

  

想象一下應用程度執行到該步驟,這次我們不是要從本地檔案系統中獲取影像,而是從 CDN 獲取,我們用 file_get_contents 代替更優雅的處理方式 (例如 Guzzle),它們的實際效果是一樣的,

記憶體使用情況為 581KB,現在,我們如何嘗試進行流傳輸呢?

// from piping-files-4.php

$handle1 = fopen(
    "https://github.com/assertchris/uploads/raw/master/rick.jpg", "r"
);

$handle2 = fopen(
    "piping-files-4.jpeg", "w"
);

// ...or write this straight to stdout, if we don't need the memory info

stream_copy_to_stream($handle1, $handle2);

fclose($handle1);
fclose($handle2);

require "memory.php";

  

記憶體使用比剛才略少 (400 KB),但是結果是相同的,如果我們不需要記憶體資訊,也可以列印至標準輸出,PHP 提供了一種簡單的方法來執行此操作:

$handle1 = fopen(
    "https://github.com/assertchris/uploads/raw/master/rick.jpg", "r"
);

$handle2 = fopen(
    "php://stdout", "w"
);

stream_copy_to_stream($handle1, $handle2);

fclose($handle1);
fclose($handle2);

// require "memory.php";

  

其他流

還存在一些流可以通過管道來讀寫,

  • php://stdin 只讀
  • php://stderr 只寫,與 php://stdout 相似
  • php://input 只讀,使我們可以訪問原始請求內容
  • php://output 只寫,可讓我們寫入輸出緩沖區
  • php://memory 與 php://temp (可讀寫) 是臨時存盤資料的地方,區別在于資料足夠大時 php:/// temp 就會將資料存盤在檔案系統中,而 php:/// memory 將繼續存盤在記憶體中直到耗盡,

過濾器

我們可以對流使用另一個技巧,稱為過濾器,它介于兩者之間,對資料進行了適當的控制使其不暴露給外接,假設我們要壓縮 shakespeare.txt 檔案,我們可以使用 Zip 擴展

// from filters-1.php

$zip = new ZipArchive();
$filename = "filters-1.zip";

$zip->open($filename, ZipArchive::CREATE);
$zip->addFromString("shakespeare.txt", file_get_contents("shakespeare.txt"));
$zip->close();

require "memory.php";

  

這段代碼雖然整潔,但是總共使用了大概 10.75 MB 的記憶體,我們可以使用過濾器來進行優化

// from filters-2.php

$handle1 = fopen(
    "php://filter/zlib.deflate/resource=shakespeare.txt", "r"
);

$handle2 = fopen(
    "filters-2.deflated", "w"
);

stream_copy_to_stream($handle1, $handle2);

fclose($handle1);
fclose($handle2);

require "memory.php";

  

在這里,我們可以看到 php:///filter/zlib.deflate 過濾器,該過濾器讀取和壓縮資源的內容,然后我們可以將該壓縮資料通過管道傳輸到另一個檔案中,這僅使用了 896KB 記憶體,

雖然格式不同,或者說使用 zip 壓縮檔案有其他諸多好處,但是,你不得不考慮:如果選擇其他格式你可以節省 12 倍的記憶體,你會不會心動?

要對資料進行解壓,只需要通過另外一個 zlib 過濾器:

// from filters-2.php

file_get_contents(
    "php://filter/zlib.inflate/resource=filters-2.deflated"
);

  

自定義流

fopen 和 file_get_contents 具有它們自己的默認選項集,但是它們是完全可定制的,要定義它們,我們需要創建一個新的流背景關系

// from creating-contexts-1.php

$data = https://www.cnblogs.com/a609251438/p/join("&", [
    "twitter=assertchris",
]);

$headers = join("\r\n", [
    "Content-type: application/x-www-form-urlencoded",
    "Content-length: " . strlen($data),
]);

$options = [
    "http" => [
        "method" => "POST",
        "header"=> $headers,
        "content" => $data,
    ],
];

$context = stream_content_create($options);

$handle = fopen("https://example.com/register", "r", false, $context);
$response = stream_get_contents($handle);

fclose($handle);

  

本例中,我們嘗試發送一個 POST 請求給 API,API 端點是安全的,不過我們仍然使用了 http 背景關系屬性(可用于 http 或者 https),我們設定了一些頭部,并打開了 API 的檔案句柄,我們可以將句柄以只讀方式打開,背景關系負責撰寫,

自定義的內容很多,如果你想了解更多資訊,可查看對應 檔案,

創建自定義協議和過濾器

在總結之前,我們先談談創建自定義協議,如果你查看 檔案,可以找到一個示例類:,

Protocol {
    public resource $context;
    public __construct ( void )
    public __destruct ( void )
    public bool dir_closedir ( void )
    public bool dir_opendir ( string $path , int $options )
    public string dir_readdir ( void )
    public bool dir_rewinddir ( void )
    public bool mkdir ( string $path , int $mode , int $options )
    public bool rename ( string $path_from , string $path_to )
    public bool rmdir ( string $path , int $options )
    public resource stream_cast ( int $cast_as )
    public void stream_close ( void )
    public bool stream_eof ( void )
    public bool stream_flush ( void )
    public bool stream_lock ( int $operation )
    public bool stream_metadata ( string $path , int $option , mixed $value )
    public bool stream_open ( string $path , string $mode , int $options ,
        string &$opened_path )
    public string stream_read ( int $count )
    public bool stream_seek ( int $offset , int $whence = SEEK_SET )
    public bool stream_set_option ( int $option , int $arg1 , int $arg2 )
    public array stream_stat ( void )
    public int stream_tell ( void )
    public bool stream_truncate ( int $new_size )
    public int stream_write ( string $data )
    public bool unlink ( string $path )
    public array url_stat ( string $path , int $flags )
}

  

我們并不打算實作其中一個,因為我認為它值得擁有自己的教程,有很多作業要做,但是一旦完成作業,我們就可以很容易地注冊流包裝器:

if (in_array("highlight-names", stream_get_wrappers())) {
    stream_wrapper_unregister("highlight-names");
}

stream_wrapper_register("highlight-names", "HighlightNamesProtocol");

$highlighted = file_get_contents("highlight-names://story.txt");

  

同樣,也可以創建自定義流過濾器, 檔案 有一個示例過濾器類:

Filter {
    public $filtername;
    public $params
    public int filter ( resource $in , resource $out , int &$consumed ,
        bool $closing )
    public void onClose ( void )
    public bool onCreate ( void )
}

  

可被輕松注冊

$handle = fopen("story.txt", "w+");
stream_filter_append($handle, "highlight-names", STREAM_FILTER_READ);

  

highlight-names 需要與新過濾器類的 filtername 屬性匹配,還可以在 php:///filter/highligh-names/resource=story.txt 字串中使用自定義過濾器,定義過濾器比定義協議要容易得多,原因之一是協議需要處理目錄操作,而過濾器僅需要處理每個資料塊,

如果您愿意,我強烈建議您嘗試創建自定義協議和過濾器,如果您可以將過濾器應用于 stream_copy_to_stream 操作,則即使處理令人討厭的大檔案,您的應用程式也將幾乎不使用任何記憶體,想象一下撰寫調整大小影像過濾器或加密應用程式過濾器,

如果你愿意,我強烈建議你嘗試創建自定義協議和過濾器,如果你可以將過濾器應用于 stream_copy_to_stream 操作,即使處理煩人的大檔案,你的應用程式也幾乎不使用任何記憶體,想象下撰寫 resize-image 過濾器和 encrypt-for-application 過濾器吧,

總結

雖然這不是我們經常遇到的問題,但是在處理大檔案時的確很容易搞砸,在異步應用中,如果我們不注意記憶體的使用情況,很容易導致服務器的崩潰,

本教程希望能帶給你一些新的想法(或者更新你的對這方面的固有記憶),以便你能夠更多的考慮如何有效地讀取和寫入大檔案,當我們開始熟悉和使用流和生成器并停止使用諸如 file_get_contents 這樣的函式時,這方面的錯誤將全部從應用程式中消失,這不失為一件好事,

 

更多學習內容請訪問:

騰訊T3-T4標準精品PHP架構師教程目錄大全,只要你看完保證薪資上升一個臺階(持續更新)?圖示

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/56974.html

標籤:PHP

上一篇:PHP正則運算式表【轉】

下一篇:php-laravel框架下,通過指定欄位對結果集進行排序

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more