המחבר בחר בקרן ההקלה מפני קורונה לקבל תרומה כחלק מתוכנית "כתוב בשביל תרומות".
הקדמה
A buffer is a space in memory (typically RAM) that stores binary data. In Node.js, we can access these spaces of memory with the built-in Buffer
class. Buffers store a sequence of integers, similar to an array in JavaScript. Unlike arrays, you cannot change the size of a buffer once it is created.
ייתכן וכבר השתמשת במקופיים באופן משולב אם כתבת קוד Node.js. לדוגמה, כאשר אתה קורא מקובץ באמצעות fs.readFile()
, הנתונים שמוחזרים ל-callback או Promise הם אובייקט קופץ . בנוסף, כאשר בקשות HTTP מתבצעות ב-Node.js, הן מחזירות זרמים של נתונים שמאוחסנים זמנית בבאפר פנימי כאשר הלקוח לא יכול לעבד את הזרם כולו בפעם אחת.
מקופיים מועילים כאשר אתה מתמודד עם נתונים בינאריים, בדרך כלל ברמות הרשתות התחתונות. הם גם מזמינים אותך עם היכולת לבצע עיבוד נתונים מדוקדק ב-Node.js.
במדריך זה, תשתמשו ב־Node.js REPL כדי לעבור דרך דוגמאות שונות של מקטעי זיכרון, כגון יצירת מקטעי זיכרון, קריאה ממקטעי זיכרון, כתיבה והעתקה ממקטעי זיכרון, ושימוש במקטעי זיכרון כדי להמיר בין נתונים בינאריים ומוצפנים. עד סיום המדריך, תלמדו איך להשתמש במחלקת Buffer
כדי לעבוד עם נתונים בינאריים.
דרישות מוקדמות
- תצטרכו להתקין את Node.js על המכשיר לפיתוח שלכם. מדריך זה משתמש בגרסה 10.19.0. כדי להתקין את זה על macOS או Ubuntu 18.04, עקבו אחרי השלבים ב־איך להתקין Node.js וליצור סביבת פיתוח מקומית על macOS או בקטע ה־התקנה באמצעות PPA של איך להתקין Node.js על Ubuntu 18.04.
- במדריך זה, תתקשרו עם מקטעי זיכרון ב־Node.js REPL (Read-Evaluate-Print-Loop). אם ברצונכם לערוך רענון על איך להשתמש ב־Node.js REPL בצורה יעילה, תוכלו לקרוא את המדריך שלנו על איך להשתמש ב־Node.js REPL.
- עבור מאמר זה אנו מצפים מהמשתמש להיות נוח עם JavaScript בסיסי וסוגי הנתונים שלו. ניתן ללמוד את היסודות האלה עם הסדרה שלנו איך לכתוב ב-JavaScript.
שלב 1 — יצירת חוטית
השלב הראשון יראה לך את שתי הדרכים העיקריות ליצירת עצם חוטית ב-Node.js.
כדי להחליט איזו שיטה להשתמש, עליך לענות על השאלה הזו: האם ברצונך ליצור חוטית חדשה או לחלץ חוטית מנתונים קיימים? אם אתה מתכנן לאחסן נתונים בזיכרון שטרם קיבלת, תרצה ליצור חוטית חדשה. ב-Node.js אנו משתמשים בפונקציית alloc()
של Buffer
class כדי לעשות זאת.
בואו נפתח את ה־Node.js REPL כדי לראות בעצמנו. בטרמינל שלך, הזן את הפקודה node
:
תראה את המודעה מתחילה עם >
.
פונקציית ה־alloc()
מקבלת את גודל החוטית כארגומנט הראשי ויחיד הנדרש. הגודל הוא מספר שלם המייצג כמה בתים של זיכרון העובדת עם חוטית זו. לדוגמה, אם רצינו ליצור חוטית בגודל 1 קילו־בייט (KB), ששווה ל־1024 בתים, נזין את זה לקונסולה:
כדי ליצור חלון חדש, השתמשנו במחלקת ה־Buffer
הזמינה באופן גלובלי, שכוללת את השיטה alloc()
. על ידי מתן 1024
כארגומנט ל־alloc()
, יצרנו חלון בגודל של 1KB.
ברירת המחדל, כאשר מאתחלים חלון עם alloc()
, החלון מתמלא באפסים בינאריים כמקום מזין למידע מאוחר יותר. אך ניתן לשנות את ערך ברירת המחדל אם נרצה. אם נרצה ליצור חלון חדש עם 1
ים במקום 0
ים, נגדיר את הפרמטר השני של פונקציית alloc()
— fill
.
בטרמינל שלך, צור חלון חדש בפקודת REPL שמתמלא ב־1
ים:
רק יצרנו אובייקט חלון חדש שמפנה למקום בזיכרון שמאחסן 1KB של 1
ים. אף על פי שהזנו מספר שלם, כל המידע המאוחסן בחלון הוא מידע בינארי.
מידע בינארי יכול להגיע בפורמטים שונים מאוד. לדוגמה, נניח רצף בינארי המייצג בייט של נתונים: 01110110
. אם רצף הבינארי הזה היה מייצג מחרוזת באנגלית באמצעות תקן הקידוד ASCII, הוא היה האות v
. אך, אם המחשב שלנו עיבד תמונה, רצף הבינארי הזה יכל להכיל מידע על צבע הפיקסל.
המחשב יודע לעבד אותם באופן שונה מאחר כי הבתים מוצפנים בצורה שונה. הצפנת הבית נעשית בתבנית הבית. מאגר בתוך Node.js משתמש במערכת הקידוד UTF-8 כברירת מחדל אם הוא מאותחל עם נתוני מחרוזת. בית ב-UTF-8 מייצג מספר, אות (באנגלית ובשפות אחרות), או סמל. UTF-8 הוא תת-קבוצה של ASCII, תקן הקוד האמריקאי לקידוד מידע. ASCII יכול לקודד בתים עם אותיות אנגליות ריבועיות וקטנות, המספרים 0-9, וכמה סמלים אחרים כמו סימן הקריאה (!) או סימן האמפרסנד (&).
אם היינו כותבים תוכנית שיכולה לעבוד רק עם תווים ב-ASCII, היינו יכולים לשנות את הקידוד המשמש את המטמון שלנו באמצעות הפונקציה alloc()
עם הפרמטר השלישי—encoding
.
בואו ניצור מטמון חדש שאורכו חמישה בתים ומאחסן רק תווי ASCII:
המטמון מאותחל עם חמישה בתים של התו a
, באמצעות הייצוג ב-ASCII.
הערה: ברירת המחדל של Node.js כוללת את הקידודים התווים הבאים:
- ASCII, המיוצג כ-
ascii
- UTF-8, המיוצג כ-
utf-8
אוutf8
- UTF-16, המיוצג כ-
utf-16le
אוutf16le
- UCS-2, מיוצג כ-
ucs-2
אוucs2
- Base64, מיוצג כ-
base64
- Hexadecimal, מיוצג כ-
hex
- ISO/IEC 8859-1, מיוצג כ-
latin1
אוbinary
כל אחת מהערכים הללו יכולה לשמש בפונקציות המחלקה Buffer שמקבלות פרמטר encoding
. לכן, ערכים אלו הם כלם תקפים עבור שיטת alloc()
.
עד כה יצרנו Buffers חדשים בעזרת פונקציית alloc()
. אך לפעמים ייתכן שנרצה ליצור Buffer מנתונים שכבר קיימים, כמו מחרוזת או מערך.
ליצירת Buffer מנתונים קיימים, אנו משתמשים בשיטה from()
. ניתן להשתמש בפונקציה זו כדי ליצור Buffers מ:
- מערך של מספרים שלמים: ערכי המספרים יכולים להיות בין
0
ל-255
. ArrayBuffer
: זהו אובייקט JavaScript שמאחסן אורך קבוע של בתים.- A string.
- Buffer נוסף.
- אובייקטים אחרים ב-JavaScript שיש להם נכס
Symbol.toPrimitive
. נכס זה מספר ל-JavaScript כיצד להמיר את האובייקט לסוג נתונים פרימיטיבי:boolean
,null
,undefined
,number
,string
, אוsymbol
. ניתן לקרוא עוד על סמלים ב-JavaScript בתיעוד של Mozilla.
בואו נראה כיצד אפשר ליצור חבילה (buffer) ממחרוזת. בפרומפט של Node.js, הזינו את זה:
כעת יש לנו אובייקט חבילה שנוצר מהמחרוזת שמי הוא פול
. בואו ניצור חבילה חדשה מחבילה אחרת שיצרנו קודם:
כעת יצרנו חבילה חדשה asciiCopy
שמכילה את אותם נתונים כמו ב-asciiBuf
.
עכשיו שחווינו ביצור חבילות, אפשר לקפוץ לדוגמאות של קריאת הנתונים שלהם.
שלב 2 — קריאה מחבילה
ישנן רבות דרכים לגשת לנתונים בחבילה. אפשר לגשת לבת יחידה בחבילה או לחלץ את תוכן החבילה במלואו.
כדי לגשת לבת יחידה בחבילה, אנו מעבירים את האינדקס או המיקום של הבת שאנו רוצים. חבילות שומרות נתונים באופן רציף כמו מערכות. הן גם אינדקסות את הנתונים שלהן כמו מערכות, התחלתיים ב-0
. אנו יכולים להשתמש בתחביר מערך על אובייקט החבילה כדי לקבל בת יחידה.
בואו נראה איך זה נראה על ידי יצירת גוף מחסנית ממחרוזת ב-REPL:
עכשיו בואו נקרא את הבייט הראשון של המחסנית:
כאשר אתה לוחץ על ENTER
, ה-REPL יציג:
Output72
המספר השלם 72
מתאים לייצוג UTF-8 של האות H
.
הערה: ערכי הבייטים יכולים להיות מספרים בין 0
ל-255
. בייט הוא רצף של 8 ביטים. ביט הוא בינארי, ולכן יכול להיות לו רק אחת משני ערכים: 0
או 1
. אם יש לנו רצף של 8 ביטים ושני ערכים אפשריים לביט, אז לנו יש מרבי של 2⁸ ערכים אפשריים לבייט. זה יוצא ממספר מרבי של 256 ערכים. מאחר ואנו מתחילים לספור מאפס, זה אומר שהמספר הגבוה ביותר שלנו הוא 255.
בוא נעשה את אותו הדבר עבור הבייט השני. הזן את הפקודה הבאה ב-REPL:
ה-REPL מחזיר 105
, שמייצג את ה-i
באותיות קטנות.
לבסוף, בואו נקבל את התו השלישי:
תראו 33
מוצג ב-REPL, שמתאים ל-!
.
בוא ננסה לאחזר בייט מאינדקס שגוי:
ה-REPL יחזיר:
Outputundefined
זה דומה לכאילו שניסינו לגשת לאיבר במערך עם אינדקס שגוי.
עכשיו שראינו איך לקרוא לבייטים יחידים של מחסנית, בוא נראה את האפשרויות שלנו לאחזר את כל הנתונים השמורים במחסנית פעם אחת. עצם המחסנית מגיע עם השיטות toString()
ו־toJSON()
, המחזירות את תוכן המחסנית בשני פורמטים שונים.
כפי שמציין שמו, השיטה toString()
ממירה את הבתים של המטמון למחרוזת ומחזירה אותה למשתמש. אם נשתמש בשיטה זו על hiBuf
, נקבל את המחרוזת Hi!
. בואו ננסה את זה!
בתוך הפקודה, הזינו:
ה- REPL יחזיר:
Output'Hi!'
המטמון הזה נוצר ממחרוזת. בואו נראה מה יקרה אם נשתמש ב־ toString()
על מטמון שלא נוצר מנתוני מחרוזת.
בואו ניצור מטמון חדש, ריק שגודלו 10
בתים:
עכשיו, בואו נשתמש בשיטת toString()
:
נראה את התוצאה הבאה:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
המחרוזת \u0000
היא התו היוניקודי עבור NULL
. הוא מתאים למספר 0
. כאשר נתוני המטמון לא מוצפנים כמחרוזת, השיטה toString()
מחזירה את ההצפנה UTF-8 של הבתים.
ה־ toString()
כולל פרמטר אופציונלי, encoding
. אפשר להשתמש בפרמטר זה כדי לשנות את הצפנת נתוני המטמון שמוחזרים.
לדוגמה, אם הייתם רוצים את הצפנת הקודקודים של hiBuf
הייתם מזינים את הבא במסוף:
ההצהרה הזו תחזיר:
Output'486921'
486921
היא הייצוג ההקסדצימלי של הבתים המייצגים את המחרוזת Hi!
. ב-Node.js, כאשר משתמשים רוצים להמיר את הצפנת הנתונים מצורה אחת לצורה אחרת, הם נותנים תחילה את המחרוזת במטמון וקוראים ל־ toString()
עם הצפנה הרצויה שלהם.
השיטה toJSON()
מתנהגת באופן שונה. בלתי תלוי אם הבאפר נוצר ממחרוזת או לא, היא מחזירה תמיד את הנתונים כייצוג שלם של הבת.
בואו נשתמש שוב בבאפר hiBuf
ובבאפר tenZeroes
כדי לתרגל את השימוש ב- toJSON()
. בפקודה הבאה, הזינו:
ה- REPL יחזיר:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
האובייקט JSON מכיל מאפיין type
שתמיד יהיה Buffer
. כך תוכניות יכולות להבחין בין אובייקטי JSON אלו לבין אובייקטי JSON אחרים.
המאפיין data
מכיל מערך של ייצוג השלם של הבת. אתם עשויים להבחין ש- 72
, 105
ו- 33
מתאימים לערכים שקיבלנו כאשר דפדפנו בנפרד את הבתים.
בואו ננסה את השיטה toJSON()
עם הבאפר tenZeroes
:
ב- REPL תראו את התוצאה הבאה:
Output{ type: 'Buffer', data: [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
] }
ה- type
זהה למה שצוין למעלה. אך, הנתונים כעת הם מערך עם עשרה אפסים.
עכשיו שכיסינו את הדרכים העיקריות לקריאה מבאפר, בואו נסתכל על כיצד אנו משנים את תוכן הבאפר.
שלב 3 — שינוי באפר
קיימות רבות שיטות לשינוי אובייקט באפר קיים. בדומה לקריאה, אנו יכולים לשנות את הבתים באפר באופן יחידי באמצעות תחביר המערך. נוכל גם לכתוב תוכן חדש לבאפר, וכך להחליף את הנתונים הקיימים.
בואו נתחיל על ידי להביט באופן שבו ניתן לשנות בתים יחידים של תוך זרימה. נזכור את המשתנה שלנו בשם ה־hiBuf
, שמכיל את המחרוזת Hi!
. בואו נשנה כל בת כך שתכיל במקום זאת את Hey
.
ב־REPL, ננסה תחילה להגדיר את האיבר השני של hiBuf
ל־e
:
עכשיו, בואו נצפה בבאפס כמחרוזת כדי לוודא שהוא שומר על הנתונים הנכונים. נמשיך ונקרא לשיטת toString()
:
זה יערך כ־
Output'H\u0000!'
קיבלנו פלט מוזר כי הבאפר יכול לקבל רק ערך של מספר שלם. לא נוכל להקצות לו את האות e
; במקום זאת, נצטרך להקצות לו את המספר שבינריו מייצג את e
:
עכשיו כאשר נקרא לשיטת toString()
:
נקבל פלט זה ב־REPL:
Output'He!'
כדי לשנות את התו האחרון בבאפר, עלינו להגדיר את האיבר השלישי למספר שלם שמתאים לבית הבינרי של y
:
בואו נוודא באמצעות שימוש בשיטת toString()
פעם נוספת:
ה־REPL שלך יציג:
Output'Hey'
אם ננסה לכתוב בייט שנמצא מחוץ לטווח של הבאפר, הוא יתעלם ותוכן הבאפר לא ישתנה. לדוגמה, בואו ננסה להגדיר את האיבר הרביעי הלא קיים של הבאפר ל־o
:
נוכל לוודא שהבאפר לא שונה עם שימוש בשיטת toString()
:
הפלט עדיין הוא:
Output'Hey'
אם רצינו לשנות את תוכן המטמון במלואו, נוכל להשתמש בשיטת write()
. השיטה write()
מקבלת מחרוזת שתחליף את תוכן המטמון.
בואו נשתמש בשיטת write()
כדי לשנות את תוכן המטמון hiBuf
בחזרה ל־Hi!
. בשולי ה־Node.js שלך, הקלד את הפקודה הבאה בפני המערכת:
השיטה write()
החזירה 3
ב־REPL. זה מכיוון שהיא כתבה שלושה בתים של מידע. כל אות יש לה בית אחד בגודל בתים, מאחר ומטמון זה משתמש בקידוד UTF-8, שמשתמש בבית לכל תו. אם המטמון היה משתמש בקידוד UTF-16, שבו יש מינימום של שני בתים לכל תו, אז הפונקציה write()
הייתה מחזירה 6
.
עכשיו אמת את תוכן המטמון על ידי השימוש ב־toString()
:
REPL יחזיר:
Output'Hi!'
זה מהיר יותר מלשנות כל בית בית-לבית.
אם ננסה לכתוב יותר בתים מגודל המטמון, אובייקט המטמון יקבל רק את הבתים שהן תקינות. כדי להמחיש, בוא ניצור מטמון שמאחסן שלושה בתים:
כעת בוא ננסה לכתוב אליו Cats
:
כאשר שיחת ה־write()
מתבצעת, ה־REPL מחזיר 3
שמציין רק שלושה בתים נכתבו למטמון. כעת אמת שהמטמון מכיל את שלושת הבתים הראשונים:
REPL מחזיר:
Output'Cat'
הפונקציה write()
מוסיפה את הבתים בסדר רציונלי, כך שרק הבתים הראשונים נוספו למטמון.
להשוות, בואו ניצור Buffer
שמאחסן ארבעה בתים:
כתבו את אותם תוכן אליו:
ואז הוסיפו תוכן חדש שתופס פחות מקום מהתוכן המקורי:
מאחר שה-buffer כותב בסדר רציף, התחלה מ־0
, אם נדפיס את תוכן ה-buffer:
נתקבל:
Output'Hits'
שני התווים הראשונים נכתבים מחדש, אך שאר ה-buffer נשאר לא נגוע.
לעיתים הנתונים שאנו רוצים ב-buffer המקורי שלנו אינם במחרוזת אלא נמצאים באובייקט buffer אחר. במקרים אלו, אנו יכולים להשתמש בפונקציה copy()
כדי לשנות אילו נתונים ה-buffer שלנו אחסון.
בואו ניצור שני bufferים חדשים:
bufferי ה־wordsBuf
וה־catchphraseBuf
מכילים שני נתוני מחרוזות. אנו רוצים לשנות את catchphraseBuf
כך שהוא יאחסן Nananana Turtle!
במקום Not sure Turtle!
. נשתמש ב־copy()
כדי לקבל את Nananana
מה־wordsBuf
ולהעבירו ל־catchphraseBuf
.
כדי להעתיק נתונים מbuffer אחד לאחר, נשתמש בשיטת copy()
על ה-buffer שהוא המקור של המידע. לכן, בעוד ש־wordsBuf
מכיל את נתוני המחרוזת שברצוננו להעתיק, נצטרך להעתיק כך:
הפרמטר target
במקרה זה הוא ה-buffer של catchphraseBuf
.
כאשר אנו מכניסים את זה ל־REPL, הוא מחזיר 15
המציין ש־15 בתים נכתבו. מחרוזת Nananana
משתמשת רק ב־8 בתים של נתונים, לכן אנו מייד יודעים שהעתקתנו לא הלכה כפי שצוינו. השתמשו בשיטת toString()
כדי לראות את תוכן של catchphraseBuf
:
ה־REPL מחזיר:
Output'Banana Nananana!'
באופן ברירת המחדל, copy()
לקח את כל תוכן ה־wordsBuf
והניח אותו ב־catchphraseBuf
. אנו צריכים להיות יותר בררניים למטרה שלנו ולהעתיק רק Nananana
. בואו נכתוב מחדש את תוכן המקורי של catchphraseBuf
לפני שנמשיך:
לפונקציה copy()
יש כמה פרמטרים נוספים שמאפשרים לנו להתאים אילו נתונים מועתקים לבאפר השני. הנה רשימה של כל הפרמטרים של פונקציה זו:
target
– זהו הפרמטר היחיד הדרוש שלcopy()
. כפי שראינו משימוש הקודם שלנו, זהו הבאפר שבו רוצים להעתיק.targetStart
– זהו האינדקס של הבתים בבאפר היעד ממנה נתחיל להעתיק. כברירת מחדל זה0
, מה שאומר שהוא מעתיק נתונים החל מהתחלת הבאפר.sourceStart
– זהו האינדקס של הבתים בבאפר המקור ממנו נעתיק.sourceEnd
– זהו האינדקס של הבתים בבאפר המקור בו נעצור להעתיק. לפי ברירת המחדל, זה אורך הבאפר.
אז, כדי להעתיק Nananana
מתוך wordsBuf
אל catchphraseBuf
, ה- target
שלנו צריך להיות catchphraseBuf
כמו בעבר. ה- targetStart
יהיה 0
מאחר ואנו רוצים ש- Nananana
יופיע בתחילת catchphraseBuf
. ה- sourceStart
צריך להיות 7
מאחר שזהו האינדקס שבו מתחיל Nananana
ב- wordsBuf
. ה- sourceEnd
ימשיך להיות אורך הבאפרים.
בפקודת ה- REPL, העתק את תוכן wordsBuf
כך:
ה- REPL מאשר כי כתובו 8
בתים. שים לב ש- wordsBuf.length
משמש כערך עבור הפרמטר sourceEnd
. כמו במערכים, המאפיין length
נותן לנו את גודל הבאפר.
עכשיו נראה את תוכן catchphraseBuf
:
ה- REPL מחזיר:
Output'Nananana Turtle!'
!הצלחה! הצלחנו לשנות את הנתונים של catchphraseBuf
על ידי העתקת תוכן של wordsBuf
.
ניתן לצאת מ- REPL של Node.js אם תרצה. שים לב שכל המשתנים שנוצרו לא יהיו זמינים יותר כאשר תעשה זאת:
סיכום
במדריך זה, למדת שמזרנים הם הקצאות באורך קבוע בזיכרון המאחסנים נתונים בינריים. בתחילה יצרת מזרנים על ידי הגדרת גודלם בזיכרון ובאמצעות הטמעתם עם נתונים קיימים. לאחר מכן, קראת נתונים ממזרן על ידי בדיקת הבתים הפרטיים שלהם ובאמצעות שימוש בשיטות toString()
ו- toJSON()
. לבסוף, שינית את הנתונים המאוחסנים על ידי מתן שינויים לבתים הפרטיים שלו ובאמצעות שימוש בשיטות write()
ו- copy()
.
המזרנים נותנים לך מבט נהדר על איך נתונים בינאריים מנוהלים על ידי Node.js. עכשיו שאתה יכול להתקשר עם מזרנים, תוכל לצפות בדרכים שונות שבהן קידוד תווים משפיע על אופן האחסון של הנתונים. לדוגמה, תוכל ליצור מזרנים מנתוני מחרוזת שאינם בקידוד UTF-8 או ASCII ולראות את ההבדל בגודלם. תוכל גם לקחת מזרן עם UTF-8 ולהשתמש ב- toString()
כדי להמיר אותו לסכמות קידוד אחרות.
כדי ללמוד על מזרנים ב-Node.js, תוכל לקרוא את תיעוד ה-Node.js על אובייקט ה- Buffer
. אם ברצונך להמשיך ללמוד את Node.js, תוכל לחזור ל- סדרת לימודים על כיצד לתכנת ב-Node.js, או לעיין בפרויקטי תכנות והתקנות בדף הנושא שלנו בנושא Node.
Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js