• ,

Побайтовая работа с файлами

Special for Spiker

Начнем'c.
В 18 уровне начались первые задачи побайтного чтения файлов:
Прочитать файл, далее найти минимальные/максимальные байты или вывести в упорядоченном виде и т.п.

Народ тут весьма ушлый. Знают про коллекции и про то, что они могут сортировать, вставлять.
Коллекции — мощный механизм. И многие не применяли их вообще до 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 секунд.
Попытка скормить с вечера 1Gb файл (размер MPEG4 фильма не в самом лучшем качестве) не увенчалась успехом.
Программа утром все еще читала — а мне идти на работу уже.

В чем проблема? Наверное в использовании
ArrayList<Integer>
у которого внутри 1 миллиард элементов. Каждый элемент его занимает 16 байт минимум (Заголовок: 8 байт + Поле int: 4 байта + Выравнивание для кратности 8: 4 байта). Итого мы добровольно загоняем в память 16 Gb данных при размере оперативы в 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 секунд.
1Gb файл — 3 часа 5 минут.
Прогресс на лицо. Мы смогли «дождаться» результатов, да и 46Мб файл заметно быстрее обрабатывается.

Идем дальше. Давайте попытаемся отказаться от коллекций (это будет для некоторых мучительно больно). Будем использовать простые массивы (это так примитивно).
Заметим одну важную вещь. Кол-во встречающихся байт можно загнать в массив длиной 256.
Так просто будем увеличивать на единицу соответствующий считанному байту элемент массива.

Массив — побайтно

// Вариант 3. Считываем массив побайтно.
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();
        // Выводим отсортированный по байт-коду в обратном порядке
        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 секунд.
1Gb файл — 2 часа 55 минут.
Опять улучшение, но небольшое. И мы сделали все простыми инструментами. Не использовали микроскоп для забивания гвоздей.

Теперь лирическое отступление.
Вспомним устройство компьютера.
Память ОЗУ (DRAM) где обычно выполняется программа и хранятся переменные имеет высокую скорость доступа, но небольшой размер.
Память на жестком/flash диске (HDD или Flash-накопители) где обычно хранятся файлы, наоборот имеет низкую скорость доступа, но большой размер.
Так что когда мы побайтно читаем 1Gb файл (то есть миллиард раз обращаемся к 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 <fileSize ; i++) arrBytes[fileImage[i] & 0b11111111]++;

		inputStream.close();
        // Выводим отсортированный по байт-коду в обратном порядке
        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.");
    }
}

небольшое, но опять таки важное отступление
Заметим:
1. индекс у arrBytes определен в пределах 0..255,
2. fileImage — массив байт, элементы которого имеют значение -128..127
Поэтому для подсчета байт будем использовать конструкцию
arrBytes[fileImage[i] & 0b11111111]++;

которая банально сбросит бит знака и вернет нам значение в диапазоне 0..255

И так, результаты:
46Мб файл 0.13 секунд (меньше секунды).
1Gb файл — 9 секунд.
Мы сделали это! Мы невероятно круты! Ускорились с 3 часов до 9 секунд.

Все, можно откинуться в кресле и попить чайку. А теперь еще один эксперимент —
попробуем файл в 32 Gb (например, HD фильм). Получим в результате треск
работающего HDD с вываливанием программы в 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 <bufferSize ; i++) arrBytes[ buffer[i] & 0b11111111 ]++;
        }

		inputStream.close();
        // Выводим отсортированный по байт-коду в обратном порядке
        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Мб файл 0.08 секунд (меньше секунды).
1Gb файл — 0.9 секунд(меньше секунды).
32Gb файл — 31 секунда.

Заметим для 1 Gb файла мы улучшили производительность с нескольких часов до долей секунд!!!

На этом скромном факте закончим эксперимент и улучшение начального кода. Мы достигли прогресса во многом — нас радуют новые показатели расхода памяти и времени работы. Также мы не подтягиваем в данном случае бесполезные коллекции из стандартной библиотеки.

P.S. Кто-то скажет пример надуманный и т.п. Но полно похожих задач — проанализировать огромный объем элементов, имеющих конечное число состояний.
Например изображения (RGB — обычно хранятся в 24 байтах, в нашем случае long[] arrRGB = new long[256*256*256] занял бы в памяти всего 64Мб),
музыка (амплитуда обычно оцифровывается в 16 или 24 бита) или дискретные показатели датчиков и т.п.

23 комментария

AndreGold
Хорошая статья, почитать было приятно. А скажите вы не пробывали тоже самое, но например для ArrayList в конструкторе задавать параметр capacity, начальный размер его внетреннего массива?
Например тут
ArrayList<Integer> listData = new ArrayList<Integer>(inputStream.available());


А для TreeSet, также capacity, размер(количество корзин) и loadFactor?

И еще не пробывали ли Вы использовать LinkedList, дело в том, что при вставке в ArrayList, когда в нем не остается места он расширяется, т.е. выделяется дополнительная память, создается новый массив вдвое большего размера, и все элементы со старого массива копируются в новый массив, что с LinkedList не проиходит, просто присваивается предпоследнему элементу массива(до вставки последнему) ссылка на последний вставленный элемент.
Joysi
  • Joysi
  • 0
  • Комментарий отредактирован 2016-02-19 11:43:37 пользователем Joysi
Мусье AndreGold знает толк в тонких удовольствиях :)
// Вариант 6. Загоняем в LinkedList и сортируем используя ее метод 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();
        
        LinkedList<Integer> listData = new LinkedList<Integer>();
        while (inputStream.available() > 0) listData.add(inputStream.read());
        inputStream.close();
        LinkedList<Integer> result = new LinkedList<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Мб файле программа умирает на строчке
while (inputStream.available() > 0) listData.add(inputStream.read());


через ~десять минут, так скушала всю доступную память
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.LinkedList.linkLast(LinkedList.java:140)
	at java.util.LinkedList.add(LinkedList.java:336)


Заметим, что ArrayList отрабатывает все ~ за 3.5 минуты.
По поводу Set-ов в данное случае, по моему неактуально, так как конечное число элементов = 256.
В этом случае множество (причем в данном случае неважно, будет TreeSet или HashSet) строится буквально на первых значениях считанных байт из файла. Далее просто отбрасывает очередные значения.

Вариант
LinkedList<Integer> result = new LinkedList<Integer>(new HashSet<Integer>(listData));

быстрее
LinkedList<Integer> result = new LinkedList<Integer>(new TreeSet<Integer>(listData));

в среднем на 6 секунд (запускал раз 5 для статистики) при общем времени работы программы 3.5 минуты
Вот если строить дерево на словах (65536 комбинаций) или цветах (RGB — 16млн комбинаций) — тут да… ТУт надо пробовать.

Даже вариант

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();
        
        
        TreeSet<Integer> result = new TreeSet<Integer>();
        while (inputStream.available() > 0) result.add(inputStream.read());
        while (!result.isEmpty()) {
            System.out.print(result.last() + " ");
            result.remove(result.last());
        }

        long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}

работает в пределах тех же 3.5 минут.

Попозже попробую вариант комбинирования — чтения в промежуточный буфер, добавление в коллекцию из буфера. И поиграюсь уже не с байтами, а словами (16 битовыми значениями) — Тогда роль коллекций уже поимеет смысл, а с байтами — увы, не имеет.
Spiker
  • Spiker
  • 0
  • Комментарий отредактирован 2016-02-19 19:38:35 пользователем Spiker
arrBytes[ buffer[i] & 0b11111111 ]++
одна и это работа с байтами я где-то читал, а 0b11111111 это что-то в какой-то системе?
if (inputStream.available() < 64000) bufferSize = inputStream.available();
inputStream.read(buffer, 0, bufferSize );
for (int i = 0; i <bufferSize; i++) arrBytes[ buffer[i] & 0b11111111 ]++;
тут ковшом зачерпнули, а надо же ещё ковши брать да? просто пример такой?
И вообще глобально, с байтами нужно так работать? Их во что-то преобразовать можно? Или это просто пример, а нужны как вы писали для анализа изображений, музыки?

И спасибо за разъяснения ещё раз.
Joysi
0b11111111 — запись числа в двоичном виде. То же число можно записать как:
255 (в привычном десятичном беззнаковом, или — 128 в форме со знаком),
как 0xFF в шестнадцатеричном.
В двоичном небольшие числа иногда удобно писать, так как видно какие биты (в данном случае все 8) участвуют в логической операции «И». Можно записать и в другом виде, но так, по моему нагляднее и удобнее (это субъективно все).

Откуда тут вообще возникли биты-байты. Основная причина в том, что в Java отсутствуют привычные беззнаковые целые типы данных. А считанный байт — он и есть беззнаковый и его удобно представлять как число от 0 до 255 (а не как от -128 до 127). Отрицательные числа причем образуются не просто как смещение на константную величину — en.wikipedia.org/wiki/Two%27s_complement (есть и на русском, но URL выглядит кошмарно).

С байтами можно работать по разному — например, рассматривать перевести и рассматривать далее как int. Но тогда они будут занимать в 4 раза больше места в памяти.
В примере inputStream.read() требует массив байт как параметр. И мне было жалко процессорного времени на дополнительное преобразование и место в памяти.

Музыка вообще понятие обширное. Оцифрованная музыка в MIDI формате например использует и знаковое и беззнаковое байтовое представление.

P.S. Но по любому, битовую арифметику надо знать уверенно. Например, IP-адресация (сети, подсети, маски и т.п.) построена на ней.
Spiker
подписка закончиться, начну читать про байты, а то я в этой теме ноль (

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

Вот этот кусок. Мы создаем массив размером с байт. А потом делаем? И почему в размере байта?
почему выводим в консоль просто i? Не понятен пока смысл всего этого. Как отсортировать в обычном порядке?
Kirill_Sh
Чтобы сказать спасибо за эти уточнения, спкециально зарегистрировался на сайте info.javarush.ru
Я немного переделал свой код под свою задачу через чтение из буфера без использования сортировки, посмотрел время выполнения и это просто улет, спасибо!
Joysi
Если вопрос:
Как БЫСТРО отсортировать массив байт a[i] без операций сравнений?
вот допустим такой
и просто вывести его в консоль? То есть:
100 30 20 30 50 60 30 6 2 — в -> 2 6 20 30 30 30 50 60 100
// Объявляем массив + он инициализировано нулями.
long[] arrBytes = new long[256]; 
// Сортируем за один проход в цикле без операции сравнения
for (int i = 0; i < arrBytes.length; i++) arrBytes[a[i]]++;
// Выводим на экран (можем вместо вывода и отсортированный массив в переменную загнать)
for (int i = 0; i < 255 ; i--)
    for (long j = 0; j < arrBytes[i]; j++)
        System.out.println(i + " ");


А секрет — именно в конструкции arrBytes[a[i]].
По шагам:
1. Объявили дополнительный массив arrBytes[] в котором ровно 256 элементов — то есть все ВОЗМОЖНЫЕ значения, которые могут быть в байте. Значение элемента массива — это сколько раз он у нас встречается в основном массиве a[]. Поэтому мы объявили его как long — потому что каждый элемент массива a[] может встретиться много раз (для файла, например).
2. У этого массива сразу все 256 элементов равны нулю. То что нужно, пока мы не начали обрабатывать исходный массив a[] — мы выставим, что пока ни один элемент-байт не встретили.
3. Дальше поэлементно смотрим массив a[i], допустим в нем a[i]=30. Значит 30 встречается еще один раз.
arrBytes[a[i]]++
— мы и увеличиваем на единицу сколько раз встретили значение 30, то есть
arrBytes[30]=arrBytes[30]+1
. Таким образом за один просмотр в цикле мы «прощупали» все содержимое массива.
4. Чтобы вывести его по возрастанию достаточно подряд с 0 до 255 повторить сколько раз мы нашли очередной по порядку элемент.

Аналогично можно ОЧЕНЬ быстро отсортировать массивы огромной длины, но имеющие конечное множество элементов (например тип short или даже int). QuickSort() или метод Шелла в разы медленнее.
EvIv
по постановке задачи очень близко к Radix Sort — там тоже большие объемы данных, представляющих собой ограниченный набор значений.
Spiker
Меня это заинтересовало, то есть мы можем загнать несколько jpeg файлов в один байт файл и потом из него прочитать и получить изображение? Как это делается с изображениями? Я сейчас гуглю, много нового)
Pavel255
Интересная статья. Но мне сразу пришло в голову, что будет, если все-же использовать коллекции (тот же TreeSet), но читать не побайтно, а через буфер. Примерно так сдавал аналогичную задачу, только там финальная сортировка по возрастанию. Код вида:
public class Test3 {
    public static void main(String[] args) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        InputStream fileIS = new FileInputStream(reader.readLine());
        long startTime = System.currentTimeMillis();

        byte buffer[] = new byte[64000];
        Set<Byte> set = new TreeSet<>();

        while (fileIS.available() > 0) //пока есть еще непрочитанные байты
        {
            // прочитать очередной блок байт в переменную buffer и реальное количество в count
            int count = fileIS.read(buffer);
            for (int i = 0; i <count ; i++)    set.add(buffer[i]);
        }

        for (Byte z : set)  System.out.print(z + " ");

        reader.close();
        fileIS.close();

        long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}


Файл 45 Mб. Пример5 130 мс. Мой вариант 3113 мс.
Действительно, элегантное решение Joysi в 24 раза производительнее. Но все таки, справедливости ради надо заметить, что получилось не 176 сек, как в Пример2 с TreeSet, а 3 сек, т.е. быстрее в 58 раз! Из чего вывод, что к такому гигантскому замедлению привело не столько даже использование коллекций и их встроенных механизмов сортировки, а отсутствие буферизации, всю крутизну которой можно оценить наглядно. Перетаскивать песок из нашего камаза чайной ложкой — то еще удовольствие)
Joysi
Да, справедливо. Спасибо, что добавили для полноты вариантов.
Помимо прочего, при реализации даже очевидных задач всегда следует проводить возможную оптимизацию в нескольких плоскостях( в данном случае 2х: какую структуру использовать для хранения результат и как считывать файл с диска). Если взять задачу вывода в порядке убывания по цвет-коду из огромныx BMP/PNG файлов то сортировка 256*256*256 возможных элементов при вставке в TreeSet будет работать по времени по другому (он же будет внутри себя перестраивать дерево сортировки своих элементов). Надо экспериментировать.

Плюс пример с массивом привел, чтобы сломать некую шаблонность у некоторых при работе с массивами, когда его индекс последовательно перебирают либо в цикле, либо явно указывают константно:
a[1] = ... или for(i = 0; i < size; i++) {a[i] = ...

А конструкция использования в качестве индекса вычисляемого/считываемого выражения, например вида:
while (inputStream.available() > 0) arrBytes[inputStream.read()]++;

вызывают сначала недоумение.
Pavel255
Да, это любопытный подход — представить, что все возможные значения байта закодированы индексами в массиве из 256 элементов и увеличивать значение элемента с индексом, соответствующим считанному байту. Я так тоже не делал. Надо будет взять на заметку.
Kostyann
Получается, что в Варианте 3 строчка «arrBytes[inputStream.read()]++» автоматически преобразует считываемые значения из диапазона -128...127 в диапазон 0… 255?

Почему тогда такого не происходит в Варианте 4 и необходимо использовать побитовое И?
Joysi
Вариант3, мы считываем непосредственно сразу в массив long:
long[] arrBytes = new long[256]; а
...
while (inputStream.available() > 0) arrBytes[inputStream.read()]++;

при этом, собственно, inputStream.read() возвращает int (то есть внутри себя уже преобразовал байт в целое, учитывая возможный отрицательный байт).
А в варианте4, мы используем промежуточный байтовый буфер, который byte fileImage[] и который надо преобразовать
byte fileImage[]=new byte[inputStream.available()];
... 
for (int i = 0; i <fileSize ; i++) arrBytes[fileImage[i] & 0b11111111]++;


Если мы будем уберем побитовое (arrBytes[fileImage[i]]++), то получим отрицательный индекс (при считывание байта в битовом диапазоне 1000 0000 — 1111 1111 ), что приведет нас к java.lang.ArrayIndexOutOfBoundsException. Можете сами проэкспериментировать.
Kostyann
«inputStream.read() возвращает int (то есть внутри себя уже преобразовал байт в целое, учитывая возможный отрицательный байт).» — вот это я и хотел узнать, Спасибо))
Archie369
Joysi, если вы еще здесь, подскажите, как вывести значение размера файла, расположенного на диске, который «весит» 13гб? У меня большое 1гб программа не показывает, подоздеваю что дело в ограничении типа int, в который записывается количество считаных байт. Вот программа:
public class Solution {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        String filename = scanner.nextLine();

        FileInputStream fileInputStream = new FileInputStream(filename);
        int size = fileInputStream.available();
        System.out.println(size + "bytes");
        System.out.println(size/1024 + "Kbytes");
        System.out.println(size/1024/1024 + "Mbytes");
        System.out.println(size/1024/1024/1024 + "Gbytes");

    }
}

вместо int если использовать long, тоже не получается нужный мне результат.
Archie369
  • Archie369
  • 0
  • Комментарий отредактирован 2017-01-24 18:58:15 пользователем Archie369
Изменил программу вот так, в итоге 5мб файл читается 40сек, 13гб даже скармливать программе страшно :-) Почему так медленно??
public class Solution {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        String filename = scanner.nextLine();

        FileInputStream fileInputStream = new FileInputStream(filename);
        long filesize = 0;
        while (fileInputStream.available() > 0)
        {
            int data = fileInputStream.read();
           filesize++;

        }
        System.out.println(filesize + "bytes");
        System.out.println(filesize / 1024 + "Kbytes");
        System.out.println(filesize / 1024 / 1024 + "Mbytes");
        System.out.println(filesize / 1024 / 1024 / 1024 + "Gbytes");
    }
}
Joysi
  • Joysi
  • 0
  • Комментарий отредактирован 2017-01-24 21:39:54 пользователем Joysi
потому что побайтовое чтение
Кликните, зажав ctrl на read();
<code>/**
     * Reads a byte of data from this input stream. This method blocks
     * if no input is yet available.
     *
     * @return     the next byte of data, or ...
 * file is reached.
 * @exception IOException if an I/O error occurs.
 */
 public int read() throws IOException { </code>
Joysi
В вашем случае для этих целей лучше использовать более к месту .getChannel().size() имеющий тип long.
.available() имеет корни из InputStream (возможно и более древние), отсюда и «наследие» в виде int.


        public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        String filename = scanner.nextLine();

        FileInputStream fileInputStream = new FileInputStream(filename);
        long size = fileInputStream.getChannel().size();
        System.out.println(size + "bytes");
        System.out.println((size >>> 10) + " Kbytes");
        System.out.println((size >>> 20) + " Mbytes");
        System.out.println((size >>> 30) + " Gbytes");
    }
Joysi
  • Joysi
  • 0
  • Комментарий отредактирован 2017-01-24 22:11:22 пользователем Joysi
Вывод.
C:\Java\JDK8u77\bin\java ...
d:\1.mkv
4687819017bytes
4577948 Kbytes
4470 Mbytes
4 Gbytes

Process finished with exit code 0

Важный момент — чтобы узнать размер файла не обязательно его считывать, так как эта информация отдельно хранится в файловой системе.

P.S. >>> в данном случае аналогично делению (так как делитель — степень двойки)
Archie369
Спасибо!!! Кстати, про >>> не знал, как называется этот оператор? (в гугле не могу вписать, видимо такой символ поисковик не принимает)
Joysi
Двоичная логика будет тут дальше по курсу.
Для информации можно глянуть developer.alexanderklimov.ru/android/java/bitwise.php и т.п. ресурсы
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.