序列化二叉树时的StackOverflowException

时间:2011-09-07 05:32:37

标签: java android serialization binary-tree

我正在尝试为Android应用程序实现二叉树,我希望能够将其序列化到设备上的文件中。不幸的是,我在尝试序列化时遇到了stackoverflow错误。

代码:

class BTree<V extends Model> implements Serializable {
/**
 * 
 */
private static final long serialVersionUID = 4944483811730762415L;

private V value;
private BTree<V> left;
private BTree<V> right;

public BTree(V value) {
    this.value = value;
}

public BTree(V value, BTree<V> left, BTree<V> right) {
    this.value = value;
    this.left = left;
    this.right = right;
}

private int getValue() {
    return this.value.hashCode();
}

public void insert(BTree<V> node) {     
    if (this.getValue() >= node.getValue()) {
        if (this.left == null) {
            this.left = node;
        } else {
            this.left.insert(node);
        }
    } else {
        if (this.right == null) {
            this.right = node;
        } else {
            this.right.insert(node);
        }
    }
}

public boolean containsKey(Object key) {
    return this.find(key) != null;
}

public V find(Object key) {
    if (key.hashCode() == this.getValue()) {
        return this.value;
    } else if (key.hashCode() > this.getValue() && this.left != null) {
        return this.left.find(key);
    } else if (key.hashCode() < this.getValue() && this.right != null) {
        return this.right.find(key);
    } else {
        return null;
    }
}

public ArrayList<V> getAllValues() {
    ArrayList<V> values = new ArrayList<V>();
    if (this.left != null) {
        values.addAll(this.left.getAllValues());
    }
    if (this.right != null) {
        values.addAll(this.right.getAllValues());
    }

    return values;
}
}

我正在尝试在一个单独的类中序列化这个文本块:

try {           
        FileOutputStream fos = this.context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(this.tree);
        oos.close();

        //this.lastModified = getLastModified(FILE_NAME);
    } catch (FileNotFoundException e) {
        //File will get created, so this doesn't matter
    } catch (IOException e) {
        Log.d("BTreeModel Serialization Error", "Error serialization model.");
        Log.e("Serialization error details", e.toString());
    } 

我注意到的是,如果我序列化到一个当前不存在的文件,那么序列化很好。第二次运行相同的程序时,再次序列化到磁盘时会导致StackOverflowException。

以下是logcat的输出:

09-07 05:29:42.011: ERROR/AndroidRuntime(916): FATAL EXCEPTION: main
09-07 05:29:42.011: ERROR/AndroidRuntime(916): java.lang.StackOverflowError
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at         java.util.IdentityHashMap.getModuloHash(IdentityHashMap.java:435)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.util.IdentityHashMap.findIndex(IdentityHashMap.java:419)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at     java.util.IdentityHashMap.get(IdentityHashMap.java:371)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.dumpCycle(ObjectOutputStream.java:471)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1739)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1689)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1653)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:1143)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:413)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1241)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1205)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1575)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1847)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1689)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1653)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:1143)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:413)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1241)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1575)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1847)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1689)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1653)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:1143)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:413)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1241)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1575)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1847)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1689)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1653)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:1143)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:413)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1241)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1575)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1847)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1689)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1653)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:1143)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:413)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1241)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1575)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1847)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1689)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1653)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:1143)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:413)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1241)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1575)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1847)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1689)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1653)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:1143)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:413)
09-07 05:29:42.011: ERROR/AndroidRuntime(916):     at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.j

从添加节点的活动:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    //TextView averageHashMapTime = (TextView)findViewById(R.id.averageHashCollectionTime);
    //averageHashMapTime.setText("Just a moment");
    //TextView averageBtreeTime = (TextView)findViewById(R.id.averageBTreeCollectionTime);
    //averageBtreeTime.setText("working");

    HashMapModelCollection<Integer, Content> hashCollection = new HashMapModelCollection<Integer, Content>(this);
    long totalTicks = 0;
    final int totalIterations = 16;

    BTreeModelCollection<Integer, Content> btreeCollection = new BTreeModelCollection<Integer, Content>(this);
    totalTicks = 0;
    for(int i = 0; i < totalIterations; i++) {
        Content test = new Content();
        test.setText(String.valueOf(i));
        Random r = new Random();
        int key = r.nextInt();
        Date start = new Date();
        btreeCollection.put(key, test);
        Date end = new Date();

        totalTicks += end.getTime() - start.getTime();
    }

    Log.d("Finished", "btree length: " + String.valueOf(totalTicks / totalIterations));
    Toast.makeText(this, "btree length: " + String.valueOf(totalTicks / totalIterations), Toast.LENGTH_LONG).show();
    //averageBtreeTime.setText(String.valueOf(totalTicks / totalIterations));

}

直接处理BTree对象的类:

public class BTreeModelCollection<K extends Serializable, V extends Model> implements IModelCollection<K, V>,
    Serializable {


/**
 * 
 */
private static final long serialVersionUID = -3969909157515987705L;

private static final String FILE_NAME = "testCollection-5";

private BTree<V> tree;
private Context context;

public BTreeModelCollection(Context context) {
    this.context = context;
}

@Override
public void clear() {
    // TODO Auto-generated method stub

}

@Override
public boolean containsKey(Object key) {
    return tree.containsKey(key);
}

@Override
public boolean containsValue(Object value) {
    // TODO Auto-generated method stub
    return false;
}

@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
    // TODO Auto-generated method stub
    return null;
}

@Override
public V get(Object key) {
    return getTree().find(key);
}

@Override
public boolean isEmpty() {
    // TODO Auto-generated method stub
    return false;
}

@Override
public Set<K> keySet() {
    // TODO Auto-generated method stub
    return null;
}

@Override
public void put(K key, V value) {
    value.setKey(key);
    getTree();
    if (this.tree != null) {
        this.tree.insert(new BTree<V>(value));
    } else {
        this.tree = new BTree<V>(value);
    }
    serialize();
}

@Override
public V remove(Object key) {
    // TODO Auto-generated method stub
    return null;
}

@Override
public int size() {
    // TODO Auto-generated method stub
    return 0;
}

@Override
public Collection<V> values() {
    return getTree().getAllValues();
}

@Override
public void putAll(Map<? extends K, ? extends V> map) {
    // TODO Auto-generated method stub

}

private BTree<V> getTree() {
    if (this.tree == null) {
        loadTree();
    }

    return tree;
}

@SuppressWarnings("unchecked")
private void loadTree() {
    try {
        FileInputStream fis = context.openFileInput(FILE_NAME);
        ObjectInputStream ois = new ObjectInputStream(fis);
        this.tree = (BTree<V>)ois.readObject();
        ois.close();
    } catch (FileNotFoundException e) {
        this.tree = null;
    } catch (StreamCorruptedException e) {
        e.printStackTrace();
    } catch (IOException e) {
        this.tree = null;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } 
}

private void serialize() {
    if (this.tree == null) {
        Log.w("Serialization problem", "No collection to serialize to disk");
        return;
    }

    try {           
        FileOutputStream fos = this.context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(this.tree);
        oos.close();

        //this.lastModified = getLastModified(FILE_NAME);
    } catch (FileNotFoundException e) {
        //File will get created, so this doesn't matter
    } catch (IOException e) {
        Log.d("BTreeModel Serialization Error", "Error serialization model.");
        Log.e("Serialization error details", e.toString());
    } 
}

}

Model类:

public abstract class Model implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = -5724152403115334006L;

private Serializable key;

void setKey(Serializable key) {
    this.key = key;
}

Serializable getKey() {
    return this.key;
}
}

3 个答案:

答案 0 :(得分:0)

当递归没有正确的退出条件时,通常会发生这种情况。

答案 1 :(得分:0)

我已经复制了你的bug。看起来Android没有很好地处理深度递归,因为我在桌面上尝试了相同的程序,它的工作正常,我正在尝试。

根据评论中的讨论,我做了不涉及递归到Object级别的替代序列化。肯定有不止一种方法可以做到这一点,但我选择将结构序列化为JSON数据格式的String并将其写入输出流。请注意,我没有彻底测试过。这个答案的目的是提供一种你可能也可以采取的方法。

现在假设我的值是int,我认为JSON结构应该如下:

 { 
       root : {
           "value" : 8
           "left": {
                   "value" : 3
                   "left" : {
                       "value" : 1
                   }
               },
           "right" : {
               "value" : 10
               "right" : {
                   "value": 14
               }
           }
       }
   }

现在遵循该结构,我有在BTree类中构建树的方法:


    private JSONObject buildJSONObject(BTree  root) throws JSONException {
        JSONObject baseJSON = new JSONObject();
        JSONObject rootElement = new JSONObject();
        rootElement.put("value", root.getValue());
        baseJSON.put("root", rootElement);
        buildJSONObject(root, rootElement);
        return baseJSON;
    }

    private void buildJSONObject(BTree currentNode, JSONObject jsonElement) throws JSONException {
        jsonElement.put("value", currentNode.getValue());

        if(currentNode.left != null) {
            JSONObject leftJSON = new JSONObject();
            jsonElement.put("left", leftJSON);
            leftJSON.put("value", currentNode.left.getValue());
            buildJSONObject(currentNode.left, leftJSON);
        } 

        if (currentNode.right != null ){
            JSONObject rightJSON = new JSONObject();
            jsonElement.put("right", rightJSON);
            rightJSON.put("value", currentNode.right.getValue());
            buildJSONObject(currentNode.right, rightJSON);
        }
    }

反过来读回来:


private void readJSONObject(String json) throws JSONException, IllegalAccessException, InstantiationException {
        JSONObject baseJSON = new JSONObject(json);
        JSONObject rootElement = baseJSON.getJSONObject("root");
        readJSONObject(this, rootElement);
    }

    private void readJSONObject(BTree btree, JSONObject jsonElement) throws JSONException, IllegalAccessException, InstantiationException {
        int nodeValue = jsonElement.getInt("value");
        btree.value.setKey(nodeValue);

        if(jsonElement.has("left")) {
            JSONObject leftJSON = jsonElement.getJSONObject("left");
            Model m = this.value.getClass().newInstance();
            this.left = new BTree((V) m); 
            readJSONObject(this.left, leftJSON);
        } 

        if (jsonElement.has("right")){
            JSONObject rightJSON = jsonElement.getJSONObject("right");
            V m = (V)this.value.getClass().newInstance();
            this.right = new BTree(m); 
            readJSONObject(this.right, rightJSON);
        }
    }

最后,我应用了自定义序列化:


 private void writeObject(ObjectOutputStream oos)
    throws IOException {
        try {
            oos.defaultWriteObject();
            JSONObject root = buildJSONObject(this);
            oos.writeObject(root.toString());
            oos.writeObject(this.value);
        } catch(Exception e) {
            // handle exception
            throw new RuntimeException(e);
        }
    }

    // assumes "static java.util.Date aDate;" declared
    private void readObject(ObjectInputStream ois)
    throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        String jsonString = (String)ois.readObject();
        this.value = (V)ois.readObject();

        try {
            readJSONObject(jsonString);
        } catch (Exception e) {
            // handle exception
            throw new RuntimeException(e);
        }
    }

答案 2 :(得分:0)

以下是解决StackOverflowException问题的修订类的代码。它确实有一个严重的缺点:对于不平衡的树,由于它必须反复调整内部数组的大小,所以效率极低。

class BTree<V extends Model> implements Serializable {  
/**
 * 
 */
private static final long serialVersionUID = -7602392759811243945L;

private static final int MINIMUM_CAPACITY = 15;

private Model[] values;
private int depth = 3;

public void insert(V newValue) {    
    int index = 0;
    while(true) {           
        ensureSpotExists(index);

        Model value = values[index];
        if (value == null) {                
            values[index] = newValue;
            return;
        } else if (newValue.getKey().hashCode() < value.getKey().hashCode()) {
            index = (2 * index) + 1;
        } else if (newValue.getKey().hashCode() > value.getKey().hashCode()) {
            index = (2 * index) + 2;
        } else {
            values[index] = newValue;
            return;
        }
    }
}

protected void ensureSpotExists(int index) {
    if (this.values == null) {
        this.values = new Model[MINIMUM_CAPACITY];
    } else if (this.values.length < index + 1) {
        Model[] temp = this.values;
        this.values = new Model[getSize(++depth)];
        for(int i = 0; i < temp.length; i++) {
            this.values[i] = temp[i];
        }
    }
}

protected static int getSize(int depth) {
    int size = 0;
    for(int i = 0; i <= depth; i++) {
        size += Math.pow(2, i);
    }

    return size;
}

public boolean containsKey(Object key) {
    return this.find(key) != null;
}

public V find(Object key) {
    return null;
}

void replace(Object key, V value) {
    return;
}

public List<Model> getAllValues() {
    return Arrays.asList(this.values);
}  
}