狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

前端下載超大文件的完整方案

freeflydom
2024年4月30日 9:38 本文熱度 1167

本文從前端方面出發(fā)實現(xiàn)瀏覽器下載大文件的功能。不考慮網(wǎng)絡(luò)異常、關(guān)閉網(wǎng)頁等原因造成傳輸中斷的情況。分片下載采用串行方式(并行下載需要對切片計算hash,比對hash,丟失重傳,合并chunks的時候需要按順序合并等,很麻煩。對傳輸速度有追求的,并且在帶寬允許的情況下可以做并行分片下載)。

測試發(fā)現(xiàn)存一兩個G左右數(shù)據(jù)到IndexedDB后,瀏覽器確實會內(nèi)存占用過高導(dǎo)致退出 (我測試使用的是chrome103版本瀏覽器)

實現(xiàn)步驟

  1. 使用分片下載: 將大文件分割成多個小塊進(jìn)行下載,可以降低內(nèi)存占用和網(wǎng)絡(luò)傳輸中斷的風(fēng)險。這樣可以避免一次性下載整個大文件造成的性能問題。

  2. 斷點續(xù)傳: 實現(xiàn)斷點續(xù)傳功能,即在下載中途中斷后,可以從已下載的部分繼續(xù)下載,而不需要重新下載整個文件。

  3. 進(jìn)度條顯示: 在頁面上展示下載進(jìn)度,讓用戶清晰地看到文件下載的進(jìn)度。如果一次全部下載可以從process中直接拿到參數(shù)計算得出(很精細(xì)),如果是分片下載,也是計算已下載的和總大小,只不過已下載的會成片成片的增加(不是很精細(xì))。

  4. 取消下載和暫停下載功能: 提供取消下載和暫停下載的按鈕,讓用戶可以根據(jù)需要中止或暫停下載過程。

  5. 合并文件: 下載完成后,將所有分片文件合并成一個完整的文件。

以下是一個基本的前端大文件下載的實現(xiàn)示例:

可以在類里面增加注入一個回調(diào)函數(shù),用來更新外部的一些狀態(tài),示例中只展示下載完成后的回調(diào)

class FileDownloader {

  constructor({url, fileName, chunkSize = 2 * 1024 * 1024, cb}) {

    this.url = url;

    this.fileName = fileName;

    this.chunkSize = chunkSize;

    this.fileSize = 0;

    this.totalChunks = 0;

    this.currentChunk = 0;

    this.downloadedSize = 0;

    this.chunks = [];

    this.abortController = new AbortController();

    this.paused = false;

    this.cb = cb

  }


  async getFileSize() {

    const response = await fetch(this.url, { signal: this.abortController.signal });

    const contentLength = response.headers.get("content-length");

    this.fileSize = parseInt(contentLength);

    this.totalChunks = Math.ceil(this.fileSize / this.chunkSize);

  }


  async downloadChunk(chunkIndex) {

    const start = chunkIndex * this.chunkSize;

    const end = Math.min(this.fileSize, (chunkIndex + 1) * this.chunkSize - 1);


    const response = await fetch(this.url, {

      headers: { Range: `bytes=${start}-${end}` },

      signal: this.abortController.signal

    });


    const blob = await response.blob();

    this.chunks[chunkIndex] = blob;

    this.downloadedSize += blob.size;


    if (!this.paused && this.currentChunk < this.totalChunks - 1) {

      this.currentChunk++;

      this.downloadChunk(this.currentChunk);

    } else if (this.currentChunk === this.totalChunks - 1) {

      this.mergeChunks();

    }

  }


  async startDownload() {

    if (this.chunks.length === 0) {

      await this.getFileSize();

    }

    this.downloadChunk(this.currentChunk);

  }


  pauseDownload() {

    this.paused = true;

  }


  resumeDownload() {

    this.paused = false;

    this.downloadChunk(this.currentChunk);

  }


  cancelDownload() {

    this.abortController.abort();

    this.reset();

  }


  async mergeChunks() {

    const blob = new Blob(this.chunks, { type: "application/octet-stream" });

    const url = window.URL.createObjectURL(blob);

    const a = document.createElement("a");

    a.href = url;

    a.download = this.fileName;

    document.body.appendChild(a);

    a.click();

    setTimeout(() => {

      this.cb && this.cb({

        downState: 1

      })

      this.reset();

      document.body.removeChild(a);

      window.URL.revokeObjectURL(url);

    }, 0);

  }

  

  reset() {

    this.chunks = [];

    this.fileName = '';

    this.fileSize = 0;

    this.totalChunks = 0;

    this.currentChunk = 0;

    this.downloadedSize = 0;

  }

}



// 使用示例

const url = "https://example.com/largefile.zip";

const fileName = "largefile.zip";


const downloader = new FileDownloader({url, fileName, cb: this.updateData});


// 更新狀態(tài)

updateData(res) {

  const {downState} = res

  this.downState = downState

}


// 開始下載

downloader.startDownload();


// 暫停下載

// downloader.pauseDownload();


// 繼續(xù)下載

// downloader.resumeDownload();


// 取消下載

// downloader.cancelDownload();

分片下載怎么實現(xiàn)斷點續(xù)傳?已下載的文件怎么存儲?

瀏覽器的安全策略禁止網(wǎng)頁(JS)直接訪問和操作用戶計算機上的文件系統(tǒng)。

在分片下載過程中,每個下載的文件塊(chunk)都需要在客戶端進(jìn)行緩存或存儲,方便實現(xiàn)斷點續(xù)傳功能,同時也方便后續(xù)將這些文件塊合并成完整的文件。這些文件塊可以暫時保存在內(nèi)存中或者存儲在客戶端的本地存儲(如 IndexedDB、LocalStorage 等)中。

一般情況下,為了避免占用過多的內(nèi)存,推薦將文件塊暫時保存在客戶端的本地存儲中。這樣可以確保在下載大文件時不會因為內(nèi)存占用過多而導(dǎo)致性能問題。

在上面提供的示例代碼中,文件塊是暫時保存在一個數(shù)組中的,最終在mergeChunks()方法中將這些文件塊合并成完整的文件。如果你希望將文件塊保存在本地存儲中,可以根據(jù)需要修改代碼,將文件塊保存到 IndexedDB 或 LocalStorage 中。

IndexedDB本地存儲

IndexedDB文檔:IndexedDB_API

IndexedDB 瀏覽器存儲限制和清理標(biāo)準(zhǔn)

無痕模式是瀏覽器提供的一種隱私保護(hù)功能,它會在用戶關(guān)閉瀏覽器窗口后自動清除所有的瀏覽數(shù)據(jù),包括 LocalStorage、IndexedDB 和其他存儲機制中的數(shù)據(jù)。

IndexedDB 數(shù)據(jù)實際上存儲在瀏覽器的文件系統(tǒng)中,是瀏覽器的隱私目錄之一,不同瀏覽器可能會有不同的存儲位置,普通用戶無法直接訪問和手動刪除這些文件,因為它們受到瀏覽器的安全限制。可以使用 deleteDatabase 方法來刪除整個數(shù)據(jù)庫,或者使用 deleteObjectStore 方法來刪除特定的對象存儲空間中的數(shù)據(jù)。

原生的indexedDB api 使用起來很麻煩,稍不留神就會出現(xiàn)各種問題,封裝一下方便以后使用。

這個類封裝了 IndexedDB 的常用操作,包括打開數(shù)據(jù)庫、添加數(shù)據(jù)、通過 ID 獲取數(shù)據(jù)、獲取全部數(shù)據(jù)、更新數(shù)據(jù)、刪除數(shù)據(jù)和刪除數(shù)據(jù)表。

封裝indexedDB類

class IndexedDBWrapper {

  constructor(dbName, storeName) {

    this.dbName = dbName;

    this.storeName = storeName;

    this.db = null;

  }


  openDatabase() {

    return new Promise((resolve, reject) => {

      const request = indexedDB.open(this.dbName);

      

      request.onerror = () => {

        console.error("Failed to open database");

        reject();

      };


      request.onsuccess = () => {

        this.db = request.result;

        resolve();

      };


      request.onupgradeneeded = () => {

        this.db = request.result;

        

        if (!this.db.objectStoreNames.contains(this.storeName)) {

          this.db.createObjectStore(this.storeName, { keyPath: "id" });

        }

      };

    });

  }


  addData(data) {

    return new Promise((resolve, reject) => {

      const transaction = this.db.transaction([this.storeName], "readwrite");

      const objectStore = transaction.objectStore(this.storeName);

      const request = objectStore.add(data);


      request.onsuccess = () => {

        resolve();

      };


      request.onerror = () => {

        console.error("Failed to add data");

        reject();

      };

    });

  }


  getDataById(id) {

    return new Promise((resolve, reject) => {

      const transaction = this.db.transaction([this.storeName], "readonly");

      const objectStore = transaction.objectStore(this.storeName);

      const request = objectStore.get(id);


      request.onsuccess = () => {

        resolve(request.result);

      };


      request.onerror = () => {

        console.error(`Failed to get data with id: ${id}`);

        reject();

      };

    });

  }


  getAllData() {

    return new Promise((resolve, reject) => {

      const transaction = this.db.transaction([this.storeName], "readonly");

      const objectStore = transaction.objectStore(this.storeName);

      const request = objectStore.getAll();


      request.onsuccess = () => {

        resolve(request.result);

      };


      request.onerror = () => {

        console.error("Failed to get all data");

        reject();

      };

    });

  }


  updateData(data) {

    return new Promise((resolve, reject) => {

      const transaction = this.db.transaction([this.storeName], "readwrite");

      const objectStore = transaction.objectStore(this.storeName);

      const request = objectStore.put(data);


      request.onsuccess = () => {

        resolve();

      };


      request.onerror = () => {

        console.error("Failed to update data");

        reject();

      };

    });

  }


  deleteDataById(id) {

    return new Promise((resolve, reject) => {

      const transaction = this.db.transaction([this.storeName], "readwrite");

      const objectStore = transaction.objectStore(this.storeName);

      const request = objectStore.delete(id);


      request.onsuccess = () => {

        resolve();

      };


      request.onerror = () => {

        console.error(`Failed to delete data with id: ${id}`);

        reject();

      };

    });

  }


  deleteStore() {

    return new Promise((resolve, reject) => {

      const version = this.db.version + 1;

      this.db.close();


      const request = indexedDB.open(this.dbName, version);


      request.onupgradeneeded = () => {

        this.db = request.result;

        this.db.deleteObjectStore(this.storeName);

        resolve();

      };


      request.onsuccess = () => {

        resolve();

      };


      request.onerror = () => {

        console.error("Failed to delete object store");

        reject();

      };

    });

  }

}

使用indexedDB類示例:

const dbName = "myDatabase";

const storeName = "myStore";


const dbWrapper = new IndexedDBWrapper(dbName, storeName);


dbWrapper.openDatabase().then(() => {

  const data = { id: 1, name: "John Doe", age: 30 };


  dbWrapper.addData(data).then(() => {

    console.log("Data added successfully");


    dbWrapper.getDataById(1).then((result) => {

      console.log("Data retrieved:", result);


      const updatedData = { id: 1, name: "Jane Smith", age: 35 };

      dbWrapper.updateData(updatedData).then(() => {

        console.log("Data updated successfully");


        dbWrapper.getDataById(1).then((updatedResult) => {

          console.log("Updated data retrieved:", updatedResult);


          dbWrapper.deleteDataById(1).then(() => {

            console.log("Data deleted successfully");


            dbWrapper.getAllData().then((allData) => {

              console.log("All data:", allData);


              dbWrapper.deleteStore().then(() => {

                console.log("Object store deleted successfully");

              });

            });

          });

        });

      });

    });

  });

});

indexedDB的使用庫 - localforage

這個庫對瀏覽器本地存儲的幾種方式做了封裝,自動降級處理。但是使用indexedDB上感覺不是很好,不可以添加索引,但是操作確實方便了很多。

文檔地址: localforage

下面展示 LocalForage 中使用 IndexedDB 存儲引擎并結(jié)合 async/await 進(jìn)行異步操作

const localforage = require('localforage');


// 配置 LocalForage

localforage.config({

  driver: localforage.INDEXEDDB, // 使用 IndexedDB 存儲引擎

  name: 'myApp', // 數(shù)據(jù)庫名稱

  version: 1.0, // 數(shù)據(jù)庫版本

  storeName: 'myData' // 存儲表名稱

});


// 使用 async/await 進(jìn)行異步操作

(async () => {

  try {

    // 存儲數(shù)據(jù)

    await localforage.setItem('key', 'value');

    console.log('數(shù)據(jù)保存成功');


    // 獲取數(shù)據(jù)

    const value = await localforage.getItem('key');

    console.log('獲取到的數(shù)據(jù)為:', value);


    // 移除數(shù)據(jù)

    await localforage.removeItem('key');

    console.log('數(shù)據(jù)移除成功');

    

    // 關(guān)閉 IndexedDB 連接

    await localforage.close();

    console.log('IndexedDB 已關(guān)閉');

  } catch (err) {

    console.error('操作失敗', err);

  }

})();

現(xiàn)代的瀏覽器會自動管理 IndexedDB 連接的生命周期,包括在頁面關(guān)閉時自動關(guān)閉連接,在大多數(shù)情況下,不需要顯式地打開或關(guān)閉 IndexedDB 連接。

如果你有特殊的需求或者對性能有更高的要求,可以使用 localforage.close() 方法來關(guān)閉連接。

使用 LocalForage 來刪除 IndexedDB 中的所有數(shù)據(jù)


import localforage from 'localforage';


// 使用 clear() 方法刪除所有數(shù)據(jù)

localforage.clear()

  .then(() => {

    console.log('IndexedDB 中的所有數(shù)據(jù)已刪除');

  })

  .catch((error) => {

    console.error('刪除 IndexedDB 數(shù)據(jù)時出錯:', error);

  });


IndexedDB內(nèi)存暫用過高問題

使用 IndexedDB 可能會導(dǎo)致瀏覽器內(nèi)存占用增加的原因有很多,以下是一些可能的原因:

  1. 數(shù)據(jù)量過大:如果你在 IndexedDB 中存儲了大量數(shù)據(jù),那么瀏覽器可能需要消耗更多內(nèi)存來管理和處理這些數(shù)據(jù)。尤其是在讀取或?qū)懭氪罅繑?shù)據(jù)時,內(nèi)存占用會顯著增加。

  2. 未關(guān)閉的連接:如果在使用完 IndexedDB 后未正確關(guān)閉數(shù)據(jù)庫連接,可能會導(dǎo)致內(nèi)存泄漏。確保在不再需要使用 IndexedDB 時正確關(guān)閉數(shù)據(jù)庫連接,以釋放占用的內(nèi)存。

  3. 索引和查詢:如果你在 IndexedDB 中創(chuàng)建了大量索引或者執(zhí)行復(fù)雜的查詢操作,都會導(dǎo)致瀏覽器內(nèi)存占用增加,特別是在處理大型數(shù)據(jù)集時。

  4. 緩存:瀏覽器可能會對 IndexedDB 中的數(shù)據(jù)進(jìn)行緩存,以提高訪問速度。這可能會導(dǎo)致內(nèi)存占用增加,尤其是在大規(guī)模數(shù)據(jù)操作后。

  5. 瀏覽器實現(xiàn):不同瀏覽器的 IndexedDB 實現(xiàn)可能存在差異,某些瀏覽器可能會在處理 IndexedDB 數(shù)據(jù)時占用更多內(nèi)存。

為了減少內(nèi)存占用,你可以考慮優(yōu)化數(shù)據(jù)存儲結(jié)構(gòu)、合理使用索引、避免長時間保持大型數(shù)據(jù)集等措施。另外,使用瀏覽器的開發(fā)者工具進(jìn)行內(nèi)存分析,可以幫助你找到內(nèi)存占用增加的具體原因,從而采取相應(yīng)的優(yōu)化措施。


本文來自博客園,作者:甜點cc,轉(zhuǎn)載請注明原文鏈接:https://www.cnblogs.com/all-smile/p/18096224



該文章在 2024/4/30 9:39:18 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved