Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Чары файлов


Автор: Platon 25.4.2006, 20:35
Я тут пытался реализовать метод сжатия LZ78 на моем любимом Java. Вроде все понятно, но проблемы, кажется, с сохранением в файл. Наверное, проблемы с переводом из класса Record в строку и обратно. Напомню людям, что каждая запись содержит номер записи, с которой начинается данная и символ, который к ней дописывается. Запись состоит из одного чара и 1-го int'а. Как понимаете, int в файл может записываться с разной длиной, а мне надо с фиксированой. Я выбрал 2 байта. Вот код:

Код

import java.io.*;
import java.util.*;

public class LZ78 {

    void packToFile(String po, Vector<Record> values) throws IOException {
        BufferedWriter out = new BufferedWriter(new FileWriter(po));
        for (Record i : values)
            out.write(i.toString());
        out.close();
    }

    void unpackToFile(String po, Vector<String> values) throws IOException {
        BufferedWriter out = new BufferedWriter(new FileWriter(po));
        for (String i : values)
            out.write(i);
        out.close();
    }

    public void encode(String pi, String po) throws IOException {
        // Здесь код закодирования. Он правильный
        // values - Vector<Record>
        // po - выходной файл
        packToFile(po, values);
    }

    public void decode(String pi, String po) throws IOException {
        // Здесь код разкодирования. Он правильный
        unpackToFile(po, dictionary);
    }
    public static void main(String[] args) throws IOException {
        LZ78 temp = new LZ78();
        temp.encode("in.txt", "arch.txt");
        temp.decode("arch.txt", "out.txt");
    }
}

class Record {
    char s;
    int pos;
    public Record(char s, int pos) {
        this.s = s;
        this.pos = pos;
    }
    public char[] toCharArray() {
        return new char[]{s, (char)(pos >> 8), (char)(pos & 0xFF)};
    }
    public static Record toRecord(String str) {
        return toRecord(str.toCharArray());
    }

    public String toString() {
        return String.valueOf(toCharArray());
    }

    public static Record toRecord(char[] str) {
        return new Record(str[0], ((int)str[1] << 8) | (int)str[2]);
    }
}


Код работает для ультра-коротких файлов, и дает сбои со сравнительно средними файлами. Вероятно, это происходит из-за увеличения размера словаря. Если не сохранять в файл и раскодировать сразу из values, то все прекрасно для всех файлов умеренных размеров, а вот раскодировать архив уже не получается.  

Автор: LSD 25.4.2006, 22:20
С LZ78 я незнаком, поэтому пока объясни поподробней, что именно там происходит.

Дело в том, что char это UTF-16, он хранит 65536 различных символов. А запись в файл идет в системной кодировке, как правило это однобайтовые кодировки, т.е. 256 символов. И подобное преобразование конечно не может пройти без потерь. Так что сам принцип сохранения не верен. 

Автор: jimur 25.4.2006, 22:59
А out.flush() не пробовал ставить? 

Автор: LSD 25.4.2006, 23:02
Цитата(jimur @  25.4.2006,  23:59 Найти цитируемый пост)
А out.flush() не пробовал ставить? 

close() - выполняет flush(). 

Автор: jimur 25.4.2006, 23:07
Цитата(LSD @  25.4.2006,  23:02 Найти цитируемый пост)
close() - выполняет flush(). 


Цитата(java.io.BufferedWriter.flushBuffer())

/**
     * Flush the output buffer to the underlying character stream, without
     * flushing the stream itself.  This method is non-private only so that it
     * may be invoked by PrintStream.
     */

 

Автор: LSD 25.4.2006, 23:16
Не понял. Ты предлагаешь вызвать именно flushBuffer() вместо flush()?

И чем это поможет, если перед закрытием буффер все равно очищается? 

Автор: jimur 25.4.2006, 23:35
Предлагаю попробовать вызывать
вместо
        out.close();

вызывать
        out.flush();
        out.close();

выдержка из исходников в сочетании с приведенными выше фрагментами объясняет почему
Код

    /**
     * Flush the stream.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public void flush() throws IOException {
    synchronized (lock) {
        flushBuffer();
        out.flush();
    }
    }

    /**
     * Close the stream.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public void close() throws IOException {
    synchronized (lock) {
        if (out == null)
        return;
        flushBuffer();
        out.close();
        out = null;
        cb = null;
    }
    }
 

Автор: Platon 26.4.2006, 06:07
Ээээ, люди со flush() и close() все отлично, дело в другом.


LSD, значит с байтами. А я чего-то не пойму, ведь смотрю в архивный файл и вижу все нормально: 1 байт под символ, 2 байта под int. И как-бы чары и без этого жмутся в 1 байт. А с байтами очень уж неудобно работать.

Ну, это не очень важно, но если требуешь попробую объяснить:
В общем начинаем. Фраза, записываемая в файл, будет состоять из 3-х байт: 1 под символ, 2 под число.
В методе есть строчный вектор словарь и фразный вектор значения.
Каждый цикл считываем по 1-му чару. И прибавляем его к исследуемой строке curString. Ищем эту строку в словаре, если она есть, то запоминаем номер ее позиции, если нет, надо занести в словарь и добавить новую запись с последним прочитанным символом и позицией последней совпавшей строки. Фу... вроде ничего не упустил, правда пока не проверял, но хвост может оставаться, я там такую пришпандорку сделал.

Прошу прощения за бестактный вопрос, но как байты то в BufferedWriter записывать?  

Автор: jimur 26.4.2006, 06:22
Цитата(Platon @  26.4.2006,  06:07 Найти цитируемый пост)
Прошу прощения за бестактный вопрос, но как байты то в BufferedWriter записывать? 

дак он для этого не предназначен - он для чаров. 

Автор: Platon 26.4.2006, 08:16
Так. Я выяснил в чем ошибка. Проблема в том, что выражение return new Record(str[0], ((int)str[1] << 8) | str[2]); работает некорректно.

В случае когда int равен 128 оно естественно представляется в виде 0x0080 в двухбайтной системе. Хе а когда обратно перевожу у меня два байта один равен нулю, а второй -128, второй приводится к int, причем знак сохраняется!!! И получается все равно -128 вместо 128. Козалось бы чего сложного. В С++ это решается просто, а в Java как? 

Автор: Platon 26.4.2006, 20:27
Тупейшим образом решил свою проблему:
Код

    public static Record toRecord(byte[] str) {
        return new Record((char)str[0], ((int)str[1] << 8) | (str[2] & 0x7F) | ((((byte)str[2] & 0x80) == 0)? 0:0x0080));

    }


Как проще-то? 

Автор: LSD 26.4.2006, 22:13
Цитата(jimur @  26.4.2006,  00:35 Найти цитируемый пост)
выдержка из исходников в сочетании с приведенными выше фрагментами объясняет почему

Вовсе не объясняет (а исходники я и так видел). close() вызывает очистку данного буфера, и закрытие нижележащего потока, который в свою очередь сам очистит свой буфер перед закрытием.


Цитата(Platon @  26.4.2006,  07:07 Найти цитируемый пост)
А я чего-то не пойму, ведь смотрю в архивный файл и вижу все нормально: 1 байт под символ, 2 байта под int. И как-бы чары и без этого жмутся в 1 байт. А с байтами очень уж неудобно работать.

В Java char - это 2 байта, а int - 4 байта. Зачем тебе вообще нужны char, почему ты не работаешь с byte, что в них такого сложного? 

Автор: jimur 27.4.2006, 06:53
Цитата(LSD @  26.4.2006,  22:13 Найти цитируемый пост)
Вовсе не объясняет (а исходники я и так видел). close() вызывает очистку данного буфера, и закрытие нижележащего потока, который в свою очередь сам очистит свой буфер перед закрытием.

Да, согласен, при работе с FileWriter'ом достаточно close(). 

Автор: Platon 27.4.2006, 08:14
LSD, да согласен уже перешел. Но загвоздка в переводе 2-х байтов в int. Я решил ее одним методом, но уж больно кривенький он. Неужели в Java нет unsigned byte?

Код

public static Record toRecord(byte[] str) {
    return new Record((char)str[0], ((int)str[1] << 8) | (str[2] & 0x7F) | ((((byte)str[2] & 0x80) == 0)? 0:0x0080));


Добавлено @ 08:15 
И еще. Можно ли порции битов из файла читать? Или свой класс разрабатывать? 

Автор: batigoal 27.4.2006, 10:00
Цитата(Platon @  27.4.2006,  09:14 Найти цитируемый пост)
Неужели в Java нет unsigned byte?

Нет, беззнаковых типов нету. 

Автор: Platon 27.4.2006, 10:50
Lamer George, и что? Мне в таком же духе продолжать? 

Автор: LSD 29.4.2006, 12:00
Цитата(Platon @  27.4.2006,  09:14 Найти цитируемый пост)
LSD, да согласен уже перешел. Но загвоздка в переводе 2-х байтов в int. Я решил ее одним методом, но уж больно кривенький он. Неужели в Java нет unsigned byte?
...

Очень странный код...
1. Ты делаешь из byte - char, при этом расширение будет знаковое. Т.е. для ASCII кодов (0-127) у тебя будет вполне осмысленный результат. А вот для кодов более 127 будет фигня, например byte - 0xFF, будет преобразован в char - \uFFF3 - которй вообще непонятно какой символ дает.
char - предназначен для хранения символов, если тебе нужно читать символы из файла используй CharsetDecoder. А если нет, то используй просто byte, short или int.

2. Затем вот эта странная конструкция:
Код
(str[2] & 0x7F) | ((((byte)str[2] & 0x80) == 0)? 0:0x0080)

ее смысл я вообще не понял.

Если тебе надо прочитать байты в int то делай так:
Код
byte[] b = {1, 2, 3, 4};
int i = (b[0] << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | (b[3] & 0xFF);
System.out.println("int = 0x" + Integer.toHexString(i).toUpperCase());

для big-endian
и
Код
byte[] b = {1, 2, 3, 4};
int i = (b[3] << 24) | ((b[2] & 0xFF) << 16) | ((b[1] & 0xFF) << 8) | (b[0] & 0xFF);
System.out.println("int = 0x" + Integer.toHexString(i).toUpperCase());

для little-endian 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)