"อามิโกะ เท็นฮัท!"

"ฉันมีความสุขที่ได้เรียน Java กัปตัน!"

"สบายใจได้ Amigo วันนี้เรามีหัวข้อที่น่าสนใจมาก เราจะพูดถึงวิธีที่โปรแกรม Java โต้ตอบกับทรัพยากรภายนอก และเราจะศึกษาคำสั่ง Java ที่น่าสนใจมาก อย่าปิดหูไว้ดีกว่า"

"ฉันหูทั้งหมด"

"ขณะที่โปรแกรม Java ทำงาน บางครั้งโปรแกรมจะโต้ตอบกับเอนทิตีภายนอกเครื่อง Java เช่น กับไฟล์บนดิสก์ เอนทิตีเหล่านี้มักเรียกว่าทรัพยากรภายนอก"

“แล้วอะไรล่ะที่ถือว่าเป็นทรัพยากรภายใน?”

"ทรัพยากรภายในคือวัตถุที่สร้างขึ้นภายในเครื่อง Java โดยทั่วไปแล้ว การโต้ตอบเป็นไปตามโครงร่างนี้:

งบทดลองกับทรัพยากร

"ระบบปฏิบัติการติดตามทรัพยากรที่มีอยู่อย่างเข้มงวด และยังควบคุมการเข้าถึงร่วมกันจากโปรแกรมต่างๆ ตัวอย่างเช่น ถ้าโปรแกรมหนึ่งเปลี่ยนไฟล์ โปรแกรมอื่นจะไม่สามารถเปลี่ยนแปลง (หรือลบ) ไฟล์นั้นได้ หลักการนี้ไม่ใช่ จำกัด เฉพาะไฟล์ แต่ให้ตัวอย่างที่เข้าใจได้ง่ายที่สุด

"ระบบปฏิบัติการมีฟังก์ชัน (API) ที่อนุญาตให้โปรแกรมรับและ/หรือปล่อยทรัพยากร หากทรัพยากรไม่ว่าง เฉพาะโปรแกรมที่ได้รับมาเท่านั้นที่สามารถทำงานได้ ถ้าทรัพยากรนั้นว่าง โปรแกรมใดๆ ก็สามารถรับได้ มัน.

"ลองนึกภาพว่าสำนักงานมีแก้วกาแฟร่วมกัน ถ้ามีคนเอาแก้วไป คนอื่นจะรับไม่ได้อีกต่อไป แต่เมื่อแก้วถูกใช้งาน ล้าง และใส่กลับเข้าที่แล้ว ใครๆ ก็หยิบได้อีก"

"เข้าใจแล้ว มันก็เหมือนกับที่นั่งบนรถไฟใต้ดินหรือระบบขนส่งสาธารณะอื่นๆ ถ้าที่นั่งว่าง ใครก็นั่งได้ ถ้าที่นั่งว่าง คนที่นั่งก็จะเป็นคนควบคุม"

"ถูกต้อง และตอนนี้เรามาพูดถึงการรับทรัพยากรภายนอกทุกครั้งที่โปรแกรม Java ของคุณเริ่มทำงานกับไฟล์บนดิสก์ เครื่อง Java จะถามระบบปฏิบัติการเพื่อการเข้าถึงแบบเอกสิทธิ์เฉพาะบุคคล หากทรัพยากรนั้นว่าง เครื่อง Java จะได้รับ มัน.

"แต่หลังจากที่คุณทำงานกับไฟล์เสร็จแล้ว ทรัพยากร (ไฟล์) นี้จะต้องได้รับการปล่อยตัว กล่าวคือ คุณต้องแจ้งให้ระบบปฏิบัติการทราบว่าคุณไม่ต้องการอีกต่อไปหากคุณไม่ทำเช่นนี้ ทรัพยากรจะยังคงเป็น จัดโดยโปรแกรมของคุณ "

"นั่นฟังดูยุติธรรม"

"เพื่อให้เป็นเช่นนั้น ระบบปฏิบัติการจะรักษารายการของทรัพยากรที่ครอบครองโดยโปรแกรมที่กำลังรันอยู่ ถ้าโปรแกรมของคุณมีทรัพยากรเกินขีดจำกัดที่กำหนดไว้ ระบบปฏิบัติการจะไม่ให้ทรัพยากรใหม่แก่คุณอีกต่อไป

"มันเหมือนกับโปรแกรมที่สามารถกินหน่วยความจำทั้งหมด ... "

"อะไรทำนองนั้น ข่าวดีก็คือหากโปรแกรมของคุณยุติลง ทรัพยากรทั้งหมดจะถูกปล่อยโดยอัตโนมัติ (ระบบปฏิบัติการเองก็ทำเช่นนี้)"

“ถ้านั่นเป็นข่าวดี แสดงว่ามีข่าวร้ายหรือเปล่า”

"ใช่แล้ว ข่าวร้ายก็คือ หากคุณกำลังเขียนแอปพลิเคชันเซิร์ฟเวอร์..."

"แต่ฉันจะเขียนใบสมัครดังกล่าวหรือไม่"

"แอปพลิเคชันเซิร์ฟเวอร์จำนวนมากเขียนด้วยภาษาจาวา ดังนั้นเป็นไปได้มากว่าคุณจะเขียนแอปพลิเคชันเหล่านี้เพื่อการทำงาน อย่างที่ฉันพูด หากคุณกำลังเขียนแอปพลิเคชันเซิร์ฟเวอร์ เซิร์ฟเวอร์ของคุณจะต้องทำงานไม่หยุดเป็นเวลาหลายวัน สัปดาห์ เดือน ฯลฯ"

"กล่าวอีกนัยหนึ่งคือ โปรแกรมไม่ได้หยุดทำงาน และนั่นหมายความว่าหน่วยความจำจะไม่ถูกปล่อยออกมาโดยอัตโนมัติ"

"ถูกต้อง และถ้าคุณเปิดไฟล์ 100 ไฟล์ต่อวันและไม่ปิดมัน ในอีกไม่กี่สัปดาห์ แอปพลิเคชันของคุณจะถึงขีดจำกัดของทรัพยากรและหยุดทำงาน"

"งานที่มั่นคงเหลืออีกหลายเดือน! จะทำอะไรได้บ้าง"

"คลาสที่ใช้ทรัพยากรภายนอกมีวิธีพิเศษในการเผยแพร่: close().

"ต่อไปนี้เป็นตัวอย่างของโปรแกรมที่เขียนบางอย่างลงในไฟล์แล้วปิดไฟล์เมื่อทำงานเสร็จ นั่นคือทำให้ทรัพยากรของระบบปฏิบัติการว่างลง หน้าตาประมาณนี้:

รหัส บันทึก
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
เส้นทางไปยังไฟล์.
รับวัตถุไฟล์: รับทรัพยากร
เขียนลงไฟล์
ปิดไฟล์ - ปล่อยทรัพยากร

"อา... หลังจากทำงานกับไฟล์ (หรือทรัพยากรภายนอกอื่น ๆ) ฉันต้องเรียกใช้เมธอดclose()บนวัตถุที่เชื่อมโยงกับทรัพยากรภายนอก"

"ใช่ ทุกอย่างดูเหมือนง่าย แต่ข้อยกเว้นสามารถเกิดขึ้นได้เมื่อโปรแกรมทำงาน และทรัพยากรภายนอกจะไม่ถูกปล่อยออกมา"

“แล้วมันแย่มาก ทำไงดี”

"เพื่อให้แน่ใจว่าclose()มีการเรียกใช้เมธอดเสมอ เราจำเป็นต้องห่อโค้ดของเราใน บล็อก try- catch- finallyและเพิ่มclose()เมธอดลงในfinallyบล็อก ซึ่งจะมีลักษณะดังนี้:

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

"หืม... มีอะไรผิดปกติที่นี่?"

"ถูกต้อง โค้ดนี้จะไม่คอมไพล์ เนื่องจากoutputตัวแปรถูกประกาศภายในtry{}บล็อก ดังนั้นจึงมองไม่เห็นในfinallyบล็อก

มาแก้ไขกันเถอะ:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

“ตอนนี้ทุกอย่างโอเคไหม?”

"ไม่เป็นไร แต่จะไม่ทำงานหากมีข้อผิดพลาดเกิดขึ้นเมื่อเราสร้างวัตถุFileOutputStreamและสิ่งนี้อาจเกิดขึ้นได้ค่อนข้างง่าย

มาแก้ไขกันเถอะ:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

"และทุกอย่างทำงานตอนนี้หรือไม่"

"ยังมีข้อวิจารณ์อยู่บ้าง ประการแรก หากมีข้อผิดพลาดเกิดขึ้นเมื่อสร้างวัตถุFileOutputStreamตัวแปรนั้นoutputจะเป็นโมฆะ ความเป็นไปได้นี้จะต้องนำมาพิจารณาในfinallyบล็อก

" close()ประการที่สอง เมธอดจะถูกเรียกใช้ในfinallyบล็อกเสมอ ซึ่งหมายความว่าไม่จำเป็นในtryบล็อก รหัสสุดท้ายจะมีลักษณะดังนี้:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output!=null)
      output.close();
}

"แม้ว่าเราจะไม่พิจารณาบล็อกcatchซึ่งสามารถละเว้นได้ ดังนั้นโค้ด 3 บรรทัดของเราจึงกลายเป็น 10 แต่โดยพื้นฐานแล้ว เราเพิ่งเปิดไฟล์และเขียน 1"

“ฟู่... เป็นเรื่องที่ดีที่สรุปเรื่องนี้ได้ ค่อนข้างเข้าใจ แต่ค่อนข้างน่าเบื่อใช่ไหม”

"เป็นเช่นนั้น นั่นเป็นเหตุผลที่ผู้สร้างของ Java ช่วยเราโดยการเพิ่มน้ำตาลเชิงประโยค ตอนนี้เรามาดูไฮไลท์ของโปรแกรมหรือมากกว่านั้นคือบทเรียนนี้:

try-พร้อมทรัพยากร

"เริ่มต้นด้วยเวอร์ชันที่ 7 Java มีtryคำสั่ง -with-resources ใหม่

"มันถูกสร้างขึ้นอย่างแม่นยำเพื่อแก้ปัญหาด้วยการเรียกเมธอดที่จำเป็นclose()"

"ฟังดูมีความหวัง!"

"กรณีทั่วไปดูค่อนข้างง่าย:

try (ClassName name = new ClassName())
{
   Code that works with the name variable
}

"นี่คือการเปลี่ยนแปลงของtry คำสั่ง อื่น ?"

"ใช่ คุณต้องเพิ่มวงเล็บหลังtryคำหลัก จากนั้นสร้างวัตถุที่มีทรัพยากรภายนอกอยู่ภายในวงเล็บ สำหรับแต่ละวัตถุในวงเล็บ คอมไพเลอร์จะเพิ่มส่วนfinallyและการเรียกใช้close()เมธอด

"ด้านล่างเป็นตัวอย่างที่เทียบเท่ากันสองตัวอย่าง:

รหัสยาว รหัสที่มีการลองด้วยทรัพยากร
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output!=null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

"เจ๋งมาก! โค้ดที่ใช้try-with-resources นั้นสั้นกว่าและอ่านง่ายกว่ามาก ยิ่งเรามีโค้ดน้อย โอกาสพิมพ์ผิดหรือข้อผิดพลาดอื่นๆ ก็น้อยลง"

"ฉันดีใจที่คุณชอบ อย่างไรก็ตาม เราสามารถเพิ่มcatchและfinallyบล็อกtryคำสั่ง -with-resources ได้ หรือคุณไม่สามารถเพิ่มได้หากไม่จำเป็น

หลายตัวแปรในเวลาเดียวกัน

"คุณอาจพบกับสถานการณ์บ่อยครั้งเมื่อจำเป็นต้องเปิดไฟล์หลายไฟล์พร้อมกัน สมมติว่าคุณกำลังคัดลอกไฟล์ ดังนั้นคุณต้องมีวัตถุสองชิ้น: ไฟล์ที่คุณกำลังคัดลอกข้อมูลและไฟล์ที่คุณกำลังคัดลอกข้อมูล .

"ในกรณีนี้tryคำสั่ง -with-resources ให้คุณสร้างหนึ่งแต่หลายวัตถุในนั้น โค้ดที่สร้างวัตถุต้องคั่นด้วยเครื่องหมายอัฒภาค นี่คือลักษณะทั่วไปของคำสั่งนี้:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

ตัวอย่างการคัดลอกไฟล์:

รหัสสั้น รหัสยาว
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input!=null)
      input.close();
   if (output!=null)
      output.close();
}

"เราจะว่าอย่างไรดี - tryด้วยทรัพยากรเป็นสิ่งที่วิเศษมาก!"

"สิ่งที่เราสามารถพูดได้ว่าเราควรใช้มัน"