Чтение объектов до конца файла в java

Пытаюсь написать программу, в которой пользователь может: 1) Добавить человека в контакт (имя, телефон, email), 2) Удалить человека из контактов, 3) Читать все из контакта.

Как я это делаю, я прошу пользователя сделать свой выбор и, соответственно, делаю все, что угодно. Для записи я просто записываю объект в файл. Для удаления, я думаю, я попрошу у пользователя «фамилию», которая будет использоваться в качестве КЛЮЧА (поскольку я использую TreeMap) и удалю значение (объект) в ключе.

Так что у меня проблема с чтением здесь. Я пытаюсь прочитать объект так:

public void readContact()
{
  TreeMap<String, Contact> contactMap = new TreeMap<String, Contact>();
  try 
  {
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
                                                   new FileInputStream(file)));

    while( in.available() > 0 ) //This line does NOT read  
    {
      Contact c = (Contact)in.readObject();
      contactMap.put(c.getLastName(), c);           
    }

    for(Map.Entry contact : contactMap.entrySet() ) 
    {
      Contact con = contactMap.get( contact.getKey() );
      System.out.println( con.getLastName() + ", " + con.getFirstName() + ": " + con.getPhoneNumber() + "\t" + con.getEmail());
    }
  }
  catch(Exception e)
  {
    System.out.println("Exception caught");
  } 
}

Пожалуйста, не предлагайте делать что-то вроде while(true), пока я не получу EOFException, потому что:

  1. я считаю, что это не то, для чего нужна обработка исключений
  2. У меня еще много дел после этого, так что я не могу завершить программу.

person FudgeNouget    schedule 30.12.2012    source источник
comment
Обработка исключения не приводит к автоматическому завершению программы.   -  person Modus Tollens    schedule 31.12.2012
comment
Э-э, подождите... Вы перебираете набор записей карты и используете .get() на карте, чтобы получить значение?   -  person fge    schedule 31.12.2012
comment
Обработка исключения ПРЕВРАЩАЕТ программу, потому что, как только возникнет исключение, все, что находится внутри блока catch, будет выполнено, и программа завершится.... Например, если я вызову метод readContact() из какого-либо другого метода и попытаюсь сделать что-то, он завершится в методе readContact(), и поэтому я не смогу ничего сделать.   -  person FudgeNouget    schedule 31.12.2012
comment
@user1938670 user1938670 Да, но вам не нужно включать в область try весь ваш важный код. Просто используйте блок try вокруг кода, который может дать сбой, перехватите исключение и предоставьте альтернативу. Вы также можете вызвать исключение и обработать его в вызывающем коде.   -  person Modus Tollens    schedule 31.12.2012
comment
Поскольку вам не нужен ключ при распечатке, вы просто выполняете цикл с for(Contact con: contactMap.values())   -  person Peter Lawrey    schedule 31.12.2012
comment
Не по теме, но in.available() строго не делает того, что вы ожидаете. available() возвращает количество байтов, доступных для чтения без блокировки, но это не означает (строго говоря), что это размер данных или что там больше нет данных.   -  person Greg Kopff    schedule 31.12.2012
comment
Please do not suggest doing something like while(true) until I get the EOFException because: 1) that isn't what exception handling is for I believe - вы задаете здесь вопрос не для того, чтобы доверить более знающим людям?   -  person Greg Kopff    schedule 31.12.2012
comment
@user1938670 user1938670 Это не обязательно верно. Почему бы вам не попробовать то, что вы говорите нам, чтобы не предлагать, чтобы вы могли убедиться сами. (В качестве примера, почему бы просто не поместить return; в блок catch(EOFException exception)? На самом деле, почему бы не попробовать вызвать еще один код после того метода, который вы предоставили? Если это не удается (чего не должно быть!), почему бы не опубликовать скриншот (или ссылку на него), показывающий нам, что это произошло именно так, как вы опасались?   -  person Mike Warren    schedule 06.08.2013


Ответы (7)


Пожалуйста, не предлагайте делать что-то вроде while(true), пока я не получу EOFException

Это именно то, что я предлагаю. Когда вы ищете ответы, контрпродуктивно ограничивать пространство решений в соответствии с произвольными критериями, подобными этому.

потому что:

я считаю, что это не то, для чего нужна обработка исключений

Когда API, который вы вызываете, выдает исключение, как в этом случае, у вас нет другого выбора, кроме как перехватить его. Что бы вы ни думали о том, «для чего нужна обработка исключений», вы зависите от того, что думали разработчики API, когда они разрабатывали API.

У меня еще много дел после этого, так что я не могу завершить программу.

Так что не прерывайте его. Поймать EOFException,, закрыть вход и выйти из цикла.

Я видел более дорогостоящее время программирования, потраченное впустую на то, «для чего нужна обработка исключений», чем я действительно могу себе представить.

person user207421    schedule 31.12.2012

Я знаю, что вы ищете ответ, который не использует обработку исключений, но я считаю, что в этом случае использование EOFException для определения того, когда все входные данные были прочитаны, является правильным способом.

В JavaDoc EOFException указано, что

Это исключение в основном используется потоками ввода данных, чтобы сигнализировать об окончании потока. Обратите внимание, что многие другие операции ввода возвращают специальное значение в конце потока, а не вызывают исключение.

Таким образом, существуют входные потоки, которые используют другие средства для сигнализации о конце файла, но ObjectInputStream#readObject использует ObjectInputStream$BlockDataInputStream#peekByte, чтобы определить, есть ли еще данные для чтения, а peekByte выдает EOFException, когда достигнут конец потока.

Таким образом, это исключение можно использовать как индикатор достижения конца файла.

Для обработки исключений без прерывания выполнения программы некоторые из возможных исключений должны быть пропущены вверх по иерархии. Они могут быть обработаны блоком try - catch в коде, который вызывает readContact().

EOFException можно просто использовать как индикатор того, что мы закончили чтение объектов.

public TreeMap<String, Contact> readContact() throws FileNotFoundException,
            IOException, ClassNotFoundException {

    TreeMap<String, Contact> contactMap = new TreeMap<String, Contact>();

    // The following call can throw a FileNotFoundException or an IOException.
    // Since this is probably better dealt with in the calling function, 
    // readContact is made to throw these exceptions instead.
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
                new FileInputStream(file)));

    while (true) {
        try {
            // Read the next object from the stream. If there is none, the
            // EOFException will be thrown.
            // This call might also throw a ClassNotFoundException, which can be passed
            // up or handled here.
            Contact c = (Contact) in.readObject();
            contactMap.put(c.getLastName(), c);

            for (Map.Entry<String, Contact> contact : contactMap.entrySet()) {
                Contact con = contact.getValue();
                System.out.println(con.getLastName() + ", "
                          + con.getFirstName() + ": " + con.getPhoneNumber()
                          + "\t" + con.getEmail());
            }
        } catch (EOFException e) {
            // If there are no more objects to read, return what we have.
            return contactMap;
        } finally {
            // Close the stream.
            in.close();
        }
    }
}
person Modus Tollens    schedule 31.12.2012
comment
что-то пахнет не так, что произойдет, если возникнет какое-либо другое исключение, безусловный оператор while и return в catch? о нет... - person vels4j; 31.12.2012
comment
@ vels4j Нет ничего плохого в использовании оператора return в блоке catch. Использование одного из них в блоке finally было бы плохим. Все проверенные исключения обрабатываются, и, поскольку файл будет иметь конец, peekByte в конечном итоге выдаст исключение EOFException, так что... Я не слишком беспокоюсь. - person Modus Tollens; 31.12.2012

- Exceptions используются не только для подачи сигнала тревоги, если что-то пойдет не так при вызове метода, но также используются в Threads и IO для различных других целей.

- Вы можете использовать Exception для обозначения конца файла.

- Используйте комбинацию try-catch, чтобы работать вместе с вышеперечисленным, чтобы программа работала плавно.

person Kumar Vivek Mitra    schedule 31.12.2012

Почему так много проблем при чтении объекта из файла, просто сохраните хэш-карту в файл и прочитайте то же самое один раз из файла, а затем выполните любую операцию.

Также я бы предложил использовать любую из объектно-ориентированных баз данных, таких как Db4o, чтобы сделать это быстро, и вы никогда не будете беспокоиться о завершении исключения файла

person vels4j    schedule 31.12.2012

  1. «ObjectInputStream. available возвращает 0» — известная проблема, см. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4954570, и поскольку мы не можем его использовать, я думаю, что EOFException будет разумным подходом в вашей ситуации. Перехват EOFExcepion не остановит вашу программу.
  2. Вы можете записать количество объектов в свой файл с помощью ObjectOutputStream.writeInt, а затем прочитать это число с помощью ObjectInputStream.readInt и узнать, сколько объектов нужно прочитать
  3. Вы можете использовать null в качестве маркера EOF.
  4. Вы можете сохранить свои объекты в виде массива, списка или даже карты, а затем прочитать их с помощью одного readObject.
person Evgeniy Dorofeev    schedule 31.12.2012
comment
Известная проблема или нет, даже если ObjectInputStream.available() не возвращает ноль, это все равно неправильный тест на конец потока. - person user207421; 17.11.2014

Что вы обнаружили

Вы узнали, что FileInputStream.available() возвращает 0, хотя в файле есть непрочитанные байты! Почему это? Это может произойти (что делает FileInputStream.available() ненадежным по нескольким причинам:

  1. Согласно этой документации, FileInputStream.available() приближает количество байтов, которые можно прочитать без блокировки
  2. Сам жесткий диск может изменить свою работу во время чтения (перейти от вращения к прекращению вращения)
  3. Файл, к которому вы пытаетесь получить доступ, является либо файлом в удаленной системе, либо файлом устройства: Может ли меня сбить с толку доступный FileInputStream.?
  4. FileInputStream может быть заблокирован

Альтернативный способ

В качестве альтернативы тому, чтобы полагаться на EOFException для закрытия файла, вы можете использовать (очень маленький!) двоичный файл, который отслеживает количество объектов в вашем файле. (Судя по вашему коду, похоже, что вы просто записываете объекты в файл.) Я использовал это только для того, чтобы

  1. сохранить количество байтов, которое само число будет потреблять
  2. используя это количество байтов, сохраните само число

Например, при первом создании файла сериализации я мог бы сделать хранилище двоичных файлов 1 1 (что указывает, что количество объектов в файле сериализации занимает 1 байт, и это число равно 1). Таким образом, после 255 объектов (помните, беззнаковый байт может хранить только до 28-1 == 255), если я напишу еще один объект (номер объекта 256 до 2562-1 == 65535), двоичный файл будет иметь в качестве содержимого 2 1 0, что указывает на то, что число занимает 2 байта и равно 1*2561+0 == 256. При условии, что сериализация надежна (удачи в обеспечении того, чтобы : http://www.ibm.com/developerworks/library/j-serialtest/index.html), этот метод позволит вам хранить (и обнаруживать) до 256255-1 байтов (что в значительной степени означает, что этот метод работает бесконечно).


Сам код

Как что-то подобное будет реализовано, будет выглядеть примерно так:

ObjectOutputStream yourOutputStream = new ObjectOutputStream(new FileOutputStream(workingDirectory + File.separatorChar + yourFileName);  //The outputStream
File binaryFile = new File(workingDirectory + File.separatorChar + nameOfFile); //the binary file
int numOfObjects = 0, numOfBytes; //The number of Objects in the file
//reading the number of Objects from the file (if the file exists)
try
{
    FileInputStream byteReader = new FileInputStream(binaryFile);
    numOfBytes = byteReader.read();
    //Read the rest of the bytes (the number itself)
    for (int exponent = numOfBytes; exponent >= 0; exponent--)
    {
        numOfObjects += byteReader.read() * Math.pow(256,exponent);
    }
}
catch (IOException exception)
{
    //if an exception was thrown due to the file not existing
    if (exception.getClass() == FileNotFoundException.class)
    {
        //we simply create the file (as mentioned earlier in this answer)
         try 
         {
             FileOutputStream fileCreator = new FileOutputStream(binaryFile);
             //we write the integers '1','1' to the file 
             for (int x = 0; x < 2; x++) fileCreator.write(1);
             //attempt to close the file
             fileCreator.close();
         }
         catch (IOException innerException)
         {
              //here, we need to handle this; something went wrong
              innerException.printStackTrace();
              System.exit(-1);
         }
    }
    else
    {
         exception.printStackTrace();
         System.exit(-2);
    }
}

Теперь у нас есть количество объектов в файле (я оставляю вам решать, как обновить байты, чтобы указать, что еще один объект был записан, когда yourOutputStream вызывает writeObject(yourObject);; мне нужно идти на часы.)

Изменить: yourOutputStream либо перепишет все данные в binaryFile, либо добавит к ним данные. Я только что узнал, что RandomAccessFile — это способ вставки данных в любое место файла. Опять же, я оставляю детали для вас. Однако вы хотите это сделать.

person Mike Warren    schedule 06.08.2013
comment
также стоит отметить, что мы могли бы просто обработать исключение с условием exception instanceof FileNotFound;, что было бы немного проще. - person Mike Warren; 07.08.2013
comment
Независимо от того, что available() может или не может делать, это, конечно, не тест на конец потока, поэтому он не имеет отношения к вопросу. - person user207421; 17.06.2020

Вы можете записать последний объект как null. И затем повторяйте, пока не получите нуль на стороне чтения. например

while ((object = inputStream.readObject()) != null) {
  // ...
}
person Akshay    schedule 17.11.2014
comment
Что делать, если вам нужно написать null в середине файла? Что делать, если файл по какой-то причине был закрыт преждевременно? Вам все еще нужно поймать EOFException и сломать, иначе ваш цикл будет работать вечно. - person user207421; 17.11.2014
comment
Считается, что объекты не будут нулевыми, и если файл заканчивается преждевременно, он, скорее всего, поврежден. - person Akshay; 19.11.2014
comment
@Ashkay Но объекты могут быть нулевыми, и удаление этой опции из API является бессмысленным ограничением, если только вы не думаете, что ваша цель в жизни — искоренить исключения, а не выполнять полезную работу. - person user207421; 18.09.2016