最近在处理一些图的数据,主要是有向图,如果图的节点不是特别大可以直接加载到内存里来处理,但是当图的节点个数特别大时,内存就放不下了;我 们牵涉到的图的节点数最大可以达到数亿个节点,已经超出的机器内存的大小,所以必须把这些图的数据放到外存上,所以我们就选择了图数据库。
尝试了2种图数据库,IBM System G 和 neo4j, 这两个数据库都可以处理上亿个节点的图,起始使用的是System G,但是存在一些问题,当图的节点数在300多万个,边数为1000多万个时,在创建图时就特别麻烦,程序老是创建不成功。后来就选择了 neo4j,neo4j是一个开源的图数据库,使用起来也比较方便,在创建比较大的图时速度远远超过System G;接下来把neo4j入门的知识记录下来,主要介绍neo4j嵌入在java开发中。
1、创建图(把图的数据存入neo4j)
创建图由两种方法,一种是直接通过读取文件,在程序中显式的创建节点和边,另一种是通过加载CSV文件来创建。
1.1 程序中显示的创建图
存放图的文件的格式如下图,以'v'开图的是顶点,后面的数字是它的id,id用从0开始顺序存放,在后面是label; 以'e'开头的行是边,后面第一个数字是边的起始点的id,第二个数字是边的终点的id,后面的字符串是边的label。
创建图的方法如下:
1 public static void create_graph(GraphDatabaseService graph, File f) throws FileNotFoundException{ 2 Scanner scanner = new Scanner(f); 3 4 while (scanner.hasNextLine()){ 5 String line = scanner.nextLine().trim(); 6 7 if (line.equals("") | line.startsWith("t")){ 8 continue; 9 } else if (line.startsWith("v")) { 10 String nodeLabel = line.split(" ")[2]; //得到顶点的label 11 Label label = DynamicLabel.label(nodeLabel); //通过顶点的label,创建一个neo4j的Label类型,作为顶点的label, 这样就不用把label作为属性 12 try (Transaction tx = graph.beginTx()){ 13 graph.createNode(label); //创建顶点 14 tx.success(); 15 } 16 } else if (line.startsWith("e")) { 17 String[] lineSplit = line.split(" "); 18 int sourceId = Integer.parseInt(lineSplit[1]); //得到变得起始顶点id和终止顶点id 19 int targetId = Integer.parseInt(lineSplit[2]); 20 String edgeLabel = lineSplit[3]; //得到边的label 21 try (Transaction tx = graph.beginTx()){ 22 Relationship edge = graph.getNodeById(sourceId).createRelationshipTo(graph.getNodeById(targetId), R.DIRECTED); //创建边 23 edge.setProperty("label", edgeLabel); //给边设置属性 24 tx.success(); 25 } 26 } 27 } 28 29 scanner.close(); 30 }
1.2 通过加载CSV文件来创建图
如果使用CSV文件的话,需要通过URL来访问文件,我们使用两个URL,一个是顶点的URL,一个是边的URL,它们的文件格式要符合csv文件的格式.
可以创建一个本地的apache服务器来存放这些文件,我们使用的顶点和边的url分别是:
顶点url: http://127.0.0.1/nodes
边url: http://127.0.0.1/edges
然后存取顶点的代码如下:
1 String create_node = "USING PERIODIC COMMIT " 2 + "LOAD CSV WITH HEADERS FROM 'http://127.0.0.1/nodes' AS line " 3 + "CREATE (:node {label: line.label});"; //这样创建时,不能像上一种方法那样通过变量来指定label, 所以把label作为了顶点的属性了,第一个冒号前面可以指定顶点的名字,也可以不指定,冒号后面是该顶点的label. 4 graph.execute(create_node); //执行cypher语言来创建结点
其中"USING PERIODIC COMMIT"的作用是分段式的创建顶点,可以认为指定读取多少行后就写入数据库,默认是读取1000行后写入数据库,例如"USING PERIODIC COMMIT 500",就是读取500行后就存入数据库.
存放边的代码如下:
1 String create_edge = "USING PERIODIC COMMIT " 2 + "LOAD CSV WITH HEADERS FROM 'http://127.0.0.1/edges' AS line " 3 + "MATCH (p1), (p2) " //找到边的两个顶点 4 + "WHERE id(p1)=toInt(line.source) and id(p2)=toInt(line.target) " 5 + "CREATE (p1)-[:DIRECTED {label: line.label}]->(p2);"; //创建边 6 graph.execute(create_edge);
其中,需要注意的是带有"USING PERIODIC COMMIT "的语句不能放在Transaction中执行,否则会出现如下的错误
“org.neo4j.cypher.PeriodicCommitInOpenTransactionException: Executing queries that use periodic commit in an open transaction is not possible.”
完整的创建顶点和边的方法如下:
1 public static void create_nodes(GraphDatabaseService graph, String node_url) { //创建顶点 2 String create_node = "USING PERIODIC COMMIT " 3 + "LOAD CSV WITH HEADERS FROM " + node_url + "AS line " 4 + "CREATE (:node {label: line.label});"; 5 graph.execute(create_node); 6 System.out.println("nodes create successfully!"); 7 } 8 //创建边 9 public static void create_edges(GraphDatabaseService graph, String edge_url){ 10 11 String create_edge = "USING PERIODIC COMMIT " 12 + "LOAD CSV WITH HEADERS FROM " + edge_url + " AS line " 13 + "MATCH (p1), (p2) " 14 + "WHERE id(p1)=toInt(line.source) and id(p2)=toInt(line.target) " 15 + "CREATE (p1)-[:DIRECTED {label: line.label}]->(p2);"; 16 graph.execute(create_edge); 17 18 System.out.println("edges create successfully!"); 19 }
2.得到一个顶点的所有出边的终点的id
1 public static ArrayList<Long> get_out_nodes(GraphDatabaseService graph, Node node){ 2 ArrayList<Long> out = new ArrayList<Long>(); 3 try (Transaction tx = graph.beginTx()){ 4 Traverser tr; 5 TraversalDescription td = graph.traversalDescription() 6 .breadthFirst() 7 .relationships(R.DIRECTED, Direction.OUTGOING) 8 .evaluator(Evaluators.excludeStartPosition()); 9 tr = td.traverse(node); 10 for (Path path : tr){ 11 if (path.length() == 1){ 12 out.add(path.endNode().getId()); 13 } 14 } 15 tx.success(); 16 } 17 return out; 18 }
3.得到一个顶点的所有入边的起始点的id
1 public static ArrayList<Long> get_in_nodes(GraphDatabaseService graph, Node node){ 2 ArrayList<Long> in = new ArrayList<Long>(); 3 try (Transaction tx = graph.beginTx()){ 4 Traverser tr; 5 TraversalDescription td = graph.traversalDescription() 6 .breadthFirst() 7 .relationships(R.DIRECTED, Direction.INCOMING) 8 .evaluator(Evaluators.excludeStartPosition()); 9 tr = td.traverse(node); 10 for (Path path : tr){ 11 if (path.length() == 1){ 12 in.add(path.endNode().getId()); 13 } 14 } 15 tx.success(); 16 } 17 return in; 18 }
4.得到图中所有顶点的个数
1 public static int getSize(GraphDatabaseService graph){ 2 int size = 0; 3 try (Transaction tx = graph.beginTx()){ 4 Iterator<Node> it = graph.getAllNodes().iterator(); 5 while(it.hasNext()){ 6 size++; 7 it.next(); 8 } 9 tx.success(); 10 } 11 return size; 12 }
5.根据顶点的属性label的值,得到具有相同label值的顶点的个数
1 public static int getSizeByLabel(GraphDatabaseService graph, String label){ 2 try(Transaction tx = graph.beginTx()){ 3 Label node = DynamicLabel.label("node"); //在创建顶点时,指定了顶点的label为"node",注意这个label是Label类型的,与顶点属性的label不一样 4 ResourceIterator<Node> result = graph.findNodes(node, "label", label); 5 ArrayList<Node> nodes = new ArrayList<>(); 6 while (result.hasNext()){ 7 nodes.add(result.next()); 8 } 9 tx.success(); 10 return nodes.size(); 11 } 12 }
6. 给出顶点的id,得到该顶点某个属性的值,如label属性的值
1 public static String getNodeLabel(GraphDatabaseService graph, int id){ 2 try(Transaction tx = graph.beginTx()){ 3 String nodeLabel = graph.getNodeById(id).getProperties("label").toString(); //返回的值的样式如下:{label=AND2X1} 4 String label = nodeLabel.substring(7, nodeLabel.length()-1); //对上一步的返回值进行取子串 5 tx.success(); 6 return label; 7 } 8 }
就先介绍这些基本的操作吧,以后用到新的操作了在做补充!
参考链接入下: