JavaRush /وبلاگ جاوا /Random-FA /کار بایت به بایت با فایل ها
Joysi
مرحله

کار بایت به بایت با فایل ها

در گروه منتشر شد
ویژه برای

بیا شروع کنیم

در سطح 18، اولین وظایف خواندن فایل بایت به بایت آغاز شد: خواندن فایل، سپس یافتن حداقل/حداکثر بایت ها یا خروجی آن به صورت مرتب و غیره.
کار بایت به بایت با فایل ها - 1
مردم اینجا بسیار باهوش هستند. آنها درباره مجموعه ها می دانند و می توانند مرتب کنند و درج کنند. مجموعه ها مکانیزم قدرتمندی هستند. و بسیاری قبل از JavaRush اصلا از آنها استفاده نمی کردند. البته مطالعه آنها و تلاش برای ضربه زدن به آنها در مکانهای اشتباه قابل ستایش است. بنابراین. بیایید مشکلی را در نظر بگیریم که در وظایف نیست (به طوری که هنگام حل آن اسپویلر وجود نداشته باشد)، اما موارد بسیار مشابهی وجود دارد:
  • نام فایل را از کنسول وارد کنید
  • خواندن تمام بایت ها از یک فایل.
  • با نادیده گرفتن تکرارها، آنها را بر اساس بایت کد به ترتیب نزولی مرتب کنید.
  • نمایش دادن
  • جریان ورودی/خروجی را ببندید
نمونه بایت های یک فایل ورودی 44 83 44 مثالی از خروجی 83 44 ما علاوه بر این متغیرها startTimeو finishTimeبرای ثبت زمان اجرای برنامه را معرفی کردیم. برای محاسبه از i3-3GHz/8Gb RAM/HDD WD Blue-1Tb/Win7-64/jdk-8u73-windows-x64 استفاده کردم (نمونه هایی از برنامه ها در گزینه های 1-2 از انجمن info.javarush گرفته شده اند، آنها کمی هستند فقط برای مرتب سازی به ترتیب صعودی اصلاح شده است - یعنی واقعی هستند!!)

بیایید آن را مستقیماً حل کنیم:

// Вариант 1. Загоняем в коллекцию и сортируем используя ее метод Collections.sort
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());
        long startTime = System.currentTimeMillis();

        ArrayList<Integer> listData = new ArrayList<Integer>();
        while (inputStream.available() > 0) listData.add(inputStream.read());
        inputStream.close();
        ArrayList<Integer> result = new ArrayList<Integer>(new HashSet<Integer>(listData));
        Collections.sort(result);

        while (!result.isEmpty()) {
            System.out.print(result.get(result.size()-1) + " ");
            result.remove(result.get(result.size()-1));
        }

        long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
همه چیز را عالی حل می کند! آزمون (اگر بود، با صدای بلند می گذشت). اما در زندگی تعداد کمی فایل وجود دارد که فقط حاوی خط "مامان قاب را شست". بیایید برنامه خود را با یک فایل 46 مگابایتی تغذیه کنیم (با استانداردهای امروزی، به نظر زیاد نمی رسد). چیه، برنامه 220 ثانیه اجرا میشه. تلاش برای تغذیه یک فایل 1 گیگابایتی در شب (اندازه فیلم MPEG4 از بهترین کیفیت برخوردار نیست) ناموفق بود. من هنوز در حال خواندن برنامه در صبح بودم - و باید از قبل سر کار می رفتم. مشکل چیه؟ احتمالاً در حال استفاده است ArrayList<Integer>که دارای 1 میلیارد عنصر در داخل است. هر عنصر آن حداقل 16 بایت را اشغال می کند (سرصفحه: 8 بایت + بین فیلد: 4 بایت + تراز برای تعدد 8: 4 بایت). در مجموع، ما به طور داوطلبانه 16 گیگ دیتا را با رم 8 در حافظه قرار می دهیم. بهتر عمل خواهیم کرد. بیایید عمیق تر به مجموعه ها شیرجه بزنیم. و هورای، ما آنچه را که نیاز داشتیم پیدا کردیم.

با TreeSet آشنا شوید

این خیلی است:
  • اجازه ذخیره دو عنصر یکسان را نمی دهد (به این معنی که ما به جای یک میلیارد، همه 255 عنصر را در حافظه ذخیره خواهیم کرد!)
  • هنگام دستکاری عناصر خود، به طور خودکار سازماندهی می کند (خود را مرتب می کند - اینجا اوج کمال است!)
ما گرفتیم:
// Вариант 2. Загоняем в ТreeSet который сам сортирует (лютый win!)
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        byte[] arrBytes = new byte[256];
        long startTime = System.currentTimeMillis();

        SortedSet<Integer> list = new TreeSet<Integer>();
        while(inputStream.available()>0) list.add(inputStream.read());
        inputStream.close();

        while (!list.isEmpty())        {
            System.out.print(list.last() + " ");
            list.remove(list.last());
        }

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
خروجی: فایل 46 مگابایتی، 176 ثانیه. فایل 1 گیگابایت - 3 ساعت و 5 دقیقه. پیشرفت آشکار است. ما توانستیم برای نتایج "منتظر" بمانیم و فایل 46 مگابایتی به طور قابل توجهی سریعتر پردازش می شود. برو جلو. بیایید سعی کنیم مجموعه ها را رها کنیم (این برای برخی به شدت دردناک خواهد بود). ما از آرایه های ساده استفاده خواهیم کرد (خیلی ابتدایی است). بیایید به یک نکته مهم توجه کنیم . تعداد بایت های مواجه شده را می توان در آرایه ای به طول 256 قرار داد. بنابراین ما به سادگی عنصر آرایه مربوط به بایت خوانده شده را یک بار افزایش می دهیم.

آرایه - بایت به بایت

// Вариант 3. Считываем массив поbyteно.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        while (inputStream.available() > 0) arrBytes[inputStream.read()]++;

		inputStream.close();
        // Выводим отсортированный по byte-codeу в обратном порядке
        for (long i = 255; i >= 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

			long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
خروجی: فایل 46 مگابایتی، 158 ثانیه. فایل 1 گیگابایت - 2 ساعت 55 دقیقه. باز هم یک پیشرفت، اما کوچک. و ما همه چیز را با ابزار ساده انجام دادیم. از میکروسکوپ برای کاشت ناخن استفاده نکرده است . حالا یک انحراف غزلی. بیایید ساختار یک کامپیوتر را به یاد بیاوریم. حافظه رم (DRAM) که معمولاً برنامه در آن اجرا می شود و متغیرها ذخیره می شوند، سرعت دسترسی بالایی دارد، اما اندازه کوچکی دارد. حافظه روی هارد/فلش درایو (درایوهای HDD یا فلش) که معمولاً فایل‌ها در آن ذخیره می‌شوند، برعکس، سرعت دسترسی پایینی دارد اما اندازه بزرگی دارد. بنابراین وقتی یک فایل 1 گیگابایتی را بایت به بایت می خوانیم (یعنی یک میلیارد بار به HDD دسترسی داریم)، ​​زمان زیادی را صرف کار با یک دستگاه کم سرعت می کنیم (دانه دانه شن را از بدنه کامیون KamAZ منتقل می کنیم. در جعبه شنی). بیایید سعی کنیم آن را بیشتر بهبود بخشیم.

بیایید یکباره کل کامیون کاماز را با ماسه بریزیم!

// Вариант 4. Считываем массив сразу целиком за раз в память.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        byte fileImage[]=new byte[inputStream.available()];
        long fileSize=fileImage.length;
        inputStream.read(fileImage);
        for (int i = 0; i = 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
یک انحراف کوچک، اما باز هم مهم . توجه:
  1. شاخص arrBytes در 0..255 تعریف شده است،
  2. fileImage یک آرایه بایتی است که عناصر آن دارای مقدار -128..127 هستند
بنابراین، برای شمارش بایت ها، از ساختاری استفاده می کنیم arrBytes[fileImage[i] & 0b11111111]++; که به سادگی بیت علامت را بازنشانی می کند و مقداری در محدوده 0..255 به ما برمی گرداند و به این ترتیب، نتایج: 46MB فایل 0.13 ثانیه (کمتر از یک ثانیه). فایل 1 گیگابایت - 9 ثانیه. ما آن را انجام دادیم! ما فوق العاده باحالیم! افزایش سرعت از 3 ساعت به 9 ثانیه. تمام است، می توانید روی صندلی خود بنشینید و چای بنوشید. و اکنون آزمایش دیگری - بیایید یک فایل 32 گیگابیتی (مثلاً یک فیلم HD) را امتحان کنیم. در نتیجه، با خراب شدن برنامه در ویندوز، صدای ترقه از هارد دیسک کار دریافت می کنیم. کاماز جسد را با ماسه انداخت و جعبه شن را شکست! چه کنیم؟ بیایید یک واقعیت دیگر را به خاطر بسپاریم. فایل‌ها در سیستم‌عامل معمولاً در بخش‌هایی از ۲ تا ۶۴ کیلوبایت (بسته به نوع سیستم فایل، تنظیمات و غیره) ذخیره می‌شوند. ما در بخش هایی، به عنوان مثال 64000 بایت می خوانیم. بیایید سعی کنیم KamAZ را با یک بیل مکانیکی در بخش های نسبتاً بزرگ تخلیه کنیم:

استفاده از بافر

// Вариант 5. Считываем массив кусками.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        int  bufferSize = 64000;
        byte buffer[]   = new byte[64000];

        while (inputStream.available() > 0) {
            if (inputStream.available() < 64000) bufferSize = inputStream.available();
            inputStream.read(buffer, 0, bufferSize );
            for (int i = 0; i = 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
در نتیجه، دریافت کردیم: فایل 46 مگابایتی 0.08 ثانیه (کمتر از یک ثانیه). فایل 1 گیگابایت - 0.9 ثانیه (کمتر از یک ثانیه). فایل 32 گیگابایت - 31 ثانیه. توجه داشته باشید که برای یک فایل 1 گیگابایتی ما عملکرد را از چند ساعت به کسری از ثانیه بهبود بخشیده ایم !!! با این واقعیت ساده، آزمایش را به پایان خواهیم رساند و کد اولیه را بهبود خواهیم داد. ما از بسیاری جهات پیشرفت کرده ایم - از شاخص های جدید مصرف حافظه و زمان کار خرسندیم. همچنین، در این مورد، مجموعه های بی فایده را از کتابخانه استاندارد بیرون نمی آوریم. PS یکی می گوید مثال دور از ذهن است و غیره. اما وظایف مشابه زیادی وجود دارد - تجزیه و تحلیل حجم عظیمی از عناصر که دارای تعداد محدودی از حالت ها هستند. به عنوان مثال، تصاویر (RGB - معمولاً در 24 بایت ذخیره می شود، در مورد ما طولانی[] arrRGB = new long[256*256*256] تنها 64 مگابایت حافظه را اشغال می کند)، موسیقی (دامنه معمولاً در 16 یا 24 بیت دیجیتالی می شود. ) یا سنسورهای نشانگر گسسته و غیره.
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION