記一次用Java Stream Api的經歷
阿新 • • 發佈:2019-02-15
最近有個專案需要用到推薦系統,弄了個簡單的相似度推薦演算法。
資料為:
化簡為:
public class Worker { /** * 使用者編號 */ private long userId; /** * 期望城市 */ private String expectedCity; /** * 現在狀態 */ private int status; /** * 最高學歷 */ private String education; /** * 工作經驗 */ private int experience; /** * 星座 */ private String constellation; /** * 年齡 */ private int age; /** * 籍貫 */ private String nativePlace; /** * 自我介紹 */ private String introduction; /** * 所在地區 */ private String location; /**省略get() set()**/ }
計算策略是:
1、數值越接近,值越大
2、數值相同,返回1,否則返回0
3、如果是與字串有關的,例如(做飯做衛生,輔帶寶寶,做飯好吃,做事麻利,為人乾淨利落 ,形象好。易溝通。有育嬰師證。),則計算餘弦距離,在這裡沒有做分詞,因此將此內容的比重下降
演算法如下:
public class ScoreCos { /** * 不分詞 純字串計算 * @param text1 * @param text2 * @return */ public static double similarScoreCos(String text1, String text2){ if(text1 == null || text2 == null){ //只要有一個文字為null,規定相似度分值為0,表示完全不相等 return 0.0; }else if("".equals(text1)&&"".equals(text2)) return 1.0; Set<Integer> ASII=new TreeSet<>(); Map<Integer, Integer> text1Map=new HashMap<>(); Map<Integer, Integer> text2Map=new HashMap<>(); for(int i=0;i<text1.length();i++){ Integer temp1=new Integer(text1.charAt(i)); if(text1Map.get(temp1)==null) text1Map.put(temp1,1); else text1Map.put(temp1,text1Map.get(temp1)+1); ASII.add(temp1); } for(int j=0;j<text2.length();j++){ Integer temp2=new Integer(text2.charAt(j)); if(text2Map.get(temp2)==null) text2Map.put(temp2,1); else text2Map.put(temp2,text2Map.get(temp2)+1); ASII.add(temp2); } double xy=0.0; double x=0.0; double y=0.0; //計算 for (Integer it : ASII) { Integer t1=text1Map.get(it)==null?0:text1Map.get(it); Integer t2=text2Map.get(it)==null?0:text2Map.get(it); xy+=t1*t2; x+=Math.pow(t1, 2); y+=Math.pow(t2, 2); } if(x==0.0||y==0.0) return 0.0; return xy/Math.sqrt(x*y); } /** * 相同返回1,不同返回0 * @param o1 * @param o2 * @return */ public static double equal(Object o1,Object o2) { return (o1!=null && o2!=null)&&o1.equals(o2)?1:0; } /** * 值約接近,返回值越接近1 * 演算法為 1-(大-小)/(最大-最小) * @param o1 * @param o2 * @return */ public static double similarByNumber(int o1, int o2, int max) { return 1-Math.abs(o1-o2)/max; } }
演算法大致如下:
1、先從excel獲取資料
2、用兩個for迴圈計算物品間的相似度
3、排序後取前10個最大的
4、儲存資料
第一次跑,以工作人員的自我介紹作為相似度判斷依據
//資料結構 //結果 Map<Long,List<Node>> map = new HashMap<>(); //結果的每一行 Map<Long, Double> row = new HashMap<>(); //檔案內容 Map<Long, String> content = new HashMap<>(); //讀取檔案 File file = new File("d:/data7.xls"); InputStream inputStream = new FileInputStream(file); Workbook workbook = ExcelUtil.getWorkbok(inputStream,file); Sheet sheet = workbook.getSheetAt(0); //跳過第一個 for (int i = 1; i < sheet.getLastRowNum(); i++) { Row r = sheet.getRow(i); Cell id = r.getCell(9); Cell cont = r.getCell(5); content.put(Long.valueOf(id.getStringCellValue()), cont.getStringCellValue()); } // System.out.println(content); //兩個for迴圈計算相似度,取前10個 for (Map.Entry<Long,String> c1:content.entrySet()) { Map<Long, Double> m = new HashMap<>(); for (Map.Entry<Long,String> c2:content.entrySet()) { if(c1.getKey().equals(c2.getKey())) continue; double r = ScoreCos.similarScoreCos(c1.getValue(), c2.getValue()); m.put(c2.getKey(), r); } List<Map.Entry<Long,Double>> list = new ArrayList<Map.Entry<Long,Double>>(m.entrySet()); Collections.sort(list,new MyComparator()); List<Node> nodeList = new ArrayList<>(); for(int i = 0; i< 10 && i < list.size(); i++){ Map.Entry<Long, Double> entry = list.get(i); nodeList.add(new Node(entry.getKey(), entry.getValue())); } map.put(c1.getKey(), nodeList); log.info("key:{},value:{}",c1.getKey(),nodeList); } //儲存為檔案 save(map);
結果跑了4個小時左右,資料大概有30000個。
推測大概有如下原因:
1、單執行緒
2、只要取前10個,用不著全排序
將單執行緒變成多執行緒有多種方法。其中較為簡便的可以用Java1.8提供的並行流處理(parallelStream)
同時,從多個方面進行判斷
/**
* 值越接近1表示越接近
* @param o
* @return
*/
public double distinct(Worker o){
double dis = 0;
dis += (3d / 16) * equal(this.expectedCity, o.expectedCity);
dis += (1d / 16) * equal(this.status, o.status) ;
dis += (2d / 16) * similarByNumber(this.experience,o.experience,496);
dis += (2d / 16) * equal(this.education, o.education);
dis += (2d / 16) * equal(this.constellation, o.constellation);
dis += (2d / 16) * similarByNumber(this.age,o.age,40);
dis += (2d / 16) * equal(this.nativePlace, o.nativePlace);
dis += (1d / 16) * similarScoreCos(this.introduction, o.introduction);
dis += (1d / 16) * similarScoreCos(this.location, o.location);
return dis;
}
改進後:
/**
* 流處理
* @throws IOException
*/
private static void useStreamApi() throws IOException {
List<Worker> data = getFromDB();
Map<Long, List<Node>> map = new ConcurrentHashMap<>();
AtomicInteger integer = new AtomicInteger();
//併發執行
data.parallelStream().forEach(x->{
//相當於兩個for迴圈
List<Node> nodes = data.stream()
//如果userId相同,則置為0
.map(y -> new Node(y.getUserId(), x.getUserId()==y.getUserId()?0:x.distinct(y)))
//降序
.sorted(Comparator.reverseOrder())
//取前10個
.limit(10)
//.peek(System.out::println)
.collect(Collectors.toList());
map.put(x.getUserId(), nodes);
//每隔100個輸出一次
if(integer.getAndIncrement()%100==0)
log.info("key:{} value:{}",x.getUserId(),nodes);
});
save(map);
}
重新計算一遍後用了40分鐘左右便出來了,而且stream用的也很簡潔。
參考:
《寫給大忙人看的Java SE 8》第二章