سلام خدمت شما دوستان و همراهان سایت اسمارت دولوپرز . تو این آموزش میخام شما رو با مفاهیم InputStream و OutputStream آشنا کنم و درک این دو مورد رو براتون راحت ترکنم .
InputStream چیست ؟
ما در جاوا برای اینکه بتونیم از یک ورودی اطلاعات رو دریافت کنیم باید یک InpustStream از اون ورودی بگیریم . حالا ورودی ما میخاد یک فایل تکست یا عکس باشه یا یک Socket یا یک کانکشن اینترنت . InputStream زیر کلاس های زیادی داره مثل FileInputStream ، ObjectInputStream ، ByteInputStream و … . که هرکدوم از اینها برای موارد خاصی استفاده میشن .
OutPutStream چیست ؟
برعکس InputStream که برای خوندن اطلاعات نیاز بود ما برای نوشتن اطلاعات به OutPutStream ها نیاز داریم . OutPutStream ها هم متناظر با هر InputStream یک زیر کلاس داره مثل FileOutPutStream ، ObjectOutPutStream و … .
چگونه اطلاعات را از InputStream بخوانیم ؟
کلاس های InputStream همشون متد های ()read برای خودند اطلاعات دارند و سه نوع متد read داریم .
اولیش که هیچ پارامتری دریافت نمیکنه به این صورته :
1 |
inputStream.read() |
این متد ()read میاد دونه دونه بایت هارو از ورودی میخونه ! توجه کنید دونه دونه ! حالا فرض کنید شما یک فایل 50 مگابایتی دارید اینجا اگه همزمان از ورودی بخونیم و همون لحطه بخایم تو خروجی رایت کنیم یعنی توی یه حلقه بخونیم و رایت کنیم، باید چند ساعت طول بکشه تا کل فایل رو بخونه ! اما خوشبختانه متد های بعدی ()read اینطوری نیستن .
نسخه بعدی متد ()read که یک پارامتر از جنس آرایه از byte ها میگیره :
1 2 |
byte[] buffer=new byte[1024*4]; inputStream.read(buffer); |
اینجا این متد میاد به اندازه سایز آرایه buffer ینی به اندازه ()buffer.length بایت از ورودی میخونه و توی آرایه buffer ذخیره میکنه . با این روش دیگه چون ما اومدیم مقدار آرایه رو 4Kb ساختیم به اندازه 4 کیلوبایت از ورودی به یکباره میخونه . این متد یک مقدار بازگشتی هم داره که مقدار بازگشتیش تعداد بایت هایی هست که از ورودی خونده .
فقط اینو در نظر داشته باشید که زیاد کردن سایز buffer ممکنه خوب نباشه . چون این اندازه بستگی به سخت افزار و بلاک های مموری و خیلی عوامل دیگه داره که در حال حاضر مناسب ترین عدد همین 4 کیلوبایته . البته ممکنه 16kb و یا 32kb هم گزینه مناسبی باشند .
خب آخرین نسخه متد ()read که سه تا پارامتر میگیره به این صورته :
1 2 3 4 |
//InputStream.read(byte[] buffer,int offset,int length); byte[] buffer=new byte[1024*4]; int len=0; len=inputStream.read(buffer,0,buffer.length()); |
اینجا متد read میاد مقدار length عدد بایت رو از ورودی میخونه و از مکان buffer[offset] شروع به نوشتن داخل آرایه buffer میکنه (به قسمت کامنت شده دقت کنید ) و در مقدار بازگشتی هم تعداد بایت های خونده شده رو برگشت میده البته اگه به اندازه length بایت توی ورودی نداشته باشیم مسلما به همون اندازه بایتی که وجود داره میخونه که ممکنه کمتر از مقدار length باشه .
نکته : اما مقدار بازگشتی متد های ()read زمانی که به انتهای فایل رسیده باشیم مقدار1- برگشت میده .
این مثال رو ببینید و به خروجی دقت کنید :
1 2 3 4 5 6 |
byte[] in= {1,2,3,4,5,6,7,8}; InputStream stream=new ByteArrayInputStream(in); byte[] buffer=new byte[8]; stream.read(buffer,2,3); System.out.println("buffer="+Arrays.toString(buffer)); //buffer=[0, 0, 1, 2, 3, 0, 0, 0] |
چگونه اطلاعات را در OutputStream بنویسیم ؟
کلاس های OutputStream هم متد هایی برای نوشتن اطلاعات دارند که متناظر با متد های ()read کلاس های InputStream هست .
اولیش متد write ی هست که هیچ پارامتری نمیگیره و دونه دونه بایت هارو داخل خروجی رایت میکنه .
متد بعدی هم که یک پارامتر از جنس آرایه ای از byte ها دریافت میکنه .
1 |
outputStream.write(buffer); |
این متد مقدار بایت های موجود داخل آرایه buffer رو توی خروجی رایت میکنه .
و متد بعدی هم به این صورته :
1 2 |
//write(byte[] buffer, int offset, int lenght) outputStream.write(buffer,offset,langth); |
این متد به اندازه length تعداد بایت رو از مکان offset در آرایه buffer میخونه و توی خروجی رایت میکنه (به قسمت کامنت شده دقت کنید ). یعنی چیزی که توی خروجی رایت میشه به این صورته :
1 |
buffer[offset] تا buffer[offset+length] |
این متد رو اگه به این صورت بنویسیم :
1 |
write(buffer, 0, buffer.length) |
برابر این کده :
1 |
write(buffer) |
حالا اگه آخرین مثالی که توی بخش inputStream زدیم رو تکمیل کنیم و توی خروجی رایت کنیم با offset و length دلخواه ببینیم خروجی چی میشه :
1 2 3 4 5 6 7 8 9 10 11 |
byte[] in= {1,2,3,4,5,6,7,8}; InputStream stream=new ByteArrayInputStream(in); ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); byte[] buffer=new byte[8]; stream.read(buffer,2,3); System.out.println("buffer="+Arrays.toString(buffer)); outputStream.write(buffer,1,3); byte[] out=outputStream.toByteArray(); System.out.println("out="+Arrays.toString(out)); //buffer=[0, 0, 1, 2, 3, 0, 0, 0] //out=[0, 1, 2] |
مثال عملی:
خب الان بیایم با چیزهایی که یاد گرفتیم یک فایل رو به عنوان ورودی بخونیم و توی یه فایل دیگه بنویسیمیش . ( عمل کپی کردن فایل ) .
برای اینکار اول باید یک InputStream از فایل بگیریم و تا زمانی که به انتهای فایل نرسیدیم به خوندن از اون فایل ادامه بدیم و هرچقدر که بایت از ورودی خوندیم رو توی خروجی هم رایت کنیم . برای کارکردن با فایل ها ما از FileInputStream و FileOutputStream استفاده میکنیم .
برای مثال فرض کنید میخایم فایلی با آدرس “/myFolder/file.txt/” رو توی پوشه دیگه ای کپی کنیم مثلا “/folder2/file.txt” .
خب به این صورت باید بنویسیم :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
File sourceFile=new File("/myFolder/file.txt"); File targetFile=new File("/folder2/file.txt"); FileInputStream fileInputStream=null; FileOutputStream fileOutputStream=null; try { fileInputStream=new FileInputStream(sourceFile); fileOutputStream=new FileOutputStream(targetFile); byte[] buffer=new byte[1024*4]; while (fileInputStream.read(buffer)!=-1){ fileOutputStream.write(buffer); } }catch (IOException e) { e.printStackTrace(); }finally { try { if(fileInputStream!=null) fileInputStream.close(); if(fileOutputStream!=null) fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } |
خیلی راحت و بدون هیچ سردرگمی باید متوجه شده باشید که چی شده .
همونطور که گفتم باید تا زمانی که فایل به انتها نرسیده از فایل مقدار buffer.length بایت رو بخونیم و هرچقدر که خوندیم رو توی خروجی هم رایت کنیم . پس میایم از حلقه while استفاده میکنیم و توی شرطش هم مقدار بازگشتی متد ()read رو بررسی میکنیم که اگه تا زمانی که 1- نشده به خوندن ادامه بده و وقتی که به مقدار 1- رسیدیم ینی به آخر اون فایل رسیدیم باید از حلقه خارج شیم . و در نهایت هم باید InputStream ها و OutputStream هارو close کنیم تا منابعی که اشغال کردیم رو رها کنیم تا بقیه هم بتونن از این این منابع استفاده کنند . اما این روش یه مشکلی داره . مثلا فرض کنید ما توی یه فایلی 4 بایت رایت کردیم :
1 |
fileOutputStream.write(new byte[]{1,2,3,4}); |
حالا اگه به روش بالا بخایم از این فایل اطلاعات رو بخونیم و توی یه فایل دیگه رایت کنیم ینی 4 کیلوبایت از این فایل بخونیم ( اینجا چون 4 بایت بیشتر اطلاعات نداره فقط 4 بایت خونده میشه ) و توی آرایه 4 کیلوبایتی buffer قرار بدیم ، این آرایه 4 خونه اولش فقط مقدار داره و بقیش مقدار صفر داره . و اگه بیایم این آرایه رو با استفاده از متد
1 |
fileInputStream.write(buffer); |
توی خروجی رایت کنیم ، اینجا ما یه فایل جدید داریم که حجم فایلش 4 کیلوبایته ، در صورتی که فایل ورودی ما فقط 4 بایت حجم داشت ! به همین منظور ما همیشه برای رایت کردن باید تعداد بایت هایی که از ورودی خونده شده رو نگه داریم و به همون تعداد توی خروجی رایت کنیم . به این کد دقت کنید :
1 2 3 4 5 |
byte[] buffer=new byte[4*1024]; int lenght=0; while((lenght=fileInputStream.read(buffer))!=-1){ fileOutputStream.write(buffer,0,lenght); } |
به این ترتیب ما فقط به همون اندازه که بایت خوندیم رو توی خروجی رایت میکنیم و حجم فایل خروجی ما هم فقط 4 بایت خواهد بود .
کپی کردن فایل ها در اندروید :
در اندروید هم روش کپی کردن به همین صورته ولی به دست اوردن InputStream و OutputStream ها فرق میکنه .
اگه شما Uri مبدا و مقصد رو داشته باشید با استفاده از متد openInputStream و openOutputStream کلاس ContentResolver میتونید به ورودی و خروجی دسترسی داشته باشید و متونید از ورودی بخونید و توی خروجی رایت کنید
1 |
getContentResolver().openInputStream(uri); |
و اگه هم آدرس کامل فایل رو دارید که به روش FileInputStream و FileOutputStream میتونید عمل کپی کردن رو انجام بدید . . یا ترکیبی از اینها . مهم داشتن InputStream و OutputStream هست .
نکته : از اونجایی که کار با فایل ها و InputStream ها و OutputStream ها عمل سنگینی هست و باعث بلاک شدن UI میشه باید این عملیات رو داخل Thread ی غیر از main Thread انجام بدید .
خب امید وارم این آموزش براتون مفید واقع شده باشه . منتظر نظرات و پیشنهاداتون هستیم .
عالی بازم
ای ول یعنی Bravo. اشتباهه تو عشقه
بابا دمت گررررررررررررررررم مصطفی جان
بابا تو دیگه کی هستی
چقدرررررررررر قشنگ توضیح دادی
از ساده به سخت
خدایی ایول داری
ایشالا همیشه موفق باشی
حال کردم با توضیحاتت
دیدم نمیشه نظر ندم
عالیییییییییییی بود
خیلی ممنون 🙂
شما هم موفق باشی
دمت گرم خدا خیرت بده، همینجوری ساده توضیح بدین
عالی بود.
یه سوال: بعد اتمام کار هم خواندن و هم نوشتن در هر دو حال باید close کنیم؟ ببخشین flush چکاری میکند؟ در بعضی جاها قبل از close عمل flush انجام داده اند.
بله برای اینکه منابعی که از اون ورودی یا خروجی در اختیار گرفتیم رو آزاد کنیم باید close کنیم . در توضیح متد flush ، بیشتر کلاس های I/O بایت ها رو قبل از نوشتن توی خروجی بافر میکنند . تا پرفورمنس رو بالا ببرند . و فراخوانی متد flush باعث میشه که تا هر اندازه که بافر شده حالا چه بافر پر شده باشه یا نه در همون لحظه بافر تخلیه و توی خروجی نوشته بشه . بعضی از زیر کلاس های OutputStream خودشون به جای مناسبش از متد flush استفاده میکنند و در واقع AutoFlush هستند . و… بیشتر »