2

I'm developing a little project, a web application where I run some analysis on data based on user text input.

To do so, I need to use a Map where I have words and corresponding scores for that word.

This is what I tried:

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
        }
    }
}

It works, but this way I need to have the key/value pairs hard-coded in my class. So, if I want to add more pairs, I have to write code, instead of just adding pairs do a text file. Doesn't seem good.

So I want to obtain this pairs from a text file. Also, I need this Map to be created when the application starts, so when any user makes a request the Map is already loaded, and can be used by the analysis logic. I mean, the Map must be in memory before the first request happens, and last in memory after that, to be used in subsequent requests. And it need to be visible from anywhere in my application (maybe this part wasn't very clear, but I don't know how to explain it better).

I've tried some research, but haven't found answers to this specific part of keeping the Map in memory since the application start. It's something similar to ASP.NET Application_Start method, in the Global class.

I'm very new to programming and specially to Java, so maybe I'm completely misled about how would be the best way of achieving this task. If that is the case, some tip would be appreciated.

I'm using Servlets, JSP and Tomcat.

Edit: Actually, it would not be only one Map. There will be several Maps, and those Maps can have some keys that are identical.

Rafael Eyng
  • 3,892
  • 1
  • 30
  • 34
  • 4
    Maybe [Properties](http://docs.oracle.com/javase/tutorial/essential/environment/properties.html) and implementing `Singleton` pattern to access these properties? – dic19 Sep 06 '13 at 14:24
  • @dic19 the only problem I see with `Properties` class is that it doesn't allow null values to be added. But if you have no null values, it can't be an issue. – Mauren Sep 06 '13 at 14:28
  • @Mauren agreed. You're right. – dic19 Sep 06 '13 at 14:35
  • Look at my edit in the end of the question. I would need to have several different Maps (one for each language), and I need to have the possibility of having the same key in different Maps, with different values. As far as I could understand, Properties doesn't do the trick, does it? – Rafael Eyng Sep 06 '13 at 17:28
  • Besides, since `Properties` only stores Strings, and I need the values to be int to perform the calculations, wouldn't it be some kind of overhead to perform the conversion every time? – Rafael Eyng Sep 06 '13 at 17:59
  • @RafaelEyng See edited my answer, methods `setNewWord()` and `getValue()`. You easily can solve the fact that Properties only store Strings. Regarding your requirement of having several Maps you can make a `Singleton` class with its own properties for each language you want to support. I am right? – dic19 Sep 06 '13 at 18:42
  • @dic19 I don't know if you are right. I'm very beginner and still didn't get the idea of the `Singleton` – Rafael Eyng Sep 06 '13 at 18:48
  • [Singleton pattern](http://en.wikipedia.org/wiki/Singleton_pattern) restricts the instantiation of a class, because its constructor is private. So you only will have one instance during the whole JVM time life accessible through a `public static` method (and it always will return the same instance of that class). So in your case, you'll need one class implementing Singleton pattern for each language: `EnDic`,`SpDic`,`DeDic`,`ItDic`,`FrDic` etc – dic19 Sep 06 '13 at 18:57
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36954/discussion-between-dic19-and-rafael-eyng) – dic19 Sep 06 '13 at 19:00

6 Answers6

4

Define this map as static - it will be in memory until class loader who loaded this class is not garbage collected.

I say above by refering : http://docs.oracle.com/javase/specs/jls/se5.0/html/execution.html#12.7

Static member are linked to class and above specification says that classes will not be unloaded until class loader is in place.

Whereas objects do get garbage collected. Hence suggested to make map static(make it public too in case needs access from outside).

And for loading file into map

store it in file as

key1=value1 key2=value2 .... ....

now use BufferedReader as below

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]));
            }
        }

Class loader is something which loads classes in memory while starting the application. Tomcat also has its classloader which loads required classes in memory(Classes and not objects). Now we know that static variables are associated with class and not object. So static members are loaded in memory along with class. IN many other cases you would be creating object of the class and use it. If you have millions of objects loaded in memory- You will be soon short of it. So java have something called garbage collector. This garbage collector removes unwanted/old objects from memory to recycle it. Garbage collector removes objects not classes and hence static member still remains in memory.

Ashay Thorat
  • 620
  • 4
  • 11
  • "Define this map as static - it will be in memory until JVM shutdown". This answered my doubt. – Rafael Eyng Sep 06 '13 at 14:46
  • I edited my answer - Its until Classloader is in place. Means classloader itself is not garbage collected. I think classloader will only be unloaded on jvm shutdown for tomcat. – Ashay Thorat Sep 06 '13 at 15:22
  • Sorry, but I really can't understand it now. There are several aspects that I don't understand anymore that you've changed your answer. What is class loader? I'm reading the reference links that you provided but this is all to advanced for me right now. Anyway, my doubt is still open by now, so any help would be appreciated. – Rafael Eyng Sep 06 '13 at 17:12
3

You can staticaly initialize static variable in static block like this:

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
}

How to read file see something like this Reading a plain text file in Java.

As far as its all static map will be initialized on application startup.

Community
  • 1
  • 1
svobol13
  • 1,576
  • 2
  • 22
  • 33
  • And this would load the file at application startup, and keep the Map in memory to handle all subsequent requests? – Rafael Eyng Sep 06 '13 at 14:32
  • 1
    The fillMap method will be performed before anyone can access wrapper class. It is static so the variables will be held all the tim and shared with whole application. It would be nice to use immutable map with getter only access. I think this is what you want altough its not designed well :) – svobol13 Sep 06 '13 at 14:36
  • But wouldn't the map be initialized for each different user who makes a request? I don't get this part. – Rafael Eyng Sep 06 '13 at 17:53
  • What is the `static { ... }` block in your code? I mean, is it a method, or what? – Rafael Eyng Sep 06 '13 at 18:09
  • Everything what is static belongs to the Class not to the object. It means that it is invoked after class is loaded (that is often before it is used first time). Loaded class exists only one per jvm instance so its properties are shared. static { ... } block has vaguely some similarities to constructor. Simply put everything iside the block is invoked after class is loaded by class loader. – svobol13 Sep 06 '13 at 19:58
  • Ah! So the static { ... } block is "kind of" a constructor for a class that will have no instances? – Rafael Eyng Sep 06 '13 at 20:09
1

Try this for loading the text file into your application: Read from a Text File into a hash map or list

When I was just starting to program, I know I was tempted to use a lot of global variables. As it turns out, this is usually not the best strategy (see http://c2.com/cgi/wiki?GlobalVariablesAreBad).

Perhaps you can load your dictionary first thing in your main method, and pass it through to other methods that need later on.

Community
  • 1
  • 1
lucas
  • 753
  • 6
  • 18
1

You can define a Listener on web.xml file:

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

and you implement the class:

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());
       }
   }
}

if you want to access your Map application-wide, just create a singleton or use Spring to have the class managed, if a singleton do something like:

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);
     }
}

and so you can access the elements from anywhere in the application via:

YourClass.get().getValueForKey("yourKey");
OscarG
  • 265
  • 5
  • 11
1

I would suggest you use Properties to store/load key/value pairs and implement Singleton pattern to access these properties. Something like this:

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){}
         }
     }
}

As @Mauren pointed out just keep in mind Properties doesn't allow null values. Also instead of .properties files you can use XML files. See Loading Properties from XML

dic19
  • 17,446
  • 6
  • 35
  • 64
  • The `Singleton` pattern did help, but the `Properties` didn't since when I was expecting things like `cool stuff=3` and actually got `cool=stuff=3`. – Rafael Eyng Sep 08 '13 at 20:26
  • Well in that case you have two options: a) Load/store Properties in XML format (see [this](http://www.ibm.com/developerworks/library/j-tiger02254/index.html)). This way you can have an entry like this ` 3 ` and it will perfectly work. b) Forget Properties and implement your own file reader/writer as Akshay Thorat suggested. – dic19 Sep 09 '13 at 11:26
0

For loading the constants from different resources to different java classes you can use apache commons configuration library http://commons.apache.org/proper/commons-configuration/

For application start up you can use

<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> 
Alireza Fattahi
  • 33,509
  • 12
  • 96
  • 140