有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java在局部变量中缓存属性吗?

考虑类^ {CD1>}。

public class Foo {
    private double size;

    public double getSize() {
        return this.size; // Always O(1)
    }
}

Foo有一个名为size的属性,该属性经常被给定的方法访问,但从未修改过。每当在任何方法中多次访问某个属性时,我总是将其缓存在变量中,因为“有人告诉我是这样的”,我没有仔细考虑它。i、 e

public void test(Foo foo) {
    double size = foo.getSize(); // Cache it or not?
    // size will be referenced in several places later on.
}

这是值得的,还是过火了

如果我不缓存它,现代编译器是否足够聪明,可以自己缓存它


共 (6) 个答案

  1. # 1 楼答案

    依我看,如果你真的担心性能的话,这有点过分了或太过广泛了,但是有几种方法可以确保变量被你的虚拟机“缓存”

    首先,您可以创建结果的最终静态变量(根据您的示例1或0),因此整个类只存储一个副本,然后您的局部变量只是一个布尔值(仅使用1位),但仍然保持结果值double(此外,如果仅为0或1,您也可以使用int)

    private static final double D_ZERO = 0.0;
    private static final double D_ONE = 1.0;
    
    private boolean ZERO = false;
    
    public double getSize(){
        return (ZERO ? D_ZERO : D_ONE);
    }
    

    或者,如果您能够在初始化类时设置大小,您可以使用此选项,您可以通过构造函数和static设置最终变量,但由于这是一个局部变量,您可以使用构造函数:

    private final int SIZE;
    public foo(){
        SIZE = 0;
    }
    
    public double getSize(){
        return this.SIZE;
    }
    

    这可以通过foo.getSize()访问

  2. # 2 楼答案

    当决定是否存储调用“GET()”方法返回的值时,我考虑的几个因素(

    1. get()方法的性能-除非API指定,或者除非调用代码与被调用方法紧密耦合,否则get()方法的性能没有保证。现在进行测试时,代码可能还可以,但是如果get()方法在将来性能发生变化,或者如果测试没有反映实际情况,代码可能会变得更糟。(例如,在一个容器中仅使用1000个对象进行测试,而现实世界中的容器可能有1000万个对象)用于for循环,每次迭代之前都会调用get()方法

    2. 可读性-可以为变量指定一个特定的描述性名称,以一种对get()方法的内联调用可能不清楚的方式澄清其用法和/或含义。不要低估这对那些审查和维护代码的人的价值

    3. 线程安全性-如果调用方法执行操作时另一个线程修改对象,get()方法返回的值是否可能更改?这样的更改应该反映在调用方法的行为中吗

    关于编译器是否会自己缓存它的问题,我想推测一下,在大多数情况下,答案必须是“否”。编译器安全地执行此操作的唯一方法是确定get()方法在每次调用时返回相同的值。只有当get()方法本身被标记为final,并且它所做的只是返回一个常量(即一个对象或原语也被标记为final)时,才能保证这一点。我不确定,但我认为这可能不是编译器所关心的场景。JIT编译器有更多的信息,因此可以具有更大的灵活性,但不能保证某些方法会得到JIT

    总之,不要担心编译器会做什么。缓存get()方法的返回值可能在大多数情况下都是正确的,很少(即几乎从不)是错误的。喜欢编写可读和正确的代码,而不是快速(est)和华而不实的代码

  3. # 3 楼答案

    最大的因素是性能。如果这是一个不需要大量CPU周期的简单操作,我会说不要缓存它。但如果您经常需要对不变的数据执行昂贵的操作,那么一定要缓存它。例如,在我的应用程序中,当前登录的用户在每个页面上都以JSON格式序列化,序列化操作非常昂贵,因此为了提高性能,我现在在用户登录时序列化一次用户,然后使用序列化版本将JSON放在页面上。以下是在性能方面取得显著改进的前后步骤:

    //以前

    public User(Principal principal) {
        super(principal.getUsername(), principal.getPassword(), principal.getAuthorities());
        uuid            = principal.getUuid();
        id              = principal.getId();
        name            = principal.getName();
        isGymAdmin      = hasAnyRole(Role.ROLE_ADMIN);
        isCustomBranding= hasAnyRole(Role.ROLE_CUSTOM_BRANDING);
        locations.addAll(principal.getLocations());
    }
    public String toJson() {
        **return JSONAdapter.getGenericSerializer().serialize(this);**
    }
    

    //之后

    public User(Principal principal) {
        super(principal.getUsername(), principal.getPassword(), principal.getAuthorities());
        uuid            = principal.getUuid();
        id              = principal.getId();
        name            = principal.getName();
        isGymAdmin      = hasAnyRole(Role.ROLE_ADMIN);
        isCustomBranding= hasAnyRole(Role.ROLE_CUSTOM_BRANDING);
        locations.addAll(principal.getLocations());
        **json = JSONAdapter.getGenericSerializer().serialize(this);**
    }
    public String toJson() {
        return json;
    }
    

    用户对象没有setter方法,除非用户注销然后重新登录,否则数据不会更改,所以在这种情况下,我认为缓存值是安全的

  4. # 4 楼答案

    如果每次都计算size的值,比如通过数组循环,而不是O(1),那么缓存该值将在性能方面有明显的好处。然而,由于Foosize在任何时候都不会改变,并且是O(1),因此缓存该值主要有助于提高可读性。我建议继续缓存该值,因为在现代计算系统中,可读性往往比性能更令人担忧

  5. # 5 楼答案

    我不知道是否有一个“正确”的答案,但我会保留一份本地副本

    在您的示例中,我可以看到getSize()是微不足道的,但在实际代码中,我并不总是知道它是否微不足道;即使它在今天是琐碎的,我也不知道有人不会来改变getSize()方法,使它在将来某个时候变得不琐碎

  6. # 6 楼答案

    在我的代码中,如果getSize()方法非常耗时,或者(更常见的情况是)结果用于或多或少复杂的表达式中,我会缓存它

    用于计算偏移量的大小,例如

    int offset = fooSize * count1 + fooSize * count2;
    

    (对我来说)读起来比读起来容易

    int offset = foo.getSize() * count1 + foo.getSize() * count2;