Хранение данных в бинарном файле
от vl@volk
На повестке дня у нас тема хранение данных в бинарных файлах.
У многих начинающих Java-программистов, и не только Java, возникает такой вопрос: как же нам сохранить данные быстро, легко, да и еще зашифровать? Начинается поиск по сети, возникает куча вопросов, появляется куча костылей с сложным построчным парсингом. Да и еще некоторые мастера начинают прибегать к регулярным выражениям. И что в итоге? Тормоза, непонятные ошибки, проблемы короче. Непонятные ошибки — в основном ошибки логики, забыл что на Windows вместо просто переноса\n комбинация из \n\r, функция разбиения начинает отсчет с нуля, а не единицы, да и на MacOS чего-то другое поведение. Мы от всего этого уйдем и напишем примеры сохранения карты в одном файле, хранение пар Имя=Значение и просто придумаем какой-нибудь формат хранения графики с сжатием.
Внимание!!! Я не буду делать примеров приложений, все чисто на теории и моих прошлых работах, которые мне приходилось делать. И описания методов стандартных классов не будут даны, смотри в документации. Особенно умные еще проявят смекалку и дадут ответы на вопросы)
Начнем с того, что файл — этот набор байт (так Pro100%CoolКодеры, попрошу здесь ваши мега-огурментации оставить при себе). Ну байт, это число которое может быть от нуля и до 256. Да вы знаете что все что вы сейчас видите — это набор байт в файлах, который обработали ваши программы и компьютер! Все байты в файл пишутся подряд, без каких либо разделителей и специальных вставок, просто как ты на доске, в тетраде пишешь цифры, так и в файл пишешь.
Аналогии к хранению данных в файле.
Как мы записываем распорядок дня? Как мы записываем план работы нашей программы/функции? Вы поняли к чему я веду? А если и не поняли, то я напишу. Всегда при сохранении чего либо в файле мы должны записать изначально, что нам надо сделать с этими данными, сколько их, размер. И, это важно, сначала же их надо как-то записать. Далее у нас статья пойдет по ветке описания работы на конкретном примере (по другому никак, понять никак).
Нам надо сохранить массив байт (а может и int или других значений) в файл. Характеристиками нашего массива являются количество элементов и тип. Для хранения типа мы создадим несколько констант, чтоб потом при чтении нам легко было понять что мы храним в файле. Код:
С записью закончили, теперь чтение. Здесь я сделаю очень и очень просто, просто три метода
Вот это примитивненько сделали. Можно вполне пользоваться и соблюдать определенную строгость с порядком чтения и записи — обязательно читаем все типы так как записали иначе будем ловить ошибки. Но это только начало наш дебют в шахматном понимании. Итак, переходим к миттельшпиле, будем улучшать процесс загрузки данных из файла. Например у нас имеются какие-то массивы с данными игрока для многопользовательской игры. Пусть это будет массив байт, не играет большой роли. Число игроков у нас каждый день растет и стает неудобно проходит по базе каждый раз для сохранения разных характеристик игрока. На этот случай мы можем ввести новый тип данных TYPE_PLAYER и с легкостью считывать его из файла способом выше. А что если мы хотим чтобы оно само читало из файла? Тогда сделаем следующим образом:
А теперь подключаем воображение и начинаем рисовать картиночки в головке как можно компактно хранить данные в файлах. И напоследок:
1) в начале в файл можно записывать заголовок, чтобы распознавать "ваш ли это файл";
2) можно прибежать к методам шифрования (криптографии) и защитить ваш файл от вскрытия злоумышленниками;
3) есть множество алгоритмов сжатия данных, используйте их, экономьте место на диске пользователя;
4) файл можно условно разделить на две области: основной заголовок где хранится общая информация о файле и тело файла, где хранятся ваши данные;
5) так же можете добавить в заголовок поле с хешем данных в вашем файле для проверки его целостности;
6) и самое главное опыты и фантазия.
Вопросы
1. Скажите, почему я вправе был сделать так typeNames[currentType] (смотри метка №1)? Да и чего-то там там не хватает (без "не хватает" оно будет работать).
2. Что произойдет, если мы подсунем не наш файл, а скажем песню? (все возможные ошибки которые могут встретится по коду).
3. Почему код на метке №2 не будет работать динамически?
4. Изначально я хотел написать больше примеров, но потом понял, как много мне придется писать вручную — теперь это ваше задание, по возможности могу помочь с отлавливанием ошибок.
Если уж так захочется узнать правильно ли Вы ответили, можете написать мне письмо на сайте с ответом.
У многих начинающих Java-программистов, и не только Java, возникает такой вопрос: как же нам сохранить данные быстро, легко, да и еще зашифровать? Начинается поиск по сети, возникает куча вопросов, появляется куча костылей с сложным построчным парсингом. Да и еще некоторые мастера начинают прибегать к регулярным выражениям. И что в итоге? Тормоза, непонятные ошибки, проблемы короче. Непонятные ошибки — в основном ошибки логики, забыл что на Windows вместо просто переноса\n комбинация из \n\r, функция разбиения начинает отсчет с нуля, а не единицы, да и на MacOS чего-то другое поведение. Мы от всего этого уйдем и напишем примеры сохранения карты в одном файле, хранение пар Имя=Значение и просто придумаем какой-нибудь формат хранения графики с сжатием.
Внимание!!! Я не буду делать примеров приложений, все чисто на теории и моих прошлых работах, которые мне приходилось делать. И описания методов стандартных классов не будут даны, смотри в документации. Особенно умные еще проявят смекалку и дадут ответы на вопросы)
Начнем с того, что файл — этот набор байт (так Pro100%CoolКодеры, попрошу здесь ваши мега-огурментации оставить при себе). Ну байт, это число которое может быть от нуля и до 256. Да вы знаете что все что вы сейчас видите — это набор байт в файлах, который обработали ваши программы и компьютер! Все байты в файл пишутся подряд, без каких либо разделителей и специальных вставок, просто как ты на доске, в тетраде пишешь цифры, так и в файл пишешь.
Аналогии к хранению данных в файле.
Как мы записываем распорядок дня? Как мы записываем план работы нашей программы/функции? Вы поняли к чему я веду? А если и не поняли, то я напишу. Всегда при сохранении чего либо в файле мы должны записать изначально, что нам надо сделать с этими данными, сколько их, размер. И, это важно, сначала же их надо как-то записать. Далее у нас статья пойдет по ветке описания работы на конкретном примере (по другому никак, понять никак).
Нам надо сохранить массив байт (а может и int или других значений) в файл. Характеристиками нашего массива являются количество элементов и тип. Для хранения типа мы создадим несколько констант, чтоб потом при чтении нам легко было понять что мы храним в файле. Код:
- public static final byte
- TYPE_BYTE = 0,
- TYPE_INT = 1,
- TYPE_STRING = 2;
- public void writeByteData(byte[] data, OutputStream os) throws IOException {
- // чтоб понятно было
- int dataLength = data.length;
- byte currentType = TYPE_BYTE;
- DataOutputStream dos = new DataOutputStream(os); // нужен чтоб писать int, String и другие типы
- // а вот собственно запись, понятно?)
- dos.write(currentType);
- dos.writeInt(dataLength);
- dos.write(data, 0, dataLength);
- }
- //Далее все по аналогии, только запись будет чуть другими функциями записываться
- public void writeIntData(int[] data, OutputStream os) throws IOException {
- int dataLength = data.length;
- byte currentType = TYPE_INT;
- DataOutputStream dos = new DataOutputStream(os);
- dos.write(currentType);
- dos.writeInt(dataLength);
- for (int i = 0; i < dataLength; i++) {
- dos.writeInt(data[i]);
- }
- }
- public void writeStringData(String[] data, OutputStream os) throws IOException {
- int dataLength = data.length;
- byte currentType = TYPE_STRING;
- DataOutputStream dos = new DataOutputStream(os);
- dos.write(currentType);
- dos.writeInt(dataLength);
- for (int i = 0; i < dataLength; i++) {
- dos.writeUTF(data[i]);
- }
- }
С записью закончили, теперь чтение. Здесь я сделаю очень и очень просто, просто три метода
- // добавим массив строк с названиями типов. Для облегчения улавливания наших ошибок
- // метка №1 к вопросу
- String[] typeNames = new String[] {"Байт", "Целое", "Строка" };
- public byte[] readByteData(InputStream is) throws IOException, Exception{
- DataInputStream dis = new DataInputStream(dis);
- byte currentType = dis.read();
- if (curreanType != TYPE_BYTE) {
- throw new Exception("Ошибочка! Мы читаем не тот тип! В файле: " + typeNames[currentType]);
- }
- int dataLength = dis.readInt();
- byte[] data = new Byte[dataLength];
- if (dis.readFully(data) != dataLength) { // надо проверить, чтоб все было хорошо
- throw new Excetion("Ошибочка! Что-то пошло не так...");
- }
- return data;
- }
- public int[] readIntData(InputStream is) throws IOException, Exception{
- DataInputStream dis = new DataInputStream(dis);
- byte currentType = dis.read();
- if (curreanType != TYPE_INT) {
- throw new Exception("Ошибочка! Мы читаем не тот тип! В файле: " + typeNames[currentType]);
- }
- int dataLength = dis.readInt();
- int[] data = new int[dataLength];
- int i = 0;
- for (; i < dataLength; i++) {
- data[i] = dis.readInt();
- }
- if (i != dataLength) { // надо проверить, чтоб все было хорошо
- throw new Excetion("Ошибочка! Что-то пошло не так...");
- }
- return data;
- }
- public String[] readStringData(InputStream is) throws IOException, Exception{
- DataInputStream dis = new DataInputStream(dis);
- byte currentType = dis.read();
- if (curreanType != TYPE_STRING) {
- throw new Exception("Ошибочка! Мы читаем не тот тип! В файле: " + typeNames[currentType]);
- }
- int dataLength = dis.readInt();
- String[] data = new String[dataLength];
- int i = 0;
- for (; i < dataLength; i++) {
- data[i] = dis.readUTF();
- }
- if (i != dataLength) { // надо проверить, чтоб все было хорошо
- throw new Excetion("Ошибочка! Что-то пошло не так...");
- }
- return data;
- }
Вот это примитивненько сделали. Можно вполне пользоваться и соблюдать определенную строгость с порядком чтения и записи — обязательно читаем все типы так как записали иначе будем ловить ошибки. Но это только начало наш дебют в шахматном понимании. Итак, переходим к миттельшпиле, будем улучшать процесс загрузки данных из файла. Например у нас имеются какие-то массивы с данными игрока для многопользовательской игры. Пусть это будет массив байт, не играет большой роли. Число игроков у нас каждый день растет и стает неудобно проходит по базе каждый раз для сохранения разных характеристик игрока. На этот случай мы можем ввести новый тип данных TYPE_PLAYER и с легкостью считывать его из файла способом выше. А что если мы хотим чтобы оно само читало из файла? Тогда сделаем следующим образом:
- public void loadFile(InputStream is) {
- DataInputStream dis = new DataInputStream(is);
- int type = (int)dis.readByte(); // загружаем тип
- // метка №2
- switch(type) {
- case TYPE_PLAYER:
- byte[] data = new byte[dis.readInt()];
- dis.readFully(data); // загружаем массив игрока
- players.add(new Player(data)); // а это для примера, players это вектор или хеш-таблица, Player класс игрока
- break;
- case TYPE_WORLD_CHUNK: // а это тип куска карты. Согласитесь, если у нас мир создается во время игры и растет во все стороны, то лучше бы было его загружать циклом
- byte[] data = new byte[dis.readInt()];
- dis.readFully(data);
- worldChunks.add(new WorldChunk(data));
- break;
- }
- }
А теперь подключаем воображение и начинаем рисовать картиночки в головке как можно компактно хранить данные в файлах. И напоследок:
1) в начале в файл можно записывать заголовок, чтобы распознавать "ваш ли это файл";
2) можно прибежать к методам шифрования (криптографии) и защитить ваш файл от вскрытия злоумышленниками;
3) есть множество алгоритмов сжатия данных, используйте их, экономьте место на диске пользователя;
4) файл можно условно разделить на две области: основной заголовок где хранится общая информация о файле и тело файла, где хранятся ваши данные;
5) так же можете добавить в заголовок поле с хешем данных в вашем файле для проверки его целостности;
6) и самое главное опыты и фантазия.
Вопросы
1. Скажите, почему я вправе был сделать так typeNames[currentType] (смотри метка №1)? Да и чего-то там там не хватает (без "не хватает" оно будет работать).
2. Что произойдет, если мы подсунем не наш файл, а скажем песню? (все возможные ошибки которые могут встретится по коду).
3. Почему код на метке №2 не будет работать динамически?
4. Изначально я хотел написать больше примеров, но потом понял, как много мне придется писать вручную — теперь это ваше задание, по возможности могу помочь с отлавливанием ошибок.
Если уж так захочется узнать правильно ли Вы ответили, можете написать мне письмо на сайте с ответом.