• 0.00

  • 0.00

  • ,

level20.lesson01.lection и многократное использование потоков.

Согласно лекции приводится пример сохранения данных класса(ов) в файл и загрузка из файла:

    class Human
    {
     public Cat cat;
     public Dog dog;
    
     public void save(OutputStream outputStream) throws Exception
     {
      cat.save(outputStream);
      dog.save(outputStream);
     }
    
     public void load(InputStream inputStream) throws Exception
     {
      cat.load(inputStream);
      dog.load(inputStream);
     }
    }

как видно, человек может иметь собаку и/или кота:

    class Cat
    {
     public String name;
     public int age;
     public int weight;
    
     public void save(OutputStream outputStream) throws Exception
     {
      PrintWriter printWriter = new PrintWriter(outputStream);
      printWriter.println(name);
      printWriter.println(age);
      printWriter.println(weight);
      printWriter.flush();
     }
    
     public void load(InputStream inputStream) throws Exception
     {
      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
      name = reader.readLine();
      age = Integer.parseInt(reader.readLine());
      weight = Integer.parseInt(reader.readLine());
     }
    }
    class Dog
    {
     public String name;
     public int age;
    
     public void save(OutputStream outputStream) throws Exception
     {
      PrintWriter printWriter = new PrintWriter(outputStream);
      printWriter.println(name);
      printWriter.println(age);
      printWriter.flush();
     }
    
     public void load(InputStream inputStream) throws Exception
     {
      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
      name = reader.readLine();
      age = Integer.parseInt(reader.readLine());
     }
    }


Согласно приведенного кода один InputStream оборачивается в разные BufferedReader несколько раз.
Проблема: при такой реализации чтения файла (с короткими строками, об этом далее) уже второй вызов readLine() приведет к возврату null независимо от наличия информации в файле.

Предположения: (вполне возможно неверное)
При вызове readLine() BufferedReader читает из InputStream посимвольно пока не встретит '\n', '\r' или EOF, затем возвращает прочитанный буфер.
но! BufferedReader читает не посимвольно, а в буфер, размер которого указывается при вызове конструктора, либо по умолчанию устанавливается 8192 (private static int defaultCharBufferSize = 8192)
Другими словами readLine() вызывает чтение 8192 символов, если в конструкторе не указан иной размер буфера, что приводит к тому, что в исходном InputStream указатель текущего символа смещается на 8192 символа сходу в то время, когда строка может быть длиной 2-3 символа(!), соответственно последующее использование InputStream начнется уже минимум с 8193 символа, пропустив большУю часть полезной информации.

Возможное решение №1:
В конструкторе BufferedReader указывать размер буфера в 1 символ.
код:
package my.testpackage.test27112016;
    
    import java.io.*;
    public class Main
    {
        public static void main(String[] args) throws IOException
        {
            //getting fileName & creating InputStream
            InputStream is = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());
            
            //each readLine is called of differend BufferedReader
            System.out.println(readLine(is));
            System.out.println(readLine(is));
            System.out.println(readLine(is));
    
        }
        
        private static String readLine(InputStream is) throws IOException
        {
            BufferedReader br = new BufferedReader(new InputStreamReader(is), 1);
            return br.readLine();
        }
    }
    

файл:
1
2
3
4

вывод:
1
null
null


вывод:
Указание размера буфера равного единице при создании объекта BufferedReader не является решением.

Возможное решение №2:
Отказаться от BufferedReader и пользоваться InputStreamReader, читая посимвольно. Для определения конца файла использовать ready().
код:
изменения затронули только метод String readLine()

    private static String readLine(InputStream is) throws IOException
        {
            StringBuffer sb = new StringBuffer();
            InputStreamReader isr = new InputStreamReader(is);
            boolean isStringBegan = false;
            while (isr.ready()) {
                char c = (char) isr.read(); //there is reader, so it must be a char
                if (c == '\n' || c == '\r') /*skips all \n and \r at begining of line*/ {
                    if (isStringBegan) {
                        break;
                    }
                } else if (!isStringBegan) {
                    isStringBegan = true;
                }
                sb.append©;
            }
            return sb.length() > 0 ? sb.toString() : null;
        }

файл без изменений.
вывод:
1
null
null


вывод:
1. Изначально проблема была не в BufferedReader (хотя приведенные предположения могут оставаться в силе)
2. Просто так воспользоваться методом, предложенным в лекции не вышло.

Итак, в чем решение? :)

З.Ы. Задача, породившая вопрос не требует использования данной фичи (level20.lesson02.task01), но все же
  • ,

level17.lesson10.bonus02

Не проходит тестирование.

package com.javarush.test.level17.lesson10.bonus02;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/* CRUD 2
CrUD Batch - multiple Creation, Updates, Deletion
!!!РЕКОМЕНДУЕТСЯ выполнить level17.lesson10.bonus01 перед этой задачей!!!

Программа запускается с одним из следующих наборов параметров:
-c name1 sex1 bd1 name2 sex2 bd2 ...
-u id1 name1 sex1 bd1 id2 name2 sex2 bd2 ...
-d id1 id2 id3 id4 ...
-i id1 id2 id3 id4 ...
Значения параметров:
name - имя, String
sex - пол, "м" или "ж", одна буква
bd - дата рождения в следующем формате 15/04/1990
-с  - добавляет всех людей с заданными параметрами в конец allPeople, выводит id (index) на экран в соответствующем порядке
-u  - обновляет соответствующие данные людей с заданными id
-d  - производит логическое удаление всех людей с заданными id
-i  - выводит на экран информацию о всех людях с заданными id: name sex bd

id соответствует индексу в списке
Формат вывода даты рождения 15-Apr-1990
Все люди должны храниться в allPeople
Порядок вывода данных соответствует вводу данных
Обеспечить корректную работу с данными для множества нитей (чтоб не было затирания данных)
Используйте Locale.ENGLISH в качестве второго параметра для SimpleDateFormat
*/

public class Solution {
    public static List<Person> allPeople = new ArrayList<Person>();
    static {
        allPeople.add(Person.createMale("Иванов Иван", new Date()));  //сегодня родился    id=0
        allPeople.add(Person.createMale("Петров Петр", new Date()));  //сегодня родился    id=1
    }

    public static void main(String[] args) {
        //start here - начни тут
        int argsLength = args.length;
        if (!(argsLength == 0)) {
            String param = args[0];
            try
            {
                if (param.equals("-c") && argsLength >= 4) {
                    int beginIndex = 1;
                    //parcing name
                    for (;beginIndex < argsLength;)
                    {
                        String name = "";
                        for (int i = beginIndex; i < argsLength-1; i++)
                        {
                            if (args[i].equals("м") || args[i].equals("ж")) {
                                //next Person beginIndex
                                beginIndex = i+2;
                                break;
                            }
                            if (!(i == beginIndex)) {
                                name = name.concat(" ");
                            }
                            name = name.concat(args[i]);
                        }
                        create(name, args[beginIndex-2], args[beginIndex-1]);
                    }
                }
                else if (param.equals("-u") && argsLength >= 5) {
                    int beginIndex = 1;
                    //parcing name
                    for (;beginIndex < argsLength;)
                    {
                        String id = args[beginIndex];
                        String name = "";
                        for (int i = beginIndex+1; i < argsLength-1; i++)
                        {
                            if (args[i].equals("м") || args[i].equals("ж")) {
                                //next Person beginIndex
                                beginIndex = i+2;
                                break;
                            }
                            if (!(i == beginIndex)) {
                                name = name.concat(" ");
                            }
                            name = name.concat(args[i]);
                        }
                        update(id, name, args[beginIndex-2], args[beginIndex-1]);
                    }
                }
                else if (param.equals("-d") && argsLength >= 2) {
                    for (int i = 1; i < argsLength; i++)
                    {
                        delete(args[i]);
                    }
                }
                else if (param.equals("-i") && argsLength >= 2) {
                    for (int i = 1; i < argsLength; i++)
                    {
                        info(args[i]);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //TEST BLOCK
        for (int i = 0; i < allPeople.size(); i++) {
            info(i);
        }

    }

    private static void create(String name, String sex, String bd) throws SexParceException, ParseException {
        Sex parcedSex = parceSex(sex);
        Date parcedBirthDate = parceDate(bd);
        create(name, parcedSex, parcedBirthDate);
    }
    private static void create(String name, Sex sex, Date bd) throws SexParceException, ParseException {
        Person person = null;
        if (sex == Sex.MALE) {
            person = Person.createMale(name, bd);
        } else if (sex == Sex.FEMALE) {
            person = Person.createFemale(name, bd);
        }
        if (person != null) {
            synchronized (allPeople) {
                allPeople.add(person);
                System.out.println(allPeople.indexOf(person));
            }
        }
    }
    private static void update(String id, String name, String sex, String bd) throws NumberFormatException, SexParceException, ParseException  {
        int currentPersonId = parceId(id);
        if (currentPersonId < allPeople.size() && currentPersonId >= 0)
        {
            Sex parcedSex = parceSex(sex);
            Date parcedBirthDate = parceDate(bd);
            Person person = allPeople.get(currentPersonId);
            //could pe synchronized by allPeople
            synchronized (person)
            {
                person.setName(name);
                person.setSex(parcedSex);
                person.setBirthDay(parcedBirthDate);
            }
        }
    }
    private static void delete(String id) throws NumberFormatException, IndexOutOfBoundsException {
        int currentPersonId = parceId(id);
        delete(currentPersonId);
    }
    private static void delete(int id) {
        if (id < allPeople.size() && id >= 0)
        {
            Person person = allPeople.get(id);
            //could be synchronized by allPeople
            synchronized (person)
            {
                person.setName(null);
                person.setSex(null);
                person.setBirthDay(null);
            }
        }
    }
    private static void info(String id) throws NumberFormatException, IndexOutOfBoundsException {
        int currentPersonId = parceId(id);
        info(currentPersonId);
    }
    private static void info(int id) {
        if (id < allPeople.size() && id >= 0)
        {
            Person person = allPeople.get(id);
            //could be synchrnized by allPeople
            synchronized (person)
            {
                String sex = person.getSex() == Sex.MALE ? "м" : "ж";
                SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
                String date = sdf.format(person.getBirthDay());
                System.out.println(String.format("%s %s %s", person.getName(), sex, date));
            }
        }
    }

    private static int parceId(String id) throws NumberFormatException {
        return Integer.parseInt(id);
    }
    private static Date parceDate(String date) throws ParseException {
        Date result = null;
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH);
        result = sdf.parse(date);
        return result;
    }
    private static Sex parceSex(String sex) throws SexParceException {
        if (sex.equals("м")) {
            return Sex.MALE;
        }
        else if (sex.equals("ж")) {
            return Sex.FEMALE;
        }
        else {
            throw new SexParceException("Пол указан неверно!");
        }
    }

    private static class SexParceException extends Exception {
        public SexParceException()
        {
            super();
        }

        public SexParceException(String message)
        {
            super(message);
        }
    }
}


остальные два файла без изменений.

package com.javarush.test.level17.lesson10.bonus02;

public enum Sex {
    MALE,
    FEMALE
}



package com.javarush.test.level17.lesson10.bonus02;

import java.util.Date;

public class Person {
    private String name;
    private Sex sex;
    private Date birthDay;

    private Person(String name, Sex sex, Date birthDay) {
        this.name = name;
        this.sex = sex;
        this.birthDay = birthDay;
    }

    public static Person createMale(String name, Date birthDay){
        return new Person(name, Sex.MALE, birthDay);
    }

    public static Person createFemale(String name, Date birthDay){
        return new Person(name, Sex.FEMALE, birthDay);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }

    public Date getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }
}


Следующий код, находящийся в конце psvm main файла Solution.java перед отправкой на валидацию удалялся и служит только для проверки функциональности:

//TEST BLOCK
        for (int i = 0; i < allPeople.size(); i++) {
            info(i);
        }


Вывод для входных строк:
для "-c name1 фамилия1 м 01/01/1990 name2 фамилия2 ж 02/12/2000":
2
3
Иванов Иван м 13-Nov-2016
Петров Петр м 13-Nov-2016
name1 фамилия1 м 01-Jan-1990
name2 фамилия2 ж 02-Dec-2000

Process finished with exit code 0

для "-u 1 name1 фамилия1 м 01/01/1990 0 name2 фамилия2 ж 02/12/2000":
name2 фамилия2 ж 02-Dec-2000
 name1 фамилия1 м 01-Jan-1990

Process finished with exit code 0

для "-d 0 1":
Exception in thread "main" java.lang.NullPointerException
	at java.util.Calendar.setTime(Calendar.java:1770)
	at java.text.SimpleDateFormat.format(SimpleDateFormat.java:943)
	at java.text.SimpleDateFormat.format(SimpleDateFormat.java:936)
	at java.text.DateFormat.format(DateFormat.java:345)
	at com.javarush.test.level17.lesson10.bonus02.Solution.info(Solution.java:182)
	at com.javarush.test.level17.lesson10.bonus02.Solution.main(Solution.java:112)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

Process finished with exit code 1

, что обьясняется присвоением null ссылкам Person
для "-d 1":
Иванов Иван м 13-Nov-2016
Exception in thread "main" java.lang.NullPointerException
	at java.util.Calendar.setTime(Calendar.java:1770)
	at java.text.SimpleDateFormat.format(SimpleDateFormat.java:943)
	at java.text.SimpleDateFormat.format(SimpleDateFormat.java:936)
	at java.text.DateFormat.format(DateFormat.java:345)
	at com.javarush.test.level17.lesson10.bonus02.Solution.info(Solution.java:182)
	at com.javarush.test.level17.lesson10.bonus02.Solution.main(Solution.java:112)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

Process finished with exit code 1

, что демонстрирует, что демонстрирует, что NullPointerException возникает только во время отработки теста
для "-i 0 1":
Иванов Иван м 13-Nov-2016
Петров Петр м 13-Nov-2016
Иванов Иван м 13-Nov-2016
Петров Петр м 13-Nov-2016

Process finished with exit code 0

, дублирование обусловлено наличием теста
для "-i 0":
Иванов Иван м 13-Nov-2016
Иванов Иван м 13-Nov-2016
Петров Петр м 13-Nov-2016

Process finished with exit code 0


Эксперименты с синхронизацией результата не дали.
В предыдущем задании (level17.lesson10.bonus01) аналог приведенного кода валидацию прошел (включая пользовательские исключения).

Прошу ткнуть носом на причину отказа в валидации.
Конструктивная критика читабельности кода и корректности архитектуры приветствуется.

З.Ы. виноват, что создал тему на info.javarush.ru. Прошу удалить.