Сохранить во внешний текстовый файл из runnable jar - PullRequest
0 голосов
/ 13 июля 2020

Мне не удается записать рабочий файл jar во внешний текстовый файл. Мое намерение состоит в том, чтобы хранить данные в текстовом файле, который находится в той же папке, что и исполняемая банка (НЕ внутри банки); каждая строка представляет собой один набор данных, новые наборы данных добавляются в конец существующего файла. Я пробовал несколько разных подходов, которые отлично работают, когда я запускаю код из Eclipse, но как только он экспортируется в работающий jar (также через Eclipse), он больше не меняет текстовый файл.

Вот вещи, которые я пробовал:

  1. OutputStreamWriter

     public void writeData(String dataset) {
      try {      
        URL               url = getClass().getClassLoader().getResource("datasets.txt");
        OutputStream       os = new FileOutputStream(url.getFile(), true);
        OutputStreamWriter ow = new OutputStreamWriter(os);
    
        String line = "\n" + dataset; 
        ow.append(line);
        ow.close();
        os.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    
  2. BufferedWriter

    public void writeData(String dataset) {
      try {
         URL           fileUrl = getClass().getClassLoader().getResource("datasets.txt")
         BufferedWriter writer = new BufferedWriter(new FileWriter(fileUrl.getPath(), true));
    
         writer.newLine(); 
         writer.write(dataset);
         writer.close();
    
       } catch (IOException e) {
         e.printStackTrace();
       }
     }
    
  3. FileWriter

    public void writeData(String dataset) {   
      try {
        URL              url = getClass().getClassLoader().getResource("datasets.txt");
        FileOutputStream fos = new FileOutputStream(url.getFile(), true);
        FileWriter       out = new FileWriter(getFilePath(), true);
    
        out.append(dataset);
        out.close();
        fos.close();
      }  catch (IOException e) {
        e.printStackTrace();
      }
    }
    

Чтение файла тоже дало мне некоторые проблемы вначале, но я заставил его работать с InputStreamReader, добавив код на всякий случай

private void readData() {
    InputStream       is = getClass().getClassLoader().getResourceAsStream("datasets.txt");
    InputStreamReader fr = new InputStreamReader(is);
    BufferedReader    br = new BufferedReader(fr);

    String line = null;
    while((line = br.readLine())!= null) {      
        dataVec.add(dataset); ...

Я никогда раньше не делал ничего подобного, поэтому буду благодарен за любые подсказки, почему эти подходы не работают, или за любые советы, которые могут помочь, заранее спасибо!

Редактировать: на основе объяснения, которое я получил почему мой подход не работает. Я решил реализовать средство выбора файлов, чтобы позволить пользователю выбирать файл, с которым он хочет работать, вместо того, чтобы пытаться работать с фиксированным файлом в месте, относящемся к банке. Таким образом я избегаю проблем с поиском относительного пути и имею возможность работать с несколькими отдельными файлами, содержащими разные наборы данных.

1 Ответ

2 голосов
/ 13 июля 2020

Здесь всякая путаница:

  1. getResource и getResourceAsStream не могут быть записаны. Вы не можете преобразовать их в файлы и молиться, чтобы вы могли; ресурсы - абстрактные понятия; в частности, абстрактная концепция, которая определенно не дает никаких обещаний относительно возможности записи. Тривиальный случай: обычно «при развертывании» код java находится в файлах jar, и вы не можете записывать в файлы jar таким образом, обычно не хотите, чтобы записывал в файлы jar, и даже если вы это сделали, правильная защищенная установка, как правило, такова, что процесс не может записывать в свой собственный файл jar, поэтому все это упражнение обречено на провал. Вы этим не пользуетесь. вообще.

  2. 'писать в то же место, где находится банка' - это в общем подозрительно. Зачем? В windows около 1990 года это было чрезвычайно распространено, но это никогда не было хорошим местом для записи файлов для каждого пользователя на любой ОС, которая не входила в линию DOS / Windows / CPM / MacOS, а 1980-е годы уже давно прошел мимо нас. На данный момент эта модель не была особенно распространенной на Windows уже более десяти лет; вместо этого вы бы написали, например, в папку «Документы» пользователя. Сама модель уступает: запись в тот же каталог, в котором находится исполняемый код, подразумевает, что вы можете писать в код, и это, как правило, ждет, чтобы ваш ящик получил p0wned, поэтому операционные системы массово отказываются от этой модели.

  3. Если вы ДОЛЖНЫ писать в ту же папку, в которой находится jar, ну, это предполагает, что ваш код по необходимости всегда запускается как jar, что является причудливым предположением для кодирования в вашем исходные файлы. Что, если это не баночка, например, при отладке в eclipse? Рядом с записью пути к классам, в которой размещены ваши файлы классов, поэтому для вашего среднего проекта eclipse ~/workspace/projname, поскольку это каталог, содержащий ~/workspace/projname/bin, где находятся скомпилированные файлы классов? Хорошо, ты МОЖЕШЬ это сделать. Но вы не хотите этого делать (см. Пункты 1 и 2). Что, если классы получены из сети, сгенерированы «на лету» или иным образом из загрузчика классов, который не имеет понятия «исходная запись пути к классам, откуда она пришла, является ? ' - Просто молитесь, чтобы ваш код никогда не развертывался в таких обстоятельствах? Я бы по крайней мере предложил альтернативный вариант на основе -D (который вы можете прочитать с помощью System.getProperty, поэтому в вашем java коде String overrideLocation = System.getProperty("islandersproject.homedir"); и для запуска вашего приложения: java -Dislandersproject.homedir=/Users/islander/documents -jar islandersproject.jar.

Хорошо, как мне это сделать?

Ну, я сказал, что можно. Но это непросто.

Для любого данного класса вы можете получить место, в которое он был загружен с помощью этой конструкции:

ClassName.class.getResource("ClassName.class").toString()

Пока рассматриваемый класс не является внутренним классом, это работает. Например:

System.out.println(String.class.getResource("String.class").toString()) - попробуйте.

Полученная строка может быть чем угодно. IF это файл (например, если класс /Users/islander/workspace/islandproject/bin/com/foo/island/islandproject/Island.class, вы получите эта строка с file:// прикреплена к передней части. Если это jar, вы получите что-то вроде: jar:file://Users/islander/blabla/islanderproject.jar!com/foo/island/islandproject/Island.class - если это jmod, он начинается с jmod: (запустите это в String на JDK11 или 14, чтобы увидеть его в действии ), если это сеть или генерируется на лету, это будет еще больше exoti c.

Если вы действительно уверены, что хотите это сделать, проанализируйте строку.

Нет то есть, что '.' (текущий рабочий каталог) никоим образом не гарантирует правильности; если вы запустите в командной строке windows, например:

C:
CD \foobar
java -jar d:\workspace\dist\islander.jar

Тогда . будет совершенно не связанным c:\foobar, поскольку это был рабочий каталог, в котором вы начали. Поэтому маловероятно будьте правы, даже если вам повезло с этой работой при тестировании (поскольку eclipse и тому подобное установят его в нормальное место, когда вы запустите приложение из eclipse).

...