JavaRush /Java блогы /Random-KK /Файлдармен байт-байт жұмыс
Joysi
Деңгей

Файлдармен байт-байт жұмыс

Топта жарияланған
үшін арнайы

Бастайық

18-деңгейде byte-byte файлды оқудың алғашқы тапсырмалары басталды: файлды оқу, содан кейін минималды/максималды byteтарды табу немесе оны реттелген түрде шығару және т.б.
Файлдармен byte-byte жұмыс – 1
Мұндағы адамдар өте ақылды. Олар жинақтар туралы біледі және сұрыптауға және кірістіруге болады. Жинақтар – қуатты механизм. Көбісі JavaRush-ке дейін оларды мүлдем пайдаланбады. Оларды зерттеп, дұрыс емес жерден соғуға тырысу, әрине, мақтауға тұрарлық. Сонымен. Тапсырмаларда жоқ мәселені алайық (оны шешу кезінде спойлер болмас үшін), бірақ өте ұқсастары бар:
  • Консольден файл атауын енгізіңіз
  • Файлдағы барлық byteты оқыңыз.
  • Қайталауларды елемеу, оларды byte code бойынша кему ретімен сұрыптаңыз.
  • Дисплей
  • Енгізу/шығару ағынын жабу
Енгізу файлының byte мысалы 44 83 44 Шығару мысалы 83 44 Бағдарламаның орындалу уақытын жазу үшін startTimeбіз қосымша айнымалыларды енгіздік . Есептеу үшін i3-3GHz/8Gb RAM/HDD WD Blue-1Tb/Win7-64/jdk-8u73-windows-x64 finishTime қолдандым (1-2 опцияларындағы бағдарламалардың мысалдары info.javarush форумынан алынған, олар аздап өсу ретімен сұрыптау үшін ғана өзгертілген, жарайды - яғни олар REAL!!)

Оны бетпе-бет шешейік:

// Вариант 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 byte алады (Тақырып: 8 byte + Int өрісі: 4 byte + 8 еселік үшін туралау: 4 byte). Барлығы 8 ГБ оперативті жады бар жадқа өз еркімізбен 16 Гб деректерді саламыз. Біз жақсырақ жұмыс істейміз. Жинақтарға тереңірек үңілейік. Ал асық, біз керек нәрсені таптық.

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 minutes. Прогресс анық. Біз нәтижелерді «күте» алдық және 46 МБ файл айтарлықтай жылдам өңделеді. Ілгері жүру. Коллекциялардан бас тартуға тырысайық (бұл кейбіреулер үшін өте ауыр болады). Біз қарапайым массивтерді қолданамыз (бұл өте қарапайым). Бір маңызды нәрсені атап өтейік . Кездескен byteтардың санын 256 ұзындықтағы массивке қоюға болады. Сондықтан оқылатын byteқа сәйкес массив элементін жай ғана көбейтеміз.

Массив – byte бойынша byte

// Вариант 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 minutes. Тағы да жақсарту, бірақ аз. Ал біз бәрін қарапайым құралдармен жасадық. Тырнақтарды соғу үшін микроскопты пайдаланбады . Енді лирикалық шегініс. Компьютердің құрылымын еске түсірейік. ЖЖҚ жады (DRAM) , әдетте бағдарлама орындалады және айнымалылар сақталады, қол жеткізу жылдамдығы жоғары, бірақ өлшемі шағын. Әдетте файлдар сақталатын қатты/флэш-дисктегі (HDD немесе Flash дискілері) жад, керісінше, қол жеткізу жылдамдығы төмен, бірақ өлшемі үлкен. Сонымен, біз 1 Гб файлды byteпен оқығанда (яғни біз HDD-ге миллиард рет қол жеткіземіз) біз төмен жылдамдықты құрылғымен жұмыс істеуге көп уақыт жұмсаймыз (біз КамАЗ жүк көлігінің корпусынан құм түйіршіктерін астықпен тасымалдаймыз) құм жәшігіне). Оны одан әрі жақсартуға тырысайық.

Бірден БҮТ КАМАЗды құмды төгіп алайық!

// Вариант 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 мәні бар byte массиві
arrBytes[fileImage[i] & 0b11111111]++; Сондықтан byteтарды санау үшін біз белгі битін қалпына келтіретін және бізге 0..255 диапазонындағы мәнді қайтаратын құрылысты қолданамыз және осылайша нәтижелер: 46 МБ файл 0,13 секунд (секундтан аз). 1 Гб файл - 9 секунд. Біз бұны жасадық! Біз керемет кереметпіз! 3 сағаттан 9 секундқа дейін жылдамдатылған. Болды, орындыққа отырып, шай ішуге болады. Ал енді тағы бір тәжірибе – 32 Гб файлды қолданып көрейік (мысалы, HD фильмі). Нәтижесінде Windows жүйесінде бағдарлама бұзылып, жұмыс істейтін HDD-ден сықырлаған дыбысты аламыз. КамАЗ денені құммен төгіп, құмсалғышты сындырды! Не істейміз? Тағы бір фактіні еске түсірейік. ОЖ-дағы файлдар әдетте 2-64 Кб (файлдық жүйенің түріне, параметрлерге және т.б. байланысты) бөліктерде (кластерлерде) сақталады. Біз бөліктерде оқимыз, мысалы, 64 000 byte. КамАЗды экскаватормен үлкен бөліктерде түсіріп көрейік:

Буферді пайдалану

// Вариант 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 Гб файл үшін біз өнімділікті бірнеше сағаттан секундтың бөліктеріне дейін жақсартқанымызды ескеріңіз !!! Осы қарапайым фактімен біз экспериментті аяқтаймыз және бастапқы codeты жақсартамыз. Біз көптеген жолдар бойынша прогреске қол жеткіздік - жадты тұтынудың және жұмыс уақытының жаңа көрсеткіштері бізді қуантады. Сондай-ақ, бұл жағдайда біз стандартты кітапханадан пайдасыз жинақтарды шығармаймыз. PS Біреу мысалды тым қиын деп айтады, т.б. Бірақ ұқсас тапсырмалар өте көп - күйлердің шектеулі саны бар элементтердің үлкен көлемін талдау. Мысалы, кескіндер (RGB – әдетте 24 byteта сақталады, біздің жағдайда long[] arrRGB = new long[256*256*256] жадта бар болғаны 64 МБ орын алады), музыка (амплитудасы әдетте 16 немесе 24 битте цифрланады. ) немесе дискретті индикаторлар сенсорлары және т.б.
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION