【问题标题】:How can I get size in bytes of an object sent using RMI?如何获取使用 RMI 发送的对象的字节大小?
【发布时间】:2012-10-17 13:04:19
【问题描述】:

我正在使用 MongoDB 和 ConcurrentHashMap java 类实现缓存服务器。当有可用空间将对象放入内存时,它将放入。否则,该对象将保存在 mongodb 数据库中。允许用户为内存缓存指定内存大小限制(这显然不应超过堆大小限制!)。客户端可以使用通过 RMI 连接的缓存服务。我需要知道每个对象的大小,以验证是否可以将新传入的对象放入内存。我在互联网上搜索,我得到了这个解决方案来获取大小:

  public long getObjectSize(Object o){
    try {

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(o);
        oos.close();

        return bos.size();      
    } catch (Exception e) {
        return Long.MAX_VALUE;
    }
} 

这个解决方案效果很好。但是,在内存使用方面并不能解决我的问题。 :( 如果多个客户端同时验证对象大小,这会导致堆栈溢出,对吧?嗯……有人会说:为什么你没有得到特定的对象大小并将其存储在内存中,而另一个对象是需要放入内存检查对象大小?这是不可能的,因为对象的大小是可变的。:(

有人可以帮助我吗?我正在考虑从 RMI 通信中获取套接字,但我不知道该怎么做......

【问题讨论】:

    标签: java stream size inputstream rmi


    【解决方案1】:

    您可以使用自定义 FilterOutputStream 解决限制序列化对象大小的问题:

    1. 计算write 方法调用写入的字节数,并
    2. 当计数超过您的限制时,引发自定义 IOException 子类。

    然后将此过滤器放在ByteArrayOutputStreamObjectOutputStream 之间。

    这就是代码的样子(未经测试!):

        public LimitExceededException extends IOException { ... }
    
        public class LimitingOutputStream extends FilterOutputStream {
            private int limit;
            private int count;
    
            public LimitingOutputStream(OutputStream out, int limit) {
                super(out);
                this.limit = limit;
            }
    
            @Override
            public void write(byte b) throws IOException {
                if (count++ > limit) {
                    throw LimitExceededException(...);
                }
                super.write(b);
            }
    
            @Override
            // (This override is not strictly necessary, but it makes it faster)
            public void write(byte[] bytes, int from, int size) throws IOException {
                if (count += size > limit) {
                    throw LimitExceededException(...);
                }
                super.write(bytes, from, size);
            }
        }
    
        /**
         * Return the serialization of `o` in a byte array, provided that it is
         * less than `limit` bytes.  If it is too big, return `null`.
         */
        public byte[] serializeWithLimit(Object o, int limit) {
            try {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                LimitingOutputStream los = new LimitingOutputStream(bos, limit);
                ObjectOutputStream oos = new ObjectOutputStream(los);
                oos.writeObject(o);
                oos.close();
                return bos.toByteArray(); 
            } catch (LimitExceededException e) {
                return null;
            }
        }
    

    是的,当超出限制时,它使用异常来“退出”,但这是 IMO 对异常的良好使用。我挑战任何不同意这一点的人提出更好的解决方案。把它放在另一个答案中。


    顺便说一句,这是非常糟糕的代码:

    } catch (Exception e) {
        return Long.MAX_VALUE;
    }
    

    除了您可能期望被抛出的IOExceptions 之外,您还捕获了各种未经检查的异常,其中大部分是由错误引起的……您需要了解:

    1. 捕捉Exception 是不好的做法,除非您尝试进行最后的诊断。

    2. 每当您捕获到意外异常时,请务必记录它们以便可以记录堆栈跟踪(取决于记录器配置)。或者,如果您的应用程序不使用日志框架,则让它调用 e.printStackTrace()

    (如果你不想在生产代码中这样做,也不要在 StackOverflow 问题中这样做......'因为一些复制粘贴编码器可能只是复制它。)

    【讨论】:

    • 仅就日志记录而言,使用未捕获的异常是一种不好的做法,还是对性能有一些影响?
    • @GreyBeardedGeek - “...这也意味着您可能也不应该从字节计数 FilterOutputStream 中抛出一个。”随意提出一个更好的解决方案。序列化大于用户限制的对象是异常情况
    • @StephenC - 我认为试图违反限制的用户并不罕见,因此应由应用程序处理。计算字节数的方法应该做到这一点 - 计算字节数,并返回大小。一个单独的方法应该决定这是否违反了某些限制,并返回一个布尔值。应该为真正出乎意料的事情保留一个例外。
    • 异常警察再次罢工。你开始怀疑是否有任何地方可以使用异常。 @GreyBeardedGeek 如果这很常见,为什么 OP 试图阻止它?
    • @GreyBeardedGeek - 我已将“完整”实现放入答案中。 我挑战你也这样做(在另一个答案中)来证明你的断言,即没有例外的解决方案更好。请记住,解决方案必须满足规定的要求,即如果序列化对象大于限制,它不会填满堆。
    【解决方案2】:

    这是我对在不使用异常的情况下实现所请求功能的挑战的回应。这是对@StephenC 上一个答案的相对较小的修改,除了这个实际上可以编译和工作。

     import java.io.*;
    
     public class LimitingOutputStream extends FilterOutputStream {
         private int limit;
         private int count;
         private boolean limitExceeded = false;
    
         public LimitingOutputStream(OutputStream out, int limit) {
             super(out);
             this.limit = limit;
         }
    
         public boolean isLimitExceeded() {
             return limitExceeded;
         }
    
         @Override
         public void write(int b) throws IOException {
    
             if(!limitExceeded) {
                if(count++ > limit) {
                    limitExceeded = true;
                } else {
                    super.write(b);
                }
             }
         }
    
         @Override
         // (This override is not strictly necessary, but it makes it faster)
         public void write(byte[] bytes, int from, int size) throws IOException {
             if(!limitExceeded) {
                if ( (count += size) > limit) {
                    limitExceeded = true;
                } else {
                    super.write(bytes, from, size);
                }
            }
        }
    
        /**
         * Return the serialization of `o` in a byte array, provided that it is
         * less than `limit` bytes.  If it is too big, return `null`.
         */
        public static byte[] serializeWithLimit(Object o, int limit) throws IOException {
             ByteArrayOutputStream bos = new ByteArrayOutputStream();
             LimitingOutputStream los = new LimitingOutputStream(bos, limit);
             ObjectOutputStream oos = new ObjectOutputStream(los);
             oos.writeObject(o);
             oos.close();
             return los.isLimitExceeded() ? null : bos.toByteArray(); 
        }
    
     }
    

    【讨论】:

    • 感谢您的解决方案。
    • 哦,太好了。我们没有抛出异常说我们失败了,而是允许下一个级别继续写,写,写。你认真地认为这比抛出异常更好吗?
    猜你喜欢
    • 1970-01-01
    • 2017-09-18
    • 1970-01-01
    • 2015-02-18
    • 2020-05-26
    • 2017-12-18
    • 2011-03-22
    • 2012-03-05
    相关资源
    最近更新 更多