【问题标题】:How to load text file to HashMap at application startup?如何在应用程序启动时将文本文件加载到 HashMap?
【发布时间】:2013-09-06 14:20:42
【问题描述】:

我正在开发一个小项目,一个 Web 应用程序,我在其中根据用户文本输入对数据进行一些分析。

为此,我需要使用一个地图,其中有单词和该单词的相应分数。

这是我尝试过的:

public class EnDict {
    private static Map<String, Integer> map = new HashMap<String, Integer>() {
        {
            put("abandon", -2);
            put("ability", 2);
            put("abuse", -3);
            //and so on, thousands of pairs
        }
    }
}

它有效,但这样我需要在我的类中硬编码键/值对。所以,如果我想添加更多对,我必须编写代码,而不是仅仅添加对做一个文本文件。好像不太好。

所以我想从文本文件中获取这对。此外,我需要在应用程序启动时创建此 Map,因此当任何用户发出请求时,该 Map 已经加载,并且可供分析逻辑使用。我的意思是,Map 必须在第一个请求发生之前在内存中,并且在那之后最后在内存中,以便在后续请求中使用。并且它需要在我的应用程序的任何地方都可见(可能这部分不是很清楚,但我不知道如何更好地解释它)。

我已经尝试了一些研究,但是自从应用程序启动以来,我还没有找到将地图保存在内存中的特定部分的答案。它类似于 ASP.NET 的 Application_Start 方法,在 Global 类中。

我对编程非常陌生,尤其是对 Java 非常陌生,所以我可能完全误导了如何以最佳方式完成这项任务。如果是这种情况,我们将不胜感激。

我正在使用 Servlet、JSP 和 Tomcat。

编辑: 实际上,它不会只有一张地图。会有几个 Map,这些 Map 可以有一些相同的键。

【问题讨论】:

  • 也许Properties 并实现Singleton 模式来访问这些属性?
  • @dic19 我看到Properties 类的唯一问题是它不允许添加空值。但如果你没有 null 值,这不是问题。
  • @Mauren 同意了。你是对的。
  • 看看我在问题末尾的编辑。我需要有几个不同的地图(每种语言一个),并且我需要有可能在不同的地图中拥有相同的键,具有不同的值。据我所知,属性并不能解决问题,是吗?
  • 另外,由于Properties只存储字符串,而且我需要将值转换为int来执行计算,每次执行转换不会是某种开销吗?

标签: java


【解决方案1】:

将此映射定义为静态 - 它将一直在内存中,直到加载该类的类加载器未被垃圾回收。

我上面说的是:http://docs.oracle.com/javase/specs/jls/se5.0/html/execution.html#12.7

静态成员链接到类,上面的规范说在类加载器到位之前不会卸载类。

而对象确实会被垃圾收集。因此建议将地图设为静态(也将其公开,以防需要从外部访问)。

并将文件加载到地图中

将其存储在文件中

键1=值1 键2=值2 …… ....

现在使用 BufferedReader 如下

BufferedReader reader = new BufferedReader(new FileReader(new File("pathname")));
        String line = null;
        Map<String, Integer> map = new HashMap<String, Integer>();// it should be static - whereever you define
        while ((line = reader.readLine()) != null) {
            if (line.contains("=")) {
                String[] strings = line.split("=");
                map.put(strings[0], Integer.parseInt(strings[1]));
            }
        }

类加载器是在启动应用程序时将类加载到内存中的东西。 Tomcat 也有它的类加载器,它在内存中加载所需的类(类而不是对象)。现在我们知道静态变量与类而不是对象相关联。因此静态成员与类一起加载到内存中。在许多其他情况下,您将创建该类的对象并使用它。如果您在内存中加载了数百万个对象 - 您很快就会缺少它。所以java有一个叫做垃圾收集器的东西。此垃圾收集器从内存中删除不需要的/旧对象以回收它。垃圾收集器移除对象而不是类,因此静态成员仍保留在内存中。

【讨论】:

  • “将此映射定义为静态 - 它将在内存中直到 JVM 关闭”。这回答了我的疑问。
  • 我编辑了我的答案 - 直到 Classloader 到位。意味着类加载器本身不是垃圾收集的。我认为类加载器只会在 jvm 关闭时为 tomcat 卸载。
  • 对不起,我现在实在看不懂。有几个方面我不明白你已经改变了你的答案。什么是类加载器?我正在阅读您提供的参考链接,但这对我来说都是高级的。无论如何,我的疑问现在仍然存在,所以任何帮助将不胜感激。
【解决方案2】:

您可以像这样在静态块中静态初始化静态变量:

private static Map<String, Integer> map = new HashMap<String,Integer>();

static {
    fillMap(map, "filename.txt");
}

private static void fillMap(Map<String, Integer> map, String fileName) {
   // here comes file reading code with loop
}

如何读取文件见Reading a plain text file in Java

只要它的所有静态地图都将在应用程序启动时初始化。

【讨论】:

  • 这会在应用程序启动时加载文件,并将地图保存在内存中以处理所有后续请求?
  • fillMap 方法将在任何人访问包装类之前执行。它是静态的,因此变量将一直保存并与整个应用程序共享。将不可变映射与 getter 仅访问一起使用会很好。我认为这就是你想要的,尽管它设计得不好:)
  • 但是地图不会为每个发出请求的不同用户初始化吗?我不明白这部分。
  • 代码中的static { ... } 块是什么?我的意思是,它是一种方法,还是什么?
  • 静态的一切都属于类而不属于对象。这意味着它是在加载类之后调用的(通常是在它第一次使用之前)。加载的类每个 jvm 实例仅存在一个,因此其属性是共享的。 static { ... } 块与构造函数有一些相似之处。简单地说,在类加载器加载类之后调用块之外的所有内容。
【解决方案3】:

尝试将文本文件加载到您的应用程序中:Read from a Text File into a hash map or list

当我刚开始编程时,我知道我很想使用很多全局变量。事实证明,这通常不是最好的策略(参见http://c2.com/cgi/wiki?GlobalVariablesAreBad)。

也许您可以首先在 main 方法中加载字典,然后将其传递给稍后需要的其他方法。

【讨论】:

  • 对不起,我的无知,但我没有看到我在这个网络项目中的主要方法。
【解决方案4】:

你可以在 web.xml 文件中定义一个监听器:

<listener>
    <listener-class>my.Listener</listener-class>
</listener>

然后你实现了这个类:

package my;

public class Listener implements javax.servlet.ServletContextListener {

   public void contextInitialized(ServletContext context) {
       File file = new File();
       fileEntries = ... // load your entries
       for (Object[] line : fileEntries) {
           YourClass.get().addElement((String) line[0], Integer.parseInt(line[1].toString());
       }
   }
}

如果您想在应用程序范围内访问您的 Map,只需创建一个单例或使用 Spring 来管理该类,如果单例执行以下操作:

public class YourClass {
     private static final YourClass INSTANCE = new YourClass();

     private Map<String, Integer> yourMap;

     private YourClass() {
         yourMap = new HashMap<>();
     }

     public static final YourClass get() {
         return INSTANCE;
     }

     public void addElement(String key, Integer value) {
         yourMap.put(key, value);
     }

     public Integer getValueForKey(String key) {
         return yourMap.get(key);
     }
}

因此您可以通过以下方式从应用程序中的任何位置访问元素:

YourClass.get().getValueForKey("yourKey");

【讨论】:

  • 我不知道什么是单例,但我会查一下
  • 这里只是给你一个完整的例子,是YourClass类
  • 是的,但我现在没有足够的知识来理解它。所以我会研究它,谢谢。
【解决方案5】:

我建议您使用Properties 来存储/加载键/值 对并实现Singleton 模式来访问这些属性。像这样的:

public class EnDict {

    private Properties properties;
    private static EnDict enDictInstance;

    private EnDict {
        properties = new Properties();
        FileInsputStream fis = null;
        try{
            fis = new FileInputStream("yourPropertiesFile.properties");
            properties.load(fis);
            fis.close();
        } catch(IOException ex) {
            /* log the exception */
        } finally {
            try {
                fis.close();
            } catch (IOException ignored) {}
        }
     }

     public static EnDict getEnDictInstance(){
         if(enEdictInstance == null) {
             enEdictInstance = new EnEdict();
          }
          return enEdictInstance;
     }

     public Integer getValue(String key){
         String value = properties.getProperty(key);
         return Integer.valueOf(value);
     }

     public void setNewWord(String word, Integer value){
         properties.setProperty(word, value.toString());
     }

     public void saveProperties() {
         FileOutputStream fos = null;
         try {
             fos = new FileOutputStream("yourPropertiesFile.properties");
             properties.store(fos, "Some comments");
             fos.close();
         } catch (IOException ex) {
               /* log the exception */ 
         } finally {
             try{
                fos.close();
            } catch(IOException ignored){}
         }
     }
}

正如@Mauren 指出的那样,请记住Properties 不允许null 值。 您也可以使用 XML 文件来代替 .properties 文件。见Loading Properties from XML

【讨论】:

  • Singleton 模式确实有帮助,但自从我期待 cool stuff=3 之类的东西并实际上得到 cool=stuff=3 之后,Properties 就没有了。
  • 那么在这种情况下,您有两个选择:a) 以 XML 格式加载/存储属性(请参阅this)。这样你就可以有一个像&lt;entry key="cool stuff"&gt; 3 &lt;/entry&gt; 这样的条目,它会完美地工作。 b) 忘记属性并按照 Akshay Thorat 的建议实现您自己的文件读取器/写入器。
【解决方案6】:

为了将常量从不同资源加载到不同的java类,你可以使用apache commons配置库http://commons.apache.org/proper/commons-configuration/

对于应用程序启动,您可以使用

<servlet>
    <servlet-name>StartUp</servlet-name>
    <display-name>StartUp Servlet</display-name>
    <servlet-class>foo.bar.YourStartUpServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet> 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-04
    • 1970-01-01
    • 2010-09-17
    相关资源
    最近更新 更多