Java - 每次执行循环需要更长时间吗?

时间:2014-07-24 12:18:27

标签: java performance loops jpa heap

我正在研究一些涉及将邮政编码,城市和国家存储在一起的J2EE项目。我们开发了一个Java类来处理每个国家文件的集成(包含每个邮政编码和每个城市)。问题是,对于一些国家(英国,荷兰......),该文件非常紧凑(400.000到800.000行)。

我有一个while()循环,它读取下一行,获取信息并将其存储到我的数据库中。问题是,对于1000或10.000的第一行,进程很快,非常快,然后每次进入循环时似乎都在减慢,然后恰好在150.000行之后抛出HeapSpaceOverflowException

我首先想到的是一些对象没有被垃圾收集并且减慢了我的算法,但我无法弄清楚哪一个。此外,当我在我的PC上运行这个算法时,JConsole告诉我定期清理堆空间(似乎是垃圾收集),但过程仍然更慢更慢......

以下是该方法的代码:

FileReader fr = new FileReader(nomFichier);
BufferedReader br = new BufferedReader(fr);

int index = 0; String ligne; String codePostal; String nomVille; 
String codePays; PPays pays; String[] colonnes;

while ((ligne = br.readLine()) != null)
{
    System.out.println("line "+ ++index);

    colonnes = ligne.split(Pattern.quote(";"));

    codePostal = colonnes[9];
    nomVille   = colonnes[8];
    codePays   = colonnes[0];

    pays = this.pc.getByCodePays(codePays);

    this.pc.getByCodePostalAndVilleAndINSEE(codePostal, nomVille, pays.getNomPays(), "");
}

变量this.pc通过@Inject注释注入。

有人可以帮我弄清楚为什么这段代码变得越来越慢?

非常感谢。

编辑:为了完成起见,我添加了get...()方法的代码:

public Codepostalville getByCodePostalAndVilleAndINSEE(String codePostal, String ville, 
                                                       String pays, String codeINSEE) throws DatabaseException
{
    Codepostal cp = null; Ville v = null; PPays p = null; Codepostalville cpv = null;

    try
    {
        // Tout d'abord, il faut retrouver l'objet CodePostal
        cp = (Codepostal) this.em
                        .createNamedQuery("Codepostal.findByCodePostal")
                        .setParameter("codePostal", codePostal)
                        .getSingleResult();
    }
    catch (NoResultException nre1)
    {
        // Si on ne l'a pas trouvé, on le crée
        if (cp == null)
        {
            cp = new Codepostal();
            cp.setCodePostal(codePostal);
            cpc.getFacade().create(cp);
        } 
    }

    // On retrouve la ville...
    try
    {
        // Le nom de la ville passé par l'utilisateur doit être purgé (enlever
        // les éventuels tirets, caractères spéciaux...)
        // On crée donc un nouvel objet Ville, auquel on affecte le nom à purger
        // On effectue la purge, et on récupère le nom purgé
        Ville purge = new Ville();
        purge.setNomVille(ville);
        purge.purgerNomVille();
        ville = purge.getNomVille();

        v = (Ville) this.em
                        .createNamedQuery("Ville.findByNomVille")
                        .setParameter("nomVille", ville)
                        .getSingleResult();
    }
    catch (NoResultException nre2)
    {
        // ... ou on la crée si elle n'existe pas
        if (v == null)
        {
            v = new Ville();
            v.setNomVille(ville);
            vc.getFacade().create(v);
        }
    }

    // On retrouve le pays
    try
    {
        p = (PPays) this.em
                        .createNamedQuery("PPays.findByNomPays")
                        .setParameter("nomPays", pays)
                        .getSingleResult();
    }
    catch (NoResultException nre2)
    {
        // ... ou on la crée si elle n'existe pas
        if (p == null)
        {
            p = new PPays();
            p.setNomPays(pays);
            pc.getFacade().create(p);
        }
    }

    // Et on retrouve l'objet CodePostalVille
    try
    {
        cpv = (Codepostalville) this.em
                .createNamedQuery("Codepostalville.findByIdVilleAndIdCodePostalAndIdPays")
                .setParameter("idVille", v)
                .setParameter("idCodePostal", cp)
                .setParameter("idPays", p)
                .getSingleResult();

        // Si on a trouvé l'objet CodePostalVille, on met à jour son code INSEE
        cpv.setCodeINSEE(codeINSEE);
        this.getFacade().edit(cpv);
    }
    catch (NoResultException nre3)
    {         
        if (cpv == null)
        {
            cpv = new Codepostalville();
            cpv.setIdCodePostal(cp);
            cpv.setIdVille(v);
            cpv.setCodeINSEE(codeINSEE);
            cpv.setIdPays(p);
            this.getFacade().create(cpv);
        }
    }

    return cpv;
}

再次感谢。

编辑2:所以,我有更多信息。 getCodePostal...()方法需要大约15ms才能在循环开始时执行,而在10.000行之后,它需要执行超过100ms(几乎是10倍!)。 在这个新版本中,我已经禁用了提交/回滚代码,因此每个查询都是动态提交的。

我无法真正找到为什么需要越来越多的时间。

我试图搜索有关JPA缓存的一些信息: 我当前的配置是这个(在persistence.xml中):

   <property name="eclipselink.jdbc.bind-parameters" value="true"/>
  <property name="eclipselink.jdbc.cache-statements" value="true"/>
  <property name="eclipselink.cache.size.default" value="10000"/>
  <property name="eclipselink.query-results-cache" value="true"/>

我不知道它是否是最有效的配置,我希望得到一些帮助和一些关于JPA缓存的解释。

感谢。

2 个答案:

答案 0 :(得分:12)

您可能想要了解JPA概念。简而言之,EntityManager与持久化上下文相关联,该上下文保持对通过它操纵的所有持久对象的引用,因此它可以将对这些对象所做的任何更改写回数据库。

由于您从未关闭持久性上下文,这可能是导致内存泄漏的原因。此外,如果这些更改可能会更改查询结果,则持久性提供程序必须在发出查询之前将持久对象的更改写入数据库。要检测这些更改,需要迭代与当前持久上下文关联的所有对象。在您的代码中,您发出的每个查询都有近百万个对象。

因此,至少应该定期清除持久化上下文(比如每1000行)。

值得注意的是,除非您的数据库位于同一台服务器上,否则您发出的每个查询都必须通过网络传输到数据库,并在程序可以继续之前将结果返回给应用程序服务器。根据网络延迟,每次可能很容易花费一毫秒 - 而且这样做数百万次。如果它需要真正有效,将整个表加载到内存中,并在那里执行检查,可能会大大加快。

答案 1 :(得分:0)

问题&#34;解决了#34; (差不多)! 我已经按照这种方式配置了persistence.xml

<property name="eclipselink.jdbc.batch-writing" value="JDBC"/>
<property name="eclipselink.jdbc.batch-writing.size" value="10000"/>

起初,它没有改变任何东西。但后来,我试图将我的文件切成小块(当文件有超过5000行时,我读了5000行,我将它们存储在StringBuilder中,然后我读取StringBuilder一次插入5000行)。 这样,我的代码在20.000行(现在)之后不会变慢。 它看起来工作正常,但是当我处理更大的文件时,我仍然无法理解为什么我的代码变得越来越慢......

感谢所有试图帮助我的人;)