JavaRush /Java блогу /Random-KY /Файлдар менен байт-байт иштөө
Joysi
Деңгээл

Файлдар менен байт-байт иштөө

Группада жарыяланган
үчүн атайын

Баштайлы

18-деңгээлде файлды byte-byte окуунун биринчи тапшырмалары башталды: файлды окуу, андан кийин минималдуу/максималдуу byteтарды табуу же аны иреттелген формада чыгаруу ж.б.
Файлдар менен byte-byte иштөө - 1
Бул жердегилер абдан акылдуу. Алар коллекциялар жөнүндө бorшет жана алар сорттоп, кыстара аларын бorшет. Коллекциялар — бул кубаттуу механизм. Жана JavaRush алдында көбү аларды таптакыр колдонушкан эмес. Аларды изилдеп, туура эмес жерлерге чапканга аракет кылуу, албетте, мактоого татырлык. Ошентип. Келгиле, тапшырмаларда жок маселени алалы (аны чечүүдө эч кандай бузукулук болбошу үчүн), бирок абдан окшош нерселер бар:
  • Консолдон файлдын атын киргизиңиз
  • Файлдан бардык byteтарды окуу.
  • Кайталанууларды этибарга албай, аларды byte code боюнча кемүү тартибинде иреттеңиз.
  • Дисплей
  • I/O агымын жабуу
Киргизүү файлынын byteтары 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 форумунан алынган, алар бир аз Өсүү тартибинде сорттоо үчүн гана өзгөртүлгөн, макул - башкача айтканда, алар 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 тасмасынын көлөмү эң жакшы сапатта эмес) ийгorксиз болду. Мен дагы эле эртең менен программаны окуп жаттым - жана мен буга чейин эле жумушка чыгышым керек болчу. Көйгөй эмнеде? ArrayList<Integer>Кыязы , ичинде 1 миллиард элементи бар колдонулууда . Анын ар бир элементи эң аз 16 byteты ээлейт (Такта: 8 byte + Талаа int: 4 byte + 8 көптүк үчүн тегиздөө: 4 byte). Бардыгы болуп, биз өз ыктыярыбыз менен эстутумга 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.");
    }
}
чыгаруу болуп саналат: 46MB файл, 176 секунд. 1 Гб файл - 3 саат 5 мүнөт. Прогресс айкын көрүнүп турат. Биз жыйынтыктарды "күтө" алдык жана 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.");
    }
}
чыгаруу болуп саналат: 46MB файл, 158 секунд. 1Гб файл - 2 саат 55 мүнөт. Дагы бир жакшыртуу, бирок кичинекей. Ал эми биз баарын жөнөкөй шаймандар менен жасадык. Тырмактарды кагуу үчүн микроскоп колдонгон эмес . Эми лирикалык чегинүү. Компьютердин түзүлүшүн эстеп көрөлү. RAM эс тутуму (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 - byte массивинин элементтери -128..127 маанисине ээ
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.");
    }
}
Натыйжада, биз алды: 46MB файл 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