java - 循环是否每次都需要更长的时间来执行?

标签 java performance loops jpa heap-memory

我正在从事一些涉及将邮政编码、城市和国家/地区存储在一起的 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(), "");
}

通过@Inject注解注入(inject)变量this.pc

谁能帮我弄清楚为什么这段代码变得越来越慢?

为了完整起见,我添加了 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;
}

所以,我有一些更多的信息。 getCodePostal...() 方法在循环开始时需要大约 15 毫秒的时间来执行,在 10.000 行之后,需要超过 100 毫秒的时间来执行(几乎多 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 缓存的帮助和解释。

最佳答案

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

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

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

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

关于java - 循环是否每次都需要更长的时间来执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24933492/

相关文章:

Java正则表达式连接单个字母

.net - 如何分析 XAML 性能?

c++ - 如何优化此数组抽取/下采样程序的内存访问模式/缓存未命中?

performance - 对于大于 32kb 的文件,BLOB 导出速度很慢

java - Maven 无法使用 eclipse 下载依赖项,代理不起作用

java - 可扩展的 API 服务器,带有 ReSTLet?

python - 使用 for 循环创建一定数量的输入框,但只能从最后一个输入框获取最后一个值

python - 在 Python 的列表中使用 abs()

java - 将属性设置为 viewpager 的子级

ios - Loop 不使用调度队列、iOS、Swift 完成循环