最近在處理一些圖的數據,主要是有向圖,如果圖的節點不是特別大可以直接加載到內存里來處理,但是當圖的節點個數特別大時,內存就放不下了;我 們牽涉到的圖的節點數最大可以達到數億個節點,已經超出的機器內存的大小,所以必須把這些圖的數據放到外存上,所以我們就選擇了圖數據庫。
嘗試了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 }
就先介紹這些基本的操作吧,以后用到新的操作了在做補充!
參考鏈接入下: