Search code examples
javasocketslibgdx

How to use Input/OutputStream to read/write data without creating new objects LibGDX


I'm using LibGDX and since I've started using this library, I've followed their advices and never created new objects at runtime using Pools and member variables to prevent triggering the garbage collector. However, when it comes to networking, I'm having difficulties to read from an InputStream (and writing from an OutputStream) without creating new Data(Input/Output)Stream objects.

I need a way to make those objects reusable to prevent creating a new object everytime I receive a player movement for instance. Or, I need a way to read ints, floats and UTF strings from an Input/OutputStream without using this object.

For now, my unefficient code looks like this:

/**
 * Represents a packet sent from client to server
 * Used when a client sends a chat message to the server
 * Created by winter on 25/03/16.
 */
public class PacketInChat extends Packet
{
    private String message;

    public PacketInChat() { }

    public PacketInChat(String message) {
        this.message = message;
    }

    @Override
    public void readFrom(InputStream stream) throws IOException {
        message = new DataInputStream(stream).readUTF(); //problem here
    }

    @Override
    public void writeTo(OutputStream stream) throws IOException {
        new DataOutputStream(stream).writeUTF(message); //problem here
    }

    //getters/setters for fields
}

I'm also detecting which socket it is by reading it's name so it's also problematic here:

String packetName = new DataInputStream(socket.getInputStream()).readUTF();
Packet packet = Pools.obtain((Class<? extends Packet>)Class.forName("package me.winter.socialplatformer.server.packet." + packetName));
packet.readFrom(socket.getInputStream());

Any ideas ? Thanks

Edit: EJP pointed out I can't read a String from a DataInputStream without creating a new object so my example is pretty much useless. I managed to read integers and floating points from bytes without DataInputStream and I could read Strings with StringBuffer (or StringBuilder) but the only important case in which I have to do this kind of optimization is in the packet for Player movement, which does not contain any String. I also finished by getting the packet type from an enum by reading the id instead of the name, so no more problem for that.

However, I'm still curious about how to reuse DataInputStream/DataOutputStream and will accept an answer that can explain me how to do so.


Solution

  • As per my comment, just use a WeakHashMap<InputStream, DataInputStream>, and similarly for the output streams:

    public class PacketInChat extends Packet
    {
        private Map<InputStream, DataInputStream> map = new WeakHashMap<>();
        private String message;
    
        public PacketInChat() { }
    
        public PacketInChat(String message) {
            this.message = message;
        }
    
        @Override
        public void readFrom(InputStream stream) throws IOException {
            DataInputStream din;
            synchronized (map)
            {
                if ((din = map.get(stream)) == null)
                {
                    map.put(din, in = new DataInputStream(stream));
                }
            }
            message = din.readUTF();
        }
    
    }
    

    and similarly for output. The WeakHashMap will ensure the data streams get released when their wrapped streams disappear.

    Or just restructure:

    public class PacketInChat
    {
        DataInputStream din;
        DataOutputStream dout;
    
        public PacketInChat(InputStream in, OutputStream out)
        {
            this.din = new DataInputStream(in);
            this.dout = new DataOutputStream(out);
        }
    
        public String readMessage() throws IOException { return din.readUTF(); }
        public int readInt() throws IOException { return din.readInt(); }
        // etc
        public void writeMessage(String msg) throws IOException
        {
            dout.writeUTF(msg);
        }
        // etc
    }
    

    so that you create one of these per input/output stream pair, and keep them in a map somewhere.