JavaRush /مدونة جافا /Random-AR /العمل بايت بايت مع الملفات
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>والذي يحتوي على مليار عنصر بداخله. يشغل كل عنصر منه 16 بايت كحد أدنى (الرأس: 8 بايت + حقل int: 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 جيجا - ساعتان و 55 دقيقة. مرة أخرى هناك تحسن، ولكن صغير. وقد فعلنا كل شيء بأدوات بسيطة. ولم يستخدم المجهر لدق الأظافر . الآن استطراد غنائي. دعونا نتذكر هيكل الكمبيوتر. ذاكرة الوصول العشوائي (DRAM) ، حيث يتم عادة تنفيذ البرنامج وتخزين المتغيرات، لديها سرعة وصول عالية، ولكنها صغيرة الحجم. وعلى العكس من ذلك ، فإن الذاكرة الموجودة على محرك الأقراص الثابتة/محرك الأقراص المحمول (محركات الأقراص الثابتة أو محركات أقراص فلاش) التي يتم تخزين الملفات فيها عادةً، تتمتع بسرعة وصول منخفضة ولكنها كبيرة الحجم. لذلك عندما نقرأ ملفًا بحجم 1 جيجا بايت بايت بايت (أي أننا نصل إلى محرك الأقراص الثابتة مليار مرة)، فإننا نقضي الكثير من الوقت في العمل باستخدام جهاز منخفض السرعة (ننقل حبيبات الرمل بالحبوب من جسم شاحنة كاماز في رمل). دعونا نحاول تحسينه بشكل أكبر.

دعونا نتخلص من شاحنة 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 وهكذا، النتائج: ملف بحجم 46 ميجابايت، 0.13 ثانية (أقل من ثانية). ملف 1 جيجا - 9 ثواني. لقد فعلناها! نحن رائعون بشكل لا يصدق! تم تسريعها من 3 ساعات إلى 9 ثواني. هذا كل شيء، يمكنك الجلوس على كرسيك وشرب بعض الشاي. والآن تجربة أخرى - دعونا نحاول ملف 32 جيجابايت (على سبيل المثال، فيلم HD). نتيجة لذلك، نحصل على صوت طقطقة من محرك الأقراص الثابتة العامل مع تعطل البرنامج في نظام التشغيل Windows. قام كاماز بإلقاء الجسم بالرمل وكسر صندوق الرمل! ماذا نفعل؟ دعونا نتذكر حقيقة أخرى. عادةً ما يتم تخزين الملفات الموجودة في نظام التشغيل في أجزاء (مجموعات) يتراوح حجمها بين 2 و64 كيلو بايت (حسب نوع نظام الملفات والإعدادات وما إلى ذلك). سنقرأ في أجزاء، على سبيل المثال 64000 بايت. دعونا نحاول تفريغ كاماز بحفارة بأجزاء كبيرة إلى حد ما:

باستخدام المخزن المؤقت

// Вариант 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 جيجا بايت قمنا بتحسين الأداء من عدة ساعات إلى أجزاء من الثواني !!! بهذه الحقيقة المتواضعة، سننهي التجربة ونحسن الكود الأولي. لقد أحرزنا تقدمًا في نواحٍ عديدة - نحن سعداء بالمؤشرات الجديدة لاستهلاك الذاكرة ووقت التشغيل. وفي هذه الحالة أيضًا، لا نقوم بسحب المجموعات غير المفيدة من المكتبة القياسية. ملاحظة: سيقول شخص ما أن المثال بعيد المنال، وما إلى ذلك. ولكن هناك الكثير من المهام المشابهة - تحليل كمية هائلة من العناصر التي لها عدد محدود من الحالات. على سبيل المثال، الصور (RGB - يتم تخزينها عادةً في 24 بايت، في حالتنا long[] arrRGB = new long[256*256*256] ستشغل 64 ميجابايت فقط من الذاكرة)، والموسيقى (يتم ترقيم السعة عادةً في 16 أو 24 بت ) أو أجهزة استشعار المؤشرات المنفصلة، ​​وما إلى ذلك.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION