計算機是以二進制形式存儲和表示數據,二進制是 0
和 1
的集合。例如:0100
,1010
。好比,要存儲數字 13
計算機須要將數字轉換爲 1101
。html
二進制中的
0
和1
被稱爲位(bit)。儘管它們很像表示一個數值,可是其實它們表示是符號。0
表明FALSE,1
表明TRUE。位的運算實際上是對真假值的操做。爲了存儲數據,計算機包含了大量的電路,每個電路能存儲單獨的一個位。這種位存儲器,被稱爲"主存儲器"。計算機經過存儲單元組織管理主存儲器。一個典型的存儲單元容量是8位,一個8位的串稱爲一個字節(byte)。node
可是,數字不是咱們惟一須要存儲處理的數據,咱們還須要處理字符串,圖片,視頻。shell
以字符串爲例,那麼計算機怎麼存儲字符串呢?例如,咱們要存儲字符串 "S" ,計算機首先會將 "S", 轉換爲數字'S'.charCodeAt() === 83
, 那麼計算機是如何知道83表示"S"的?json
字符集是已經定義好的規則,每個字符都有一個確切的數字表示。字符集有不一樣規則的定義,例如:"Unicode", "ASCII"。瀏覽器中使用"Unicode"字符集。正是"Unicode"字符集,定義了83表示"S"。api
那麼接下來,計算機會直接將83轉爲二進制嗎?並非,咱們還須要使用"字符編碼"。數組
字符集定義了使用特定的數字表示字符(漢字也是一樣的)。而字符編碼定義了,如何將數字轉換爲特定長度的二進制數據。常見的utf-8字符編碼,規定了字符最多由4個字節進行編碼(一個字節由8個,0或者1表示)。瀏覽器
h e l l o <==Unicode==> 104 101 108 108 111 <==utf-8==> 1101000 1100101 1101100 1101100 1101111
複製代碼
對於視頻,音頻,圖片,計算機也有特定規則,將其轉換爲二進制數據。計算機將全部數據類型,存儲爲二進制文件。這些二進制文件就是二進制數據。緩存
數據流指數據從一個位置到另外一個位置的移動(一般大文件會被拆解爲塊的形式)。安全
若是數據流動的速度,大於進程處理數據的速度,多餘的數據會在某個地方等待。若是數據流動的速度,小於進程處理數據的速度,那麼數據會在某個地方累計到必定的數量,而後在由進程進行處理。(咱們沒法控制流的速度)post
那個等待數據,累計數據,而後發生出去的地方。就是緩衝區。緩衝區一般位於電腦的RAM(內存)中。
咱們能夠把緩衝區想象成一個公交車站,較早到達車站的乘客的會等待公交車,當公交車以及裝滿離開後,剩下的乘客會等待下一班的公交車到來。那個等待公交車的地方,就是緩衝區。
舉一個常見的緩衝區的例子,咱們在觀看在線視頻的時候,若是你的網速很快,緩衝區老是會被當即填充,而後發送出去,而後當即緩衝下一段視頻。觀看的過程當中,不會有卡頓。若是網速很慢,則會看到loading,表示緩衝區正在被填充,當填充完成後數據被髮送出去,才能看到這段視頻。
在TypedArray
出現前,js沒有讀取或操做二進制數據流的機制。TypedArray
並非一個特定的全局對象,而是許多全局對象的統稱。
// TypedArray 指的是下面其中之一
Int8Array // 8位二進制補碼有符號整數的數組
Uint8Array // 8位無符號整型數組(0 > & < 256(8位無符號整形)),等等
Uint8ClampedArray
Int16Array
Uint16Array
Int32Array
Uint32Array
Float32Array
Float64Array
複製代碼
Buffer類,是以Nodejs方式實現的Uint8ArrayAPI(Uint8Array屬於TypedArray的一種)。用於與八字節的二進制數據進行交互。
Buffer.from, 接受多種形式的參數,下面介紹幾種常見的
使用8字節的數組,做爲參數
const buf = Buffer.from([0b1101000, 0b1100101, 0b1101100, 0b1101100, 0b1101111])
// hello
console.log(buf.toString())
複製代碼
使用字符串做爲參數
const buf = Buffer.from('Hello World!');
// HelloWorld
console.log(buf.toString())
複製代碼
建立一個指定大小,而且已經初始化的Buffer(默認被0填充)
// 建立一個大小爲10個字節的Buffer,並使用0進行填充
const buf = Buffer.alloc(10)
// <Buffer 00 00 00 00 00 00 00 00 00 00>
console.log(buf)
// 建立一個大小爲12個字節的Buffer,並使用漢字進行填充
const buf = Buffer.alloc(12, '大')
// 打印出 大大大大,由於漢字是3個字節的關係,因此填充了4個漢字
console.log(buf.toString())
複製代碼
建立一個指定大小,可是沒有初始化填充的Buffer
// 建立一個大小爲10個字節的Buffer,沒有被初始化
const buf = Buffer.allocUnsafe(10)
複製代碼
爲何說 Buffer.allocUnsafe 是不安全的?Buffer是內存的抽象,嘗試運行
console.log(Buffer.allocUnsafe(10000).toString()), 咱們應該能夠從控制檯看到打印出了內存裏的一些東西
向Buffer中寫入字符串,若是Buffer空間不夠,多餘的字符串不會被寫入
const buf = Buffer.alloc(5)
// 您好的長度是6個字節
buf.write('您好')
// 您
console.log(buf.toString())
複製代碼
將buffer中的數據轉換爲Unicode碼
const buf = Buffer.from('hello')
// {
// type: 'Buffer',
// data: [ 104, 101, 108, 108, 111 ] h e l l o 的 Unicode碼
// }
console.log(buf.toJSON())
複製代碼
將buffer解碼成字符串
const buf = Buffer.from([0b1101000, 0b1100101, 0b1101100, 0b1101100, 0b1101111 ])
// hello
console.log(buf.toString())
複製代碼
考慮下面這種狀況,由於兩個漢字的字節長度是6,因此字節長度等於5的buffer是放不下的,因此打印出來的字符串是不完整的。
const buf = Buffer.alloc(5, '您好')
// 您�
console.log(buf.toString())
複製代碼
那麼有什麼辦法能夠將Buffer中不完整的字符串輸出出來呢?咱們可使用String Decoder
const { StringDecoder } = require('string_decoder')
const decoder = new StringDecoder('utf8')
// Buffer.from('好') <Buffer e5 a5 bd>
const str1 = decoder.write(Buffer.alloc(5, '您好'))
// 您
console.log(str1)
const str2 = decoder.end(Buffer.from([0xbd]))
// 好
console.log(str2)
複製代碼
StringDecoder的實例接受寫入Buffer的實例,使用內部緩衝區確保解碼的字符串不包含不完成的字節,而且將不完整的字節,保存起來,直到下一次使用write或者end。
返回已解碼的字符串,字符串不包含不完整的字節,不完整的字節。不完整的字節會保存到decoder內部的緩衝區中。
const { StringDecoder } = require('string_decoder')
const decoder = new StringDecoder('utf8')
// 哈嘍 <Buffer e5 93 88 e5 96 bd>
const str = decoder.write(Buffer.from([0xe5, 0x93, 0x88, 0xe5, 0x96]))
// 哈,0xe5, 0x96因爲不完整不會被返回,而是保存在decoder的內部緩衝區
console.log(str)
複製代碼
會將decoder內部緩存區剩餘的buffer一次性返回。
const { StringDecoder } = require('string_decoder')
const decoder = new StringDecoder('utf8')
// 哈嘍 <Buffer e5 93 88 e5 96 bd>
decoder.write(Buffer.from([0xe5, 0x93, 0x88, 0xe5, 0x96]))
const str = decoder.end()
// �,decoder內部緩衝區剩餘的字節是不完整的
console.log(str)
複製代碼
流是數據的集合,流不像字符串或者數組同樣是當即可用的,流不會所有存在內存中。處理大量數據時,流很是有用。
Nodejs中,許多模塊都實現了流模式。下圖是實現了流模式的內置模塊(圖片來自於Samer Buna的在線課程)
fs.createWriteStream
fs.createReadStream
zlib.createGzip
壓縮數據流const fs = require('fs')
// 可讀流做爲數據源
const readable = fs.createReadStream('./數據源.json')
// 可寫流做爲目標
const writable = fs.createWriteStream('./目標.json')
// 將數據源經過管道鏈接到目標
readable.pipe(writable)
複製代碼
在這幾行簡單的代碼中咱們將可讀流的輸出(readable
做爲數據源),鏈接管道至可寫流的輸入(writable
做爲目標)。源必須是可讀流,目標必須是可寫流。
const fs = require('fs')
const zlib = require('zlib')
const readable = fs.createReadStream('./數據源.json')
// gzip是一個雙工流
const gzip = zlib.createGzip()
const writable = fs.createWriteStream('./目標.gz')
// 數據源鏈接到轉換流(gzip),轉換流處理數據後,鏈接到目標上
readable
.pipe(gzip)
.pipe(writable)
複製代碼
咱們也能夠將可讀流的管道鏈接到雙工流(轉換流)上。總結一下pipe
方法的用法。pipe
能夠返回一個目標流,目標流能夠鏈接到雙工流,可寫流上。
可讀流
.pipe(雙工流)
.pipe(雙工流)
.pipe(可寫流)
複製代碼
使用pipe
是消費流最簡單的方法,它會自動管理一些操做,好比錯誤處理,好比若是可讀流沒有數據可供消費時的狀況。固然咱們也能夠經過事件消費流,可是最好避免二者混合使用。
若是須要對流實現,更自定義的控制,可使用事件消費流。下面的這段代碼和以前的pipe
的代碼是等效的。
const fs = require('fs')
const readable = fs.createReadStream('./數據源.json')
const writable = fs.createWriteStream('./目標.json')
// 當可讀流綁定data事件時,會將流切換到流動模式
readable.on('data', (chunk) => {
writable.write(chunk);
})
readable.on('end', () => {
writable.end()
})
複製代碼
下圖是可讀流,可寫流的事件與方法的列表(圖片來自於Samer Buna的在線課程)
上面對於流事件的示例中,是存在隱患的。具體的問題緣由,能夠查看個人這篇文章簡單理解 backpressure(背壓)機制
// 其實這段代碼是其實有問題的
readable.on('data', (chunk) => {
writable.write(chunk);
})
複製代碼
默認狀況下,可讀流是處於暫停狀態的,可是它們能夠被切換到流動模式,並在須要時切換回暫停模式。有時,模式會被自動發生切換。
在暫停模式時,咱們可使用read
方法從流中讀取數據。
const fs = require('fs')
const readable = fs.createReadStream('./數據源.json')
const writable = fs.createWriteStream('./目標.json')
// 當可讀流是能夠被讀取時或者會發生變化時或者到達流的盡頭時。readable能夠被觸發
readable.on('readable', () => {
let chunk
while (chunk = readable.read(1)) {
writable.write(chunk)
}
})
複製代碼
在流動模式時,數據會持續流動,咱們必須添加事件並消費它。若是不能處理流動的數據,數據是會丟失的。咱們能夠添加data
事件處理數據。添加data
事件,會自動將可讀流的模式,由暫停模式切換到流動模式。
若是須要實現兩種模式的手動切換可使用resume()
(從暫停模式中恢復)和pause()
(進入暫停模式)方法。(若是監聽了可讀流的readable
的事件,resume()
方法無效)
下面的例子中,每一次經過可寫流寫入一點數據,都會暫停一秒可讀流1s,1s後繼續寫入。
const fs = require('fs')
const readable = fs.createReadStream('./數據源.json')
const writable = fs.createWriteStream('./目標.json')
// 自動切換到流動模式
readable.on('data', (chunk) => {
console.log('寫入')
writable.write(chunk)
readable.pause()
console.log('暫停')
// 暫停1s後,從新切換到流動模式
setTimeout(() => {
readable.resume()
}, 1000)
})
複製代碼
下圖是兩種模式以前的切換(圖片來自於Samer Buna的在線課程)。添加data
事件時(單獨添加data
事件,不存在readable
事件時),模式會自動切換。
建立一個自定義的可寫流,咱們須要繼承 stream.Writable
類。並在子類中實現 _write
方法。
const { Writable } = require('stream')
class CustomWritable extends Writable {
/** * @param chunk 須要寫入的數據 * @param encoding 編碼格式 * @param next 處理完成的回調 */
_write(chunk, encoding, next) {
try {
// 僅僅是作了一個打印
console.log(`僅僅作了一個打印:${chunk.toString()}`)
next()
} catch (error) {
next(error)
}
}
}
複製代碼
這個流自己沒有多大的意義。這個自定義可寫流,只會對可讀流輸入的數據,進行一個打印。咱們能夠連接到 process.stdin
可讀流上,對終端的輸入,進行一個打印。
const customWritable = new CustomWritable()
process.stdin.pipe(customWritable)
複製代碼
建立一個可讀流,咱們須要繼承 stream.Readable
類。並在子類中實現 _read
方法。
const { Readable } = require('stream');
class CustomReadable extends Readable {
_read () {
}
}
複製代碼
這個流自己也沒有很大的意義。咱們可使用 push
方法,將數據添加到流的內部隊列中,以供流進行消費。咱們能夠結合前一個自定義的可寫流,將 push
的數據打印出來。
const customReadable = new CustomReadable()
const customWritable = new CustomWritable()
customReadable.push('我喜歡西爾莎羅南')
// 通知流不會有任何數據了
customReadable.push(null)
// 可讀流將數據傳遞給可寫流
// 可寫流將數據打印出來
customReadable.pipe(customWritable)
複製代碼
建立一個可讀流,咱們須要繼承 stream.Transform
類。並在子類中實現 _transform
方法。
const { Transform } = require('stream')
class CustomTransform extends Transform {
_transform (chunk, encoding, next) {
// 咱們把可讀流的內容,所有轉換爲大寫
chunk = chunk.toString().toUpperCase()
next(null, chunk)
}
}
複製代碼
這個自定義流,會將可讀流傳過來的字符串,所有轉換爲大寫。
const customReadable = new CustomReadable()
const customWritable = new CustomWritable()
const customTransform = new CustomTransform()
customReadable.push('abcdefg')
customReadable.push(null)
customReadable
.pipe(customTransform)
.pipe(customWritable)
複製代碼
Node中的流,默認都是使用Buffer 或者 字符串進行傳輸,咱們能夠開啓 objectMode
開關。使流能夠傳輸js的對象。
const { Readable, Writable } = require('stream')
class CustomReadable extends Readable {
_read () {
}
}
class CustomWritable extends Writable {
_write(chunk, _, next) {
try {
// 這裏能夠打印js對象
// chunk可使js對象
console.log(chunk)
next()
} catch (error) {
next(error)
}
}
}
const customReadable = new CustomReadable({
objectMode: true // 開啓對象模式
})
const customWritable = new CustomWritable({
objectMode: true // 開啓對象模式
})
// 咱們能夠傳輸對象了
customReadable.push(['a', 'b', 'c', 'd'])
customReadable.push(null)
customReadable.pipe(customWritable)
複製代碼