Synocommunity提供了非常丰富和有用的第三方应用,具体可以去该网站查看。
在中国由于众所周知的原因,访问和下载packages.synocommunity.com里面的内容非常慢。安装、更新应用一直无法正常访问。最简单的想法就是使用镜像网站,发现并没有,所以就通过Cloudflare Workers自己搭建了一个。
搭建过程非常简单,我找到了一个专门用来反向代理的GitHub项目,然后修改了配置就可以使用了。
你可以使用我搭建的 https://synocommunity-packages.tuzhihao.com
直接配置在你的群晖使用,参考下图:
之后你就可以在社群里面找到丰富的插件,按需下载:
当然你可以自己搭建一个,创建一个wokers,在线编辑,填入如下脚本内容。
// copy from https://raw.githubusercontent.com/aD4wn/Workers-Proxy/master/src/index.js
// Website you intended to retrieve for users.
const upstream = 'packages.synocommunity.com'
// Custom pathname for the upstream website.
const upstream_path = '/'
// Website you intended to retrieve for users using mobile devices.
const upstream_mobile = 'packages.synocommunity.com'
// Countries and regions where you wish to suspend your service.
const blocked_region = []
// IP addresses which you wish to block from using your service.
const blocked_ip_address = []
// Whether to use HTTPS protocol for upstream address.
const https = true
// Whether to disable cache.
const disable_cache = true
// Replace texts.
const replace_dict = {
'$upstream': '$custom_domain'
}
const replace_content_type = [
'text/html',
'application/json'
]
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request));
})
async function fetchAndApply(request) {
const region = (request.headers.get('cf-ipcountry') || "").toUpperCase();
const ip_address = request.headers.get('cf-connecting-ip') || "";
const user_agent = request.headers.get('user-agent') || "";
let response = null;
let url = new URL(request.url);
let url_hostname = url.hostname;
if (https == true) {
url.protocol = 'https:';
} else {
url.protocol = 'http:';
}
if (await device_status(user_agent)) {
var upstream_domain = upstream;
} else {
var upstream_domain = upstream_mobile;
}
url.host = upstream_domain;
if (url.pathname == '/') {
url.pathname = upstream_path;
} else {
url.pathname = upstream_path + url.pathname;
}
if (blocked_region.includes(region)) {
response = new Response('Access denied: WorkersProxy is not available in your region yet.', {
status: 403
});
} else if (blocked_ip_address.includes(ip_address)) {
response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', {
status: 403
});
} else {
let method = request.method;
let request_headers = request.headers;
let new_request_headers = new Headers(request_headers);
new_request_headers.set('Host', upstream_domain);
new_request_headers.set('Referer', url.protocol + '//' + url_hostname);
let original_response = await fetch(url.href, {
method: method,
headers: new_request_headers
})
let original_response_clone = original_response.clone();
let original_text = null;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
if (disable_cache) {
new_response_headers.set('Cache-Control', 'no-store');
}
new_response_headers.set('access-control-allow-origin', '*');
new_response_headers.set('access-control-allow-credentials', true);
new_response_headers.delete('content-security-policy');
new_response_headers.delete('content-security-policy-report-only');
new_response_headers.delete('clear-site-data');
if(new_response_headers.get("x-pjax-url")) {
new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname));
}
const content_type = new_response_headers.get('content-type');
if (check_replace_response_text(content_type)) {
console.log('replace');
original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
} else {
original_text = original_response_clone.body
}
response = new Response(original_text, {
status,
headers: new_response_headers
})
}
return response;
}
function check_replace_response_text(content_type) {
if(content_type == null){
return false;
}
return replace_content_type.some((v) => content_type.includes(v));
}
async function replace_response_text(response, upstream_domain, host_name) {
let text = await response.text()
var i, j;
for (i in replace_dict) {
j = replace_dict[i]
if (i == '$upstream') {
i = upstream_domain
} else if (i == '$custom_domain') {
i = host_name
}
if (j == '$upstream') {
j = upstream_domain
} else if (j == '$custom_domain') {
j = host_name
}
console.log(i, j);
let re = new RegExp(i, 'g')
text = text.replace(re, j);
}
return text;
}
async function device_status(user_agent_info) {
var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
var flag = true;
for (var v = 0; v < agents.length; v++) {
if (user_agent_info.indexOf(agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}
访问如下地址(host替换为你自己的):
https://synocommunity-packages.tuzhihao.com/?build=42962&language=enu&arch=apollolake
检查返回的json里面的link地址是否已经被替换,成功替换就代表搭建成功,恭喜🎉
// 这里可以设置bot白名单,写入你的bot id即可,防止滥用
const whitelist = [
"/bot5245255:",
"/bot5065435:"
];
const tg_host = "api.telegram.org";
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
function validate(path) {
for (var i = 0; i < whitelist.length; i++) {
if (path.startsWith(whitelist[i]) || path.startsWith("/file" + whitelist[i]))
return true;
}
return false;
}
async function handleRequest(request) {
var u = new URL(request.url);
u.host = tg_host;
if (!validate(u.pathname))
return new Response('Unauthorized', {
status: 403
});
var req = new Request(u, {
method: request.method,
headers: request.headers,
body: request.body
});
const result = await fetch(req);
return result;
}
最后强烈建议使用自己的域名,不要用workers的域名,国内被墙了。
]]>那就说一下运营商小号和阿里小号的特点:
小号都有一个可能很大的问题就是说没就没了,特别是阿里这种。电信之前还是一直可以申请使用的,现在应该是只服务老用户,新用户不让用了。阿里的放号也是一段时间一段时间的,根本不可靠。而且这种垄断行业,想想都不可靠。号码不可靠意味着你不能用他来绑定你认为非常重要的服务。
对比也有了,那就说一下我面临的问题,以及怎么解决:
对照上面运营商小号和阿里小号都不能很完美的解决问题,因此我搞了一个自己的小号方案。
我用了一个有实体卡的副卡,买了1个阿里小号来解决我的问题。
我把这两个卡归类为能给我打电话的卡和能给我发短信的卡。
阿里的小号用来做能给我打电话的卡,我用来接外卖和快递的电话。并且1年或者2年更新一次,外卖和物流不写真实姓名,最多只留到楼栋号。更新的代价也不大,就是几个常用的外卖、电商平台。
自建的副卡用来做各种APP注册,政府、银行行业的联系电话,我不需要接他们的电话,只需要及时接收到短信即可。骚扰短信无法避免,但是还好的是他不会打断你,电话是一个都没有了。而且这个电话是我自己在运营商实名认证过的,所以有一些认证的场景也能很好的做到。最重要的这是一个实体的卡,运营商没道理无缘无故停止服务。最差后面我也可以搞一个保号的套餐,一只开着就好。
这里就不多赘述了,可以买的时候买一个就行了,设置什么的都比较简单。
准备一个不用的Android手机,安装2个app
https://github.com/pppscn/SmsForwarder
https://github.com/telegram-sms/telegram-sms-china
第一个是一个短信转发的软件,可以对接各种通知渠道,我用的是bark,体验非常好。
第二个是一个tg的软件,比单纯的转发短信多了手机状态的监控,还可以用它来发短信
使用手机自带的4g网络,不连接wifi,即使家里断网也不用担心。tg无法国内直接访问,可以参考这篇文章,部署一个代理即可。
安装两个的原因也很简单,主备比较可靠,第一个送达快,第二个有更多功能。
然后就是注意设置开机自启,防止清后台,就可以了。
怎么传图片就不赘述了,我用的是uPic,你可以用你喜欢的工具
先注册,创建一个bucket
然后上传一张图(也可以配置使用你的工具上传,或者网页上传),然后记录如下信息
为什么使用wokers而不用cname的方式接入
先创建一个workers,然后写入如下脚本,只需要修改2个变量
'use strict';
const b2Domain = 'f004.backblazeb2.com'; // configure this as per instructions above
const b2Bucket = 'bucketName'; // configure this as per instructions above
const b2UrlPath = `/file/${b2Bucket}/`;
addEventListener('fetch', event => {
return event.respondWith(fileReq(event));
});
// define the file extensions we wish to add basic access control headers to
const corsFileTypes = ['png', 'jpg', 'gif', 'jpeg', 'webp'];
// backblaze returns some additional headers that are useful for debugging, but unnecessary in production. We can remove these to save some size
const removeHeaders = [
'x-bz-content-sha1',
'x-bz-file-id',
'x-bz-file-name',
'x-bz-info-src_last_modified_millis',
'X-Bz-Upload-Timestamp',
'x-bz-server-side-encryption',
'x-bz-client-unauthorized-to-read',
'Expires'
];
const expiration = 31536000; // override browser cache for images - 1 year
// define a function we can re-use to fix headers
const fixHeaders = function(url, status, headers){
let newHdrs = new Headers(headers);
// add basic cors headers for images
if(corsFileTypes.includes(url.pathname.split('.').pop())){
newHdrs.set('Access-Control-Allow-Origin', '*');
}
// override browser cache for files when 200
if(status === 200){
newHdrs.set('Cache-Control', "public, max-age=" + expiration);
}else{
// only cache other things for 5 minutes
newHdrs.set('Cache-Control', 'public, max-age=300');
}
// set ETag for efficient caching where possible
const ETag = newHdrs.get('x-bz-content-sha1') || newHdrs.get('x-bz-info-src_last_modified_millis') || newHdrs.get('x-bz-file-id');
if(ETag){
newHdrs.set('ETag', ETag);
}
// remove unnecessary headers
removeHeaders.forEach(header => {
newHdrs.delete(header);
});
return newHdrs;
};
async function fileReq(event){
const cache = caches.default; // Cloudflare edge caching
const url = new URL(event.request.url);
if(!url.pathname.startsWith(b2UrlPath)){
url.pathname = b2UrlPath + url.pathname;
// console.log(url.pathname);
}
url.host = b2Domain;
// console.log(url);
let response = await cache.match(url); // try to find match for this request in the edge cache
if(response){
// use cache found on Cloudflare edge. Set X-Worker-Cache header for helpful debug
let newHdrs = fixHeaders(url, response.status, response.headers);
newHdrs.set('X-Worker-Cache', "true");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHdrs
});
}
// no cache, fetch image, apply Cloudflare lossless compression
response = await fetch(url, {cf: {polish: "lossless"}});
let newHdrs = fixHeaders(url, response.status, response.headers);
if(response.status === 200){
response = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHdrs
});
}else{
response = new Response('File not found!', { status: 404 })
}
event.waitUntil(cache.put(url, response.clone()));
return response;
}
脚本参考
https://blog.meow.page/archives/free-personal-image-hosting-with-backblaze-b2-and-cloudflare-workers/
https://jross.me/free-personal-image-hosting-with-backblaze-b2-and-cloudflare-workers/
我在脚本上的修改是把b2Domain
改为b2的host,这样脚本更容易理解一些。
最后你可以使用自己的域名做一个route就可以使用自己的域名,cf workers的域名在国内墙的比较厉害...
家里的Apple TV 4k突然自己就挂掉了,应该不是硬件的问题,感觉是软件的问题,但是背后一个接口都没有,也没办法自己升级系统。
周末拿到Apple店里,给的理由大陆不卖,所以也不修,只能到香港修。
这就GG了...
因为我主要是看Youtube、Nas视频、emby视频、netflix,安卓盒子的性能都太弱了,只能看看流媒体。
而且Apple TV的infuse神器,Youtube今年也支持4k了,真是唯一选择。
好在快开发布会了,等下一代吧,所以只能先不买了。
然后就研究起了我的电视,因为之前是有爱奇艺、腾讯那些视频的。所以看看能不能先装一个youtube看看。
系统是三星自己的tizen系统,应用只能去自带的应用商店下载。那这没办法了,只能换区。
果然,就搜到了2篇文章,已经说的很详细了,我这里只是对我自己的电视操作做一个备份。
https://joveng.myds.me:8888/tv-unlock/
Service_Remote_Control_1.2.apk
在电视处于亮屏的状态下,打开「IR Service remote」依次按下「S1」-「S2」即可进入工程模式菜单
使用电视遥控器「方向键」将光标移动到「Option」选项,按一下「确定键」进入,然后使用手机拍照当前画面。进入之后可以看到「Local Set」选项是「CHI_DTV」,意味当前系统是国区。
将光标移动到「Local Set」选项并按下「确定键」进入,在弹出的「Local Set List」列表里移到你想要切换的地区代码,美区选择「AD_AU2」,港区选择「HKG_DTV」,之后按下遥控器「确定键」后会自动返回。
再次进入到「MRT Option」选项,进入「Language Set」选项并确保此处参数显示为「China」,返回并进入「Region」确保此处参数显示为「asia_dtv」,其余的参数按照之前拍的照片依次复原即可。「Production Option」和「Engineer Option」同理。
重启电视。
使用遥控器依次选择「设置」-「支持」-「自诊断」-「重置 Smart Hub」,输入 PIN 码「0000」后系统桌面会重启。
重启之后光标移动到「apps」会弹出「同意条款」页面,注意即便当前电视的系统语言为简体中文,但此页面应为英文界面。使用电视原厂遥控器在「同意条款」页面以此按下「静音」-「音量 +」-「频道 +」-「静音」按键,电视会弹出「Internet 服务位置设置」选项。选择香港。
最后同意协议,安装app就行了!
工作中需要在web服务对外之前,spring的bean初始化的时候加载数据到缓存中。
但是由于数据量过大,需要多线程加载。要求所有的缓存加载成功之后,这个bean才初始化成功,程序继续往下走。
商务合作的需求,需要显示的数据来自合作方和我们自己的数据库中。
之前是只显示我们数据库点数据,现在合作方提供了一个接口让我实时调用对方的接口,并把两部分的数据合并后返回给前端。
但是由于合作方的接口不是特别稳定,而且也不能保证高可用,所以可以考虑同时从我们和合作方取数据,设置超时时间,如果都返回了数据就合并给前端,如果对方未能返回数据,还是有我们自己的数据能显示给用户的。
综合上述两个场景,我看了CountDownLatch和CyclicBarrier,看说明觉得比较适合我的使用。
package countdownlatchtest;
import com.google.common.collect.Maps;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchService {
private CountDownLatch countDownLatch = new CountDownLatch(4);
/**
* 用来存储所有线程的运行结果
*/
private ConcurrentMap<String, String> resultMap = Maps.newConcurrentMap();
public CountDownLatch getCountDownLatch() {
return countDownLatch;
}
public ConcurrentMap<String, String> getResultMap() {
return resultMap;
}
}
package countdownlatchtest;
import org.apache.commons.lang3.RandomUtils;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
public class Worker implements Runnable {
private ConcurrentMap<String, String> map;
private CountDownLatch countDownLatch;
public Worker(ConcurrentMap<String, String> map, CountDownLatch countDownLatch) {
this.map = map;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// 这里写代码做某些事
System.out.println(Thread.currentThread().getName() + "\t开始了...");
final int sleep = RandomUtils.nextInt(5, 20);
try {
Thread.sleep(sleep * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 添加结果到map中
map.putIfAbsent(Thread.currentThread().getName(), String.valueOf(sleep));
// 告诉 countDownLatch ,当前线程完成了
System.out.println(Thread.currentThread().getName() + "\t结束了...");
countDownLatch.countDown();
}
}
package countdownlatchtest;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(6);
for (int i = 0; i < 3; i++) {
// 模拟多次执行任务
doTask(executorService);
}
}
private static void doTask(ExecutorService executorService) {
CountDownLatchService countDownLatchService = new CountDownLatchService();
for (int i = 0; i < 4; i++) {
Worker worker = new Worker(countDownLatchService.getResultMap(), countDownLatchService.getCountDownLatch());
executorService.submit(worker);
}
try {
// 阻塞在这里,等待其他线程执行完成
countDownLatchService.getCountDownLatch().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有线程执行成功");
System.out.println("打印结果如下:\n" + countDownLatchService.getResultMap());
}
}
package cyclicbarriertest;
import com.google.common.collect.Maps;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierService implements Runnable {
/**
* 表示当有4个线程执行完的时候,会调用第二个参数 barrierAction 的start()方法
*
* 所以本类实现 Runnable 接口,传入this
*/
private CyclicBarrier cyclicBarrier = new CyclicBarrier(4, this);
/**
* 用来存储所有线程的运行结果
*/
private ConcurrentMap<String, String> resultMap = Maps.newConcurrentMap();
@Override
public void run() {
System.out.println("所有线程执行成功");
System.out.println("打印结果如下:\n" + resultMap);
}
public ConcurrentMap<String, String> getResultMap() {
return resultMap;
}
public CyclicBarrier getCyclicBarrier() {
return cyclicBarrier;
}
}
package cyclicbarriertest;
import org.apache.commons.lang3.RandomUtils;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CyclicBarrier;
public class Worker implements Runnable {
private ConcurrentMap<String, String> map;
private CyclicBarrier cyclicBarrier;
public Worker(ConcurrentMap<String, String> map, CyclicBarrier cyclicBarrier) {
this.map = map;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
// 这里写代码做某些事
System.out.println(Thread.currentThread().getName() + "\t开始了...");
final int sleep = RandomUtils.nextInt(5, 20);
try {
Thread.sleep(sleep * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 添加结果到map中
map.putIfAbsent(Thread.currentThread().getName(), String.valueOf(sleep));
try {
// 告诉 cyclicBarrier ,当前线程完成了
System.out.println(Thread.currentThread().getName() + "\t结束了...");
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
package cyclicbarriertest;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(6);
for (int j = 0; j < 3; j++) {
// 模拟多次任务执行
doTask(executorService);
}
}
private static void doTask(ExecutorService executorService) {
CyclicBarrierService cyclicBarrierService = new CyclicBarrierService();
for (int i = 0; i < 4; i++) {
Worker worker = new Worker(cyclicBarrierService.getResultMap(), cyclicBarrierService.getCyclicBarrier());
executorService.submit(worker);
}
}
}
写了上面两个例子,后面发现场景一适合用CountDownLatch
,场景二适合用CyclicBarrier
。场景一我们基本上不存在有任务不能执行完的情况,基本上做到计数器不归0,即使服务启动了也没办法正常使用,场景二很多情况都是任务不能正常的执行完成。
public static void main(String[] args) {
User u = new User();
u.setName("methol");
add(u);
edit(u);
}
public static void add(User user){
System.out.println("save user【" + user.getName() + "】 to db");
}
public static void edit(User user){
System.out.println("edit user【" + user.getName() + "】, and save to db");
}
上面的方法虽然基本上实现了功能,但是不利于程序以后的扩展和复用,代码也不容易维护。
通过封装、继承、多态把程序的耦合度降低,使用设计模式可以使得程序更加灵活,后期容易修改,也更加易于复用。
有了如下的代码:
package mode.factory.simple;
import mode.factory.bean.User;
public interface Operation {
void operate(User user);
}
package mode.factory.simple;
import mode.factory.bean.User;
public class AddOperate implements Operation {
@Override
public void operate(User user) {
System.out.println("save user【" + user.getName() + "】 to db");
}
}
package mode.factory.simple;
import mode.factory.bean.User;
public class EditOperate implements Operation {
@Override
public void operate(User user) {
System.out.println("edit user【" + user.getName() + "】, and save to db");
}
}
public static void main(String[] args) {
User u = new User();
u.setName("methol");
// 不同的操作需要new不同的类操作
Operation addOperate = new AddOperate();
addOperate.operate(u);
Operation editOperate = new EditOperate();
editOperate.operate(u);
}
上述代码已经经过封装,并且也适合之后的代码扩展,但是还是有一个问题,如果后期想通过不同的参数(事先不知道是要执行edit还是add),那么就要通过条件判断要做的操作,这时简单工厂模式可能就是一个比较好的选择。
package mode.factory.simple;
public class OperateFactory {
public static Operation findOperation(String operation) {
switch (operation) {
case "add":
return new AddOperate();
case "edit":
return new EditOperate();
default:
return null;
}
}
}
通过工厂类来帮我们new一个对象,而不是直接在代码中需要的地方new一个对象,在同一个地方管理,后期的代码维护和扩展性都强了很多。
或者这时想增加一个delete方法,只需要继承Operation
类,写一个方法,然后在OperateFactory
增加一个case即可。
简单工厂模式,就是一个简单的创建产品(Operation
)的工具,可以是AddOperate
类,也可以是EditOperate
类,甚至他们之间没有任何关系也是没问题的。
工厂模式,他也是一个简单的创建产品(Operation
)的工具,但是他有一个区别是所有的产品(AddOperate
,EditOperate
)都是继承(实现)Operation
这个父类的,父类中有抽象的方法,之后都是调用同一个方法来做操作。
使用中,很少使用简单工厂模式,大多都是工厂模式,谈到工厂模式,基本上都是指的工厂模式。
public static void main(String[] args) {
User u = new User();
u.setName("methol");
// 不同的操作需要new不同的类操作
Operation addOperate = new AddOperate();
addOperate.operate(u);
Operation editOperate = new EditOperate();
editOperate.operate(u);
// 使用一个简单的工厂,让工厂来帮我们选择
OperateFactory.findOperation("add").operate(u);
OperateFactory.findOperation("edit").operate(u);
}
对Operation
做了一些小的修改,增加了一个方法
public interface Operation {
void operate(User user);
Operation createDefault(String operationType);
}
public class AddOperate implements Operation {
@Override
public void operate(User user) {
System.out.println("save user【" + user.getName() + "】 to db");
}
@Override
public Operation createDefault(String operationType) {
return OperateFactory.findOperation(operationType);
}
}
public class EditOperate implements Operation {
@Override
public void operate(User user) {
System.out.println("edit user【" + user.getName() + "】, and save to db");
}
@Override
public Operation createDefault(String operationType) {
return OperateFactory.findOperation(operationType);
}
}
工厂方法主要系是两个元素,产品(Operation
)和创建者(OperateFactory
)。
而工厂方法就是一个创建者这个类的一个方法而已,这个方法就是用来封装产品的创建。
和工厂模式的区别是,工厂模式的最大优点在于工厂类中包含了逻辑判断,根据(客户端)传入的参数动态实例化相关的类,那么对于客户端来说,他是不需要关心产品(Operation
)的,他只需要关心传入的参数。而工厂模式这时把这一动作延后,接口定义了一个用来创建对象的接口,让子类决定实例化哪个类,工厂方法使一个类的实例化过程延迟到其子类。
抽象工厂,顾名思义,就是对工厂进行抽象。
工厂方法和工厂模式都是对产品抽象封装,通过一个工厂,生产多种产品。
抽象工厂就是通过不同的工厂生产不同的产品。
还是上面的例子,OperateFactory
创建了新增和修改两种操作(产品),此时有一个新的需求来了,之前用的数据库使mysql,现在想改成oracle,此时就对OperateFactory进行抽象。
package mode.factory.abstracs;
import mode.factory.bean.User;
import mode.factory.simple.Operation;
public interface Factory {
void add(User user);
void edit(User user);
Operation createDefault(String operationType);
}
接口申明了,不管是申明工厂,都需要有add和edit这两种产品的生产。不同的接口只需要实现这个接口,做自己的操作即可。同时不同的工厂还可以实现自己独有的产品的生产。
package mode.factory.abstracs;
import mode.factory.bean.User;
import mode.factory.simple.Operation;
public class MysqlOperateFactory implements Factory {
@Override
public void add(User user) {
createDefault("add").operate(user);
}
@Override
public void edit(User user) {
createDefault("edit").operate(user);
}
@Override
public Operation createDefault(String operationType) {
return new MysqlOperate().findOperation(operationType);
}
}
package mode.factory.abstracs;
import mode.factory.bean.User;
import mode.factory.simple.Operation;
public class OracleOperateFactory implements Factory {
@Override
public void add(User user) {
createDefault("add").operate(user);
}
@Override
public void edit(User user) {
createDefault("edit").operate(user);
}
@Override
public Operation createDefault(String operationType) {
return new OracleOperate().findOperation(operationType);
}
}
这里也用了工厂方法的思想,抽象工厂的模式实现了工厂线的扩展。
布隆过滤器实际上是一个很长的二进制向量和一系列的随机映射函数。可用于检索一个元素是否在一个集合中。
布隆过滤器的核心实现是一个超大的位数组和几个哈希函数。假设数组的长度为m,哈希函数的个数为k。
如上图:假设集合里面有x,y,z,通过hash函数计算后的结果为a,b,c,那么w[a],w[b],w[c]都会表标记为1。假设现在有3个hash函数,如图3个不同颜色的线,分别计算出不同的结果,并标记为1。当判断某一个元素是否在一个集合的时候,就通过判断这三个hash的结果,如果都是1,说明该元素在这个集合中。如果有一个为0,说明该元素不在此集合中。因此,这也是存在误判的原因。
总的来说,bloom filter是以极低的的错误去换取空间和时间。
public interface BloomFilter<T> {
/**
* 添加一个数据到bloomfilter内
*
* @param element 要添加的元素
*/
void add(T element);
/**
* 判断该过滤器内是否存在元素
*
* @param element 被判断的元素
* @return 是否存在
*/
boolean contains(T element);
}
import java.util.BitSet;
public class SimpleBloomFilter implements BloomFilter<String> {
/**
* 存储的bit容量
*/
private static final int DEFAULT_SIZE = Integer.MAX_VALUE;
/**
* 存储标志位的bits
*/
private BitSet bits = new BitSet(DEFAULT_SIZE);
/**
* 不同hash函数的种子
*/
private static final int[] seeds = new int[]{113, 213, 3111, 397, 611, 532};
/**
* hash函数的数组
*/
private SimpleHash[] hashFunction = new SimpleHash[seeds.length];
public SimpleBloomFilter() {
// 默认初始化 hashFunction
for (int i = 0; i < seeds.length; i++) {
hashFunction[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
@Override
public void add(String element) {
for (SimpleHash function : hashFunction) {
final int hash = function.hash(element);
bits.set(hash, true);
}
}
@Override
public boolean contains(String element) {
for (SimpleHash function : hashFunction) {
if (!bits.get(function.hash(element))) {
return false;
}
}
return true;
}
private static class SimpleHash {
private int cap;
private int seed;
private SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
/**
* 简单的写一个hash算法
*/
public int hash(String value) {
int result = 0;
int len = value.length();
for (int i = 0; i < len; i++) {
result = seed * result + value.charAt(i) + value.hashCode();
}
// 把值的范围控制在cap内
return (cap - 1) & result;
}
}
}
主要是两个类,com.google.common.hash.BloomFilter
和com.google.common.hash.BloomFilterStrategies
。
/** The bit set of the BloomFilter (not necessarily power of 2!) */
private final BitArray bits;
/** Number of hashes per element */
private final int numHashFunctions;
/** The funnel to translate Ts to bytes */
private final Funnel<? super T> funnel;
/**
* The strategy we employ to map an element T to {@code numHashFunctions} bit indexes.
*/
private final Strategy strategy;
四个成员变量,BitArray
是BloomFilterStrategies
中的静态内部类。作用和上面的BitSet
类似。有基本的get和set方法,同时提供了一些其他常用的方法。
static final class BitArray {
final long[] data;
long bitCount;
BitArray(long bits) {
this(new long[Ints.checkedCast(LongMath.divide(bits, 64, RoundingMode.CEILING))]);
}
// Used by serialization
BitArray(long[] data) {
checkArgument(data.length > 0, "data length is zero!");
this.data = data;
long bitCount = 0;
for (long value : data) {
bitCount += Long.bitCount(value);
}
this.bitCount = bitCount;
}
/** Returns true if the bit changed value. */
boolean set(long index) {
if (!get(index)) {
data[(int) (index >>> 6)] |= (1L << index);
bitCount++;
return true;
}
return false;
}
boolean get(long index) {
return (data[(int) (index >>> 6)] & (1L << index)) != 0;
}
/** Number of bits */
long bitSize() {
return (long) data.length * Long.SIZE;
}
/** Number of set bits (1s) */
long bitCount() {
return bitCount;
}
BitArray copy() {
return new BitArray(data.clone());
}
/** Combines the two BitArrays using bitwise OR. */
void putAll(BitArray array) {
checkArgument(
data.length == array.data.length,
"BitArrays must be of equal length (%s != %s)",
data.length,
array.data.length);
bitCount = 0;
for (int i = 0; i < data.length; i++) {
data[i] |= array.data[i];
bitCount += Long.bitCount(data[i]);
}
}
@Override
public boolean equals(@Nullable Object o) {
if (o instanceof BitArray) {
BitArray bitArray = (BitArray) o;
return Arrays.equals(data, bitArray.data);
}
return false;
}
@Override
public int hashCode() {
return Arrays.hashCode(data);
}
}
numHashFunctions
是表示一个元素对应了几个hash函数,和上面hashFunction.length
是一个意思。
strategy
是把元素映射为hash值的策略,类似上面的hashFunction
。BloomFilter
定了了Strategy
接口。put
方法把numHashFunctions
个通过计算后的值放入bitArray中。mightContain
返回该过滤器是否可能含有元素T。ordinal
就是枚举类的ordinal()
。
interface Strategy extends java.io.Serializable {
/**
* Sets {@code numHashFunctions} bits of the given bit array, by hashing a user element.
*
* <p>Returns whether any bits changed as a result of this operation.
*/
<T> boolean put(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits);
/**
* Queries {@code numHashFunctions} bits of the given bit array, by hashing a user element;
* returns {@code true} if and only if all selected bits are set.
*/
<T> boolean mightContain(
T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits);
/**
* Identifier used to encode this strategy, when marshalled as part of a BloomFilter. Only
* values in the [-128, 127] range are valid for the compact serial form. Non-negative values
* are reserved for enums defined in BloomFilterStrategies; negative values are reserved for any
* custom, stateful strategy we may define (e.g. any kind of strategy that would depend on user
* input).
*/
int ordinal();
}
并在BloomFilterStrategies
中给出了两个实现的枚举。
MURMUR128_MITZ_32() {
@Override
public <T> boolean put(
T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits) {
long bitSize = bits.bitSize();
long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
int hash1 = (int) hash64;
int hash2 = (int) (hash64 >>> 32);
boolean bitsChanged = false;
for (int i = 1; i <= numHashFunctions; i++) {
int combinedHash = hash1 + (i * hash2);
// Flip all the bits if it's negative (guaranteed positive number)
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
bitsChanged |= bits.set(combinedHash % bitSize);
}
return bitsChanged;
}
@Override
public <T> boolean mightContain(
T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits) {
long bitSize = bits.bitSize();
long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
int hash1 = (int) hash64;
int hash2 = (int) (hash64 >>> 32);
for (int i = 1; i <= numHashFunctions; i++) {
int combinedHash = hash1 + (i * hash2);
// Flip all the bits if it's negative (guaranteed positive number)
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
if (!bits.get(combinedHash % bitSize)) {
return false;
}
}
return true;
}
},
/**
* This strategy uses all 128 bits of {@link Hashing#murmur3_128} when hashing. It looks different
* than the implementation in MURMUR128_MITZ_32 because we're avoiding the multiplication in the
* loop and doing a (much simpler) += hash2. We're also changing the index to a positive number by
* AND'ing with Long.MAX_VALUE instead of flipping the bits.
*/
MURMUR128_MITZ_64() {
@Override
public <T> boolean put(
T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits) {
long bitSize = bits.bitSize();
byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal();
long hash1 = lowerEight(bytes);
long hash2 = upperEight(bytes);
boolean bitsChanged = false;
long combinedHash = hash1;
for (int i = 0; i < numHashFunctions; i++) {
// Make the combined hash positive and indexable
bitsChanged |= bits.set((combinedHash & Long.MAX_VALUE) % bitSize);
combinedHash += hash2;
}
return bitsChanged;
}
@Override
public <T> boolean mightContain(
T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits) {
long bitSize = bits.bitSize();
byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal();
long hash1 = lowerEight(bytes);
long hash2 = upperEight(bytes);
long combinedHash = hash1;
for (int i = 0; i < numHashFunctions; i++) {
// Make the combined hash positive and indexable
if (!bits.get((combinedHash & Long.MAX_VALUE) % bitSize)) {
return false;
}
combinedHash += hash2;
}
return true;
}
private /* static */ long lowerEight(byte[] bytes) {
return Longs.fromBytes(
bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
}
private /* static */ long upperEight(byte[] bytes) {
return Longs.fromBytes(
bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]);
}
};
Guava就是选取了这两个hash算法中的一个,创建一个BloomFilter可以指定其中的一个算法,具体的算法逻辑可以参看解读BloomFilter算法
使用实例:
private static void bloomFilter() throws Exception {
final BloomFilter<String> dealIdBloomFilter = BloomFilter.create(new Funnel<String>() {
@Override
public void funnel(String from, PrimitiveSink into) {
into.putString(from, Charsets.UTF_8);
}
}, 3000000, 0.0000001d);
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File("C:\\Users\\Methol\\Desktop\\jiaokao-word.txt")), "utf-8"));
String line;
int i = 0;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
if (!dealIdBloomFilter.mightContain(line)) {
dealIdBloomFilter.put(line);
sb.append(line).append("\n");
i++;
}
if (i % 1000 == 0) {
FileUtils.write(new File("C:\\Users\\Methol\\Desktop\\bloomFilterDistinct.txt"),
sb.toString(), Charsets.UTF_8, true);
sb = new StringBuilder();
}
}
FileUtils.write(new File("C:\\Users\\Methol\\Desktop\\bloomFilterDistinct.txt"),
sb.toString(), Charsets.UTF_8, true);
}
同样对大数据量的内容进行滤重,当时还想到了用spark,不过这个真的有点大炮打小鸟的感觉,用起来代码是非常简单,但是单单就滤重这件事来说,还是BloomFilter好用。
800w行的句子,滤重后有200w,spark的用时比BloomFilter略多。
贴一段spark的代码:
private static void spark() {
System.setProperty("hadoop.home.dir", "D:\\server\\hadoop-common-2.2.0\\");
SparkConf conf = new SparkConf().setAppName("Text String Distinct").setMaster("local").set("spark.executor.memory", "1g");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> textFile = sc.textFile("C:\\Users\\Methol\\Desktop\\jiaokao-word.txt");
final JavaRDD<String> distinct = textFile.distinct();
final long count = distinct.count();
System.out.println(count);
distinct.coalesce(1).saveAsTextFile("C:\\Users\\Methol\\Desktop\\distinct");
}
]]>create database geo_test default charset = utf8mb4;
use geo_test;
create table t_dealer
(
id bigint not null auto_increment primary key,
dealer_name varchar(255) not null,
city_code varchar(45) null,
phone varchar(32) null,
lat decimal(10,7) null,
lon decimal(10,7) null,
create_time datetime null
)default charset = utf8mb4;
create index idx_name on t_dealer (dealer_name);
comment on table t_dealer is '基本信息表';
comment on column dealer_name is '@cname:名称';
comment on column address is '@cname:地址';
comment on column city_code is '@cname:城市编码';
comment on column phone is '@cname:电话';
comment on column lat is '@cname:纬度';
comment on column lon is '@cname:经度';
comment on column create_time is '@cname:创建时间';
# 添加geo点
alter table t_dealer
add pnt POINT NULL comment '经纬度的geo点';
# 从经纬度中补全数据
update t_dealer set pnt = point(lat,lon);
# 改为not null之后才能添加索引
alter table t_dealer MODIFY pnt POINT not null;
# 添加索引,这种索引要求字段not null
ALTER TABLE t_dealer ADD SPATIAL INDEX spatIdx(pnt);
# 几种空间对象比较方法
SET @g1 = GEOMFROMTEXT('Polygon((30.7108682140 114.0961681600,30.6890070000 114.5951950000,
30.2507470000 114.5767980000,30.2507470000 114.1030670000,30.7108682140 114.0961681600))');
SET @g2 = pointfromtext('Point(30.476421 114.403866)');
# 注意:比较的都是外包络几何类型对象
# 包含
select * from t_dealer where MBRCONTAINS(@g1,pnt);
SELECT MBRCONTAINS(@g1,@g2), MBRCONTAINS(@g2,@g1), MBRCONTAINS(@g1,@g1);
# 被包含
select * from t_dealer where MBRWITHIN(pnt,@g1);
SELECT MBRWITHIN(@g2,@g1),MBRWITHIN(@g1,@g2);
# 不相交
SELECT MBRDISJOINT(@g1,@g2);
# 相等
SELECT MBREQUAL(@g1,@g2);
# 相交
SELECT MBRINTERSECTS(@g1,@g2);
# 重叠
SELECT MBROVERLAPS(@g1,@g2);
# 相切
SELECT MBRTOUCHES(@g1,@g2);
# 距离 单位 m
select id,dealer_name,city_code,phone,lat,lon,
GLength(LineStringFromWKB(linestring(pnt,pointfromtext('Point(30.476421 114.403866)'))))*100*1000 as distance
from t_dealer
order by distance asc;
数据下载:
dealer_insert.zip
/home/methol/software/idea-IU-162.1121.32/bin/idea.sh
,使用vim编辑,在下面这段文字前加上这段话。
# ---------------------------------------------------------------------
# Run the IDE.
# ---------------------------------------------------------------------
加上:
XMODIFIERS="@im=fcitx"
export XMODIFIERS
如图:
Wine QQ的启动脚本是/opt/longene/qq/qq.sh
如图:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input id="upload" type='file' name="file" webkitdirectory>
<button id="button">按钮</button>
</body>
<script src="//libs.useso.com/js/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
function Map() {
/**
* 结构
* @param key
* @param value
*/
function Struct(key, value) {
this.key = key;
this.value = value;
}
/**
* 数据存放数组
*/
this.arr = [];
/**
* 增加数据
* @param key {String}
* @param value {Object}
*/
this.put = function (key, value) {
for (var i = 0; i < this.arr.length; i++) {
if (this.arr[i].key === key) {
this.arr[i].value = value;
return;
}
}
this.arr[this.arr.length] = new Struct(key, value);
};
/**
* 通过key获取数据
* @param key {String}
* @returns {Object}
*/
this.get = function (key) {
for (var i = 0; i < this.arr.length; i++) {
if (this.arr[i].key === key) {
return this.arr[i].value;
}
}
return null;
};
/**
* 删除数据
* @param key{String}
*/
this.remove = function (key) {
var v;
for (var i = 0; i < this.arr.length; i++) {
v = this.arr[i];
if (v.key === key) {
this.arr.splice(i, 1);
return;
}
}
};
/**
* 是否存在key
* @param key {String}
* @returns {boolean}
*/
this.containsKey = function (key) {
var v;
for (var i = 0; i < this.arr.length; i++) {
v = this.arr[i];
if (v.key === key) {
return true;
}
}
return false;
};
/**
* 获取map数据量
* @returns {Number}
*/
this.size = function () {
return this.arr.length;
};
/**
* 判断Map是否为空
* @returns {boolean}
*/
this.isEmpty = function () {
return this.arr.length <= 0;
};
/**
* 全部清空
*/
this.removeAll = function () {
this.arr = [];
};
}
</script>
<script type="text/javascript">
var host = "http://example.com"; // 远程服务器地址
var map = new Map(); // 用来存放文件夹路径和id的键值对
var files = [];
$("#upload").change(function () {
files = this.files;
console.log(files);
});
$("#button").on("click", function () {
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.log(file);
var path = getFolder(file.webkitRelativePath);
var folderId = getFolderIdFromMap(path);
// 创建文件
uploadResource(file, folderId, 10001);
}
});
function uploadResource(file, folderId, projectId) {
var filepath = null;
var formData = new FormData();
formData.append("text", file);
$.ajax({
url: host + "/sys/upload/uploadProjectResource",
type: "POST",
data: formData,
xhrFields: {
withCredentials: true // 确保请求会携带上Cookie
},
enctype: 'multipart/form-data',
processData: false, // tell jQuery not to process the data
contentType: false,
complete: function (respResult) {
if (respResult.status == 200) {
filepath = respResult.responseText;
$.ajax({
url: host + "/project/resource/create",
type: "post",
dataType: "json",
xhrFields: {
withCredentials: true
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Content-Type", "application/json");
},
data: JSON.stringify({
"projectId": projectId,
"resourceContent": filepath,
"resourceName": file.name,
"folderId": folderId,
"type": 20 // 当前是文件资源
}),
success: function (data) {
console.log(data);
},
error: function (respResult) {
}
});
} else {
}
}
});
}
function getFolderIdFromMap(path) {
var folderId = map.get(path);
var folderName = path.substring(path.lastIndexOf("/") + 1, path.length);
var localFolder = 0;
if (path.indexOf("/") > 0) {
// 说明当前不是最高级目录,是子目录
localFolder = getFolderIdFromMap(path.substring(0, path.lastIndexOf("/")));
}
if (folderId == null) {
$.ajax({
async: false,
url: host + "/project/resource/create/folder",
type: "post",
dataType: "json",
xhrFields: {
withCredentials: true
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Content-Type", "application/json");
},
data: JSON.stringify({
"projectId": 10001,
"folderName": folderName,
"folderId": localFolder
}),
success: function (data) {
console.log(data);
folderId = data.id;
map.put(path, folderId);
},
error: function (respResult) {
}
});
}
return folderId;
}
function getFolder(path) {
console.log(path);
var index = path.lastIndexOf("/");
return path.substring(0, index);
}
</script>
</html>
服务器采用java,用了接受文件和创建文件夹。
文件夹也是虚拟的,采用在当前记录增加parentId来存储父节点,实现树的结构。
表结构:
CREATE TABLE t_project_resource_folder
(
id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键',
folder_name VARCHAR(255) DEFAULT '' COMMENT '文件夹名',
parent_id BIGINT(20) DEFAULT '0' NOT NULL COMMENT '父文件夹id,0表示顶级目录'
)
CREATE TABLE t_project_resource
(
id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键',
resource_name VARCHAR(255) NOT NULL COMMENT '资源名',
resource_content VARCHAR(255) COMMENT '资源内容,通常是放在oss文件的url',
resource_folder_id BIGINT(20) DEFAULT '0' COMMENT '资源文件夹ID,0表示文件处于顶级目录'
)
服务器端接口的代码:
@RequestMapping(value = "", method = RequestMethod.POST)
public ResponseEntity<String> upload(MultipartFile text) throws Exception {
String fileName = text.getOriginalFilename();
int lastDot = fileName.lastIndexOf(".");
String type = fileName.substring(lastDot + 1);
String sb = "uploads" + "/" + UUID.randomUUID().toString() + "." + type;
String uploadPath = storageProvider.upload(sb, text); // 调用service接口把文件存到oss中,返回文件在oss的地址
return ResponseEntity.ok(uploadPath);
}
]]>#!/bin/bash
cd ~
echo >shadowsocks_clients.txt
for ((i=9101; i<9105; i++)); do
echo ${i}: >>shadowsocks_clients.txt
lsof -i -n -P | egrep -c ":${i}.+ESTABLISHED" >>shadowsocks_clients.txt
lsof -i -n -P | egrep ":${i}.+ESTABLISHED" >>shadowsocks_clients.txt
echo >>shadowsocks_clients.txt
echo >>shadowsocks_clients.txt
done
cat shadowsocks_clients.txt
]]>当被问到要实现一个单例模式时,很多人的第一反应是写出如下的代码,包括教科书上也是这样教我们的。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(==synchronized==)。
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查instance == null
,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public static Singleton getSingleton() {
if (instance == null) { //Single Checked
synchronized (Singleton.class) {
if (instance == null) { //Double Checked
instance = new Singleton();
}
}
}
return instance ;
}
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()
这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
我们只需要将 instance 变量声明成 volatile 就可以了。
public class Singleton {
private volatile static Singleton instance; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。
相信你不会喜欢这种复杂又隐含问题的方式,当然我们有更好的实现线程安全的单例模式的办法。
这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。
public enum EasySingleton{
INSTANCE;
}
我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。
一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。上述所说都是线程安全的实现,文章开头给出的第一种方法不算正确的写法。
就我个人而言,一般情况下直接使用==饿汉式==就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用==静态内部类==,如果涉及到反序列化创建对象时会试着使用==枚举==的方式来实现单例。
转载:
]]>
package com.methol.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
//K-means算法实现
public class KMeans {
// 聚类的数目
public final static int ClassCount = 4;
// 样本数目(测试集)
public static int InstanceNumber = 100;
// 样本属性数目(测试)
public final static int FieldCount = 1;
// 设置异常点阈值参数(每一类初始的最小数目为InstanceNumber/ClassCount^t)
public final static double t = 2.0;
// 存放数据的矩阵
public static double[][] data;
// 每个类的均值中心
public static double[][] classData;
// 噪声集合索引
public static ArrayList<Integer> noises;
// 存放每次变换结果的矩阵
public static ArrayList<ArrayList<Integer>> result;
//存放每个属性的最大值
public static double[] classmax = new double[FieldCount];
// 构造函数,初始化
public KMeans() {
// 最后一位用来储存结果
data = new double[InstanceNumber][FieldCount + 1];
classData = new double[ClassCount][FieldCount];
result = new ArrayList<ArrayList<Integer>>(ClassCount);
noises = new ArrayList<Integer>();
}
/**
* 主函数入口 测试集的文件名称为“测试集.data”,其中有1000*57大小的数据 每一行为一个样本,有57个属性 主要分为两个步骤 1.读取数据
* 2.进行聚类 最后统计运行时间和消耗的内存
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
long startTime = System.currentTimeMillis();
KMeans cluster = new KMeans();
cluster.InstanceNumber = 100;
// 读取数据
//cluster.readData("D:/test.txt");
//随机产生数据
for (int i = 0; i < InstanceNumber; i++) {
data[i][0] = (double) Math.random();
data[i][0] = data[i][0] * 100;
System.out.println(data[i][0]);
}
// 聚类过程
cluster.cluster();
// 输出结果
cluster.printResult("clusterResult.data");
long endTime = System.currentTimeMillis();
System.out.println("Total Time:" + (endTime - startTime) + "ms");
System.out.println("Memory Consuming:"
+ (double) (Runtime.getRuntime().totalMemory() - Runtime
.getRuntime().freeMemory()) / 1000000 + "MB");
System.out.println("聚类中心:");
for (int i = 0; i < ClassCount; i++) {
System.out.println(classData[i][0] * classmax[0]);
//data[i][0] = (double) (Math.random()*100);
}
for (ArrayList<Integer> i : result) {
for (Integer integer : i) {
System.out.print(integer + "\t");
}
System.out.println("数目:" + i.size());
}
for (ArrayList<Integer> i : result) {
for (Integer integer : i) {
System.out.print(data[integer][0] + "\t");
}
System.out.println("数目:" + i.size());
}
System.out.println("noises:");
for (Integer noise : noises) {
System.out.println(noise);
}
}
/**
* 读取测试集的数据
*
* @param trainingFileName 测试集文件名
*/
public void readData(String trainingFileName) {
try {
FileReader fr = new FileReader(trainingFileName);
BufferedReader br = new BufferedReader(fr);
// 存放数据的临时变量
String lineData = null;
String[] splitData = null;
int line = 0;
// 按行读取
while (br.ready()) {
// 得到原始的字符串
lineData = br.readLine();
splitData = lineData.split(",");
// 转化为数据
// System.out.println("length:"+splitData.length);
if (splitData.length > 1) {
for (int i = 0; i < splitData.length; i++) {
// System.out.println(splitData[i]);
// System.out.println(splitData[i].getClass());
if (splitData[i].startsWith("Iris-setosa")) {
data[line][i] = (double) 1.0;
} else if (splitData[i].startsWith("Iris-versicolor")) {
data[line][i] = (double) 2.0;
} else if (splitData[i].startsWith("Iris-virginica")) {
data[line][i] = (double) 3.0;
} else { // 将数据截取之后放进数组
data[line][i] = Double.parseDouble(splitData[i]);
}
}
line++;
}
}
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 聚类过程,主要分为两步 1.循环找初始点 2.不断调整直到分类不再发生变化
*/
public void cluster() {
// 数据标准化处理
normalize();
// 标记是否需要重新找初始点
boolean needUpdataInitials = true;
// 找初始点的迭代次数
int times = 1;
// 找初始点
while (needUpdataInitials) {
needUpdataInitials = false;
result.clear();
//System.out.println("Find Initials Iteration" + (times++) + "time(s)");
// 一次找初始点的尝试和根据初始点的分类
findInitials();
firstClassify();
// 如果某个分类的数目小于特定的阈值,则认为这个分类中的所有样本都是噪声点
// 需要重新找初始点
for (int i = 0; i < result.size(); i++) {
if (result.get(i).size() < InstanceNumber
/ Math.pow(ClassCount, t)) {
needUpdataInitials = true;
noises.addAll(result.get(i));
}
}
}
// 找到合适的初始点后
// 不断的调整均值中心和分类,直到不再发生任何变化
Adjust();
// //把结果存入数组answer中
// for (int i = 0; i < ClassCount; i++) {
// KMeans.answer[i] = classData[i][0];
// }
}
/**
* 对数据进行归一化 1.找每一个属性的最大值 2.对某个样本的每个属性除以其最大值
*/
public void normalize() {
// 找最大值
for (int i = 0; i < InstanceNumber; i++) {
for (int j = 0; j < FieldCount; j++) {
if (data[i][j] > classmax[j])
classmax[j] = data[i][j];
}
}
// 归一化
for (int i = 0; i < InstanceNumber; i++) {
for (int j = 0; j < FieldCount; j++) {
data[i][j] = data[i][j] / classmax[j];
}
}
}
// 关于初始向量的一次找寻尝试
public void findInitials() {
// a,b为标志距离最远的两个向量的索引
int i, j, a, b;
i = j = a = b = 0;
// 最远距离
double maxDis = 0;
// 已经找到的初始点个数
int alreadyCls = 2;
// 存放已经标记为初始点的向量索引
ArrayList<Integer> initials = new ArrayList<Integer>();
// 从两个开始
for (; i < InstanceNumber; i++) {
// 噪声点
if (noises.contains(i))
continue;
// long startTime = System.currentTimeMillis();
j = i + 1;
for (; j < InstanceNumber; j++) {
// 噪声点
if (noises.contains(j))
continue;
// 找出最大的距离并记录下来
double newDis = calDis(data[i], data[j]);
if (maxDis < newDis) {
a = i;
b = j;
maxDis = newDis;
}
}
// long endTime = System.currentTimeMillis();
// System.out.println(i +
// "Vector Caculation Time:"+(endTime-startTime)+"ms");
}
// 将前两个初始点记录下来
initials.add(a);
initials.add(b);
classData[0] = data[a];
classData[1] = data[b];
// 在结果中新建存放某样本索引的对象,并把初始点添加进去
ArrayList<Integer> resultOne = new ArrayList<Integer>();
ArrayList<Integer> resultTwo = new ArrayList<Integer>();
resultOne.add(a);
resultTwo.add(b);
result.add(resultOne);
result.add(resultTwo);
// 找到剩余的几个初始点
while (alreadyCls < ClassCount) {
i = j = 0;
double maxMin = 0;
int newClass = -1;
// 找最小值中的最大值
for (; i < InstanceNumber; i++) {
double min = 0;
double newMin = 0;
// 找和已有类的最小值
if (initials.contains(i))
continue;
// 噪声点去除
if (noises.contains(i))
continue;
for (j = 0; j < alreadyCls; j++) {
newMin = calDis(data[i], classData[j]);
if (min == 0 || newMin < min)
min = newMin;
}
// 新最小距离较大
if (min > maxMin) {
maxMin = min;
newClass = i;
}
}
// 添加到均值集合和结果集合中
// System.out.println("NewClass"+newClass);
initials.add(newClass);
//System.err.println("newClass:"+newClass);
//System.err.println("alreadyCls:"+alreadyCls);
classData[alreadyCls++] = data[newClass];
ArrayList<Integer> rslt = new ArrayList<Integer>();
rslt.add(newClass);
result.add(rslt);
}
}
// 第一次分类
public void firstClassify() {
// 根据初始向量分类
for (int i = 0; i < InstanceNumber; i++) {
double min = 0f;
int clsId = -1;
for (int j = 0; j < classData.length; j++) {
// 欧式距离
double newMin = calDis(classData[j], data[i]);
if (clsId == -1 || newMin < min) {
clsId = j;
min = newMin;
}
}
// 本身不再添加
if (!result.get(clsId).contains(i))
result.get(clsId).add(i);
}
}
// 迭代分类,直到各个类的数据不再变化
public void Adjust() {
// 记录是否发生变化
boolean change = true;
// 循环的次数
int times = 1;
while (change) {
// 复位
change = false;
//System.out.println("Adjust Iteration" + (times++) + "time(s)");
// 重新计算每个类的均值
for (int i = 0; i < ClassCount; i++) {
// 原有的数据
ArrayList<Integer> cls = result.get(i);
// 新的均值
double[] newMean = new double[FieldCount];
// 计算均值
for (Integer index : cls) {
for (int j = 0; j < FieldCount; j++)
newMean[j] += data[index][j];
}
for (int j = 0; j < FieldCount; j++)
newMean[j] /= cls.size();
if (!compareMean(newMean, classData[i])) {
classData[i] = newMean;
change = true;
}
}
// 清空之前的数据
for (ArrayList<Integer> cls : result)
cls.clear();
// 重新分配
for (int i = 0; i < InstanceNumber; i++) {
double min = 0f;
int clsId = -1;
for (int j = 0; j < classData.length; j++) {
double newMin = calDis(classData[j], data[i]);
if (clsId == -1 || newMin < min) {
clsId = j;
min = newMin;
}
}
data[i][FieldCount] = clsId;
result.get(clsId).add(i);
}
// 测试聚类效果(训练集)
// for(int i = 0;i < ClassCount;i++){
// int positives = 0;
// int negatives = 0;
// ArrayList<Integer> cls = result.get(i);
// for(Integer instance:cls)
// if (data[instance][FieldCount - 1] == 1f)
// positives ++;
// else
// negatives ++;
// System.out.println(" " + i + " Positive: " + positives +
// " Negatives: " + negatives);
// }
// System.out.println();
}
}
/**
* 计算a样本和b样本的欧式距离作为不相似度
*
* @param aVector 样本a
* @param bVector 样本b
* @return 欧式距离长度
*/
private double calDis(double[] aVector, double[] bVector) {
double dis = 0;
int i = 0;
/* 最后一个数据在训练集中为结果,所以不考虑 */
for (; i < aVector.length; i++)
dis += Math.pow(bVector[i] - aVector[i], 2);
dis = Math.pow(dis, 0.5);
return (double) dis;
}
/**
* 判断两个均值向量是否相等
*
* @param a 向量a
* @param b 向量b
* @return 相等返回true
*/
private boolean compareMean(double[] a, double[] b) {
if (a.length != b.length)
return false;
for (int i = 0; i < a.length; i++) {
if (a[i] > 0 && b[i] > 0 && a[i] != b[i]) {
return false;
}
}
return true;
}
/**
* 将结果输出到一个文件中
*
* @param fileName 文件名
*/
public void printResult(String fileName) {
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw = new FileWriter(fileName);
bw = new BufferedWriter(fw);
// 写入文件
for (int i = 0; i < InstanceNumber; i++) {
bw.write(String.valueOf(data[i][FieldCount]).substring(0, 1));
bw.newLine();
}
// 统计每类的数目,打印到控制台
for (int i = 0; i < ClassCount; i++) {
System.out.println("第" + (i + 1) + "类数目: "
+ result.get(i).size());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (bw != null)
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
if (fw != null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
]]>命令不多,就下面几条。
#安装pure-ftpd
apt-get install pure-ftpd
# 在系统中添加相应的用户和组,如用户ftpuser 和组ftpgroup
groupadd ftpgroup
useradd ftpuser -g ftpgroup -d /home/ftp -s /sbin/nologin
# 添加虚拟用户,如添加ftpmethol,并指定查看目录为/srv/ghost
pure-pw useradd ftpmethol -u ftpuser -g ftpgroup -d /srv/ghost
# 根据提示输入两次密码
# 让 pure-ftpd 建立虚拟用户数据
pure-pw mkdb
# 重启服务
/etc/init.d/pure-ftpd restart
但是往往这样弄完之后,还是不能登陆成功,会提示用户密码验证失败,解决办法:
# 在/etc/pure-ftpd/auth下,创建一个软链接
cd /etc/pure-ftpd/auth
ln -s /etc/pure-ftpd/conf/PureDB 60puredb
其他重要配置:
# 匿名用户登录改为否
/etc/pure-ftpd/conf/NoAnonymous 内容改为no
]]>Session.load()/get()
方法均可以根据指定的实体类和id从数据库读取记录,并返回与之对应的实体对象。
其区别在于:
如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException。
Load方法可返回实体的代理类实例,而get方法永远直接返回实体类。
load方法可以充分利用内部缓存和二级缓存中的现有数据,而get方法则仅仅在内部缓存中进行数据查找,如没有发现对应数据,将越过二级缓存,直接调用SQL完成数据读取。
上面是看别人写的,我自己觉得
get()主要用于数据库有可能存在,也有可能不存在的时候,需要从数据库取出数据的时候。
load()主要用于可以肯定数据库中有这一条记录的时候,从数据库中去除这条数据。
下面两个例子,第一个是通过订单号得到订单这个实体类对象,第二个是通过订单号,从数据库中删除这条数据。
//通过订单号得到订单这个实体类对象,不能肯定这个订单是不是在数据库中存在
public Order getOrder(int orderid) {
Session session = sessionFactory.getCurrentSession();
Order order = null;
try {
order = (Order) session.get(Order.class, orderid);
} catch (RuntimeException e) {
throw e;
}
return order;
}
//通过订单号,从数据库中删除这条数据,可以肯定数据库中有这一条数据
public int deleteOrder(int orderId) {
Session session = sessionFactory.getCurrentSession();
try {
Order order = (Order) session.load(Order.class, orderId);
session.delete(order);
} catch (Exception e) {
return -1;
}
return 0;
}
]]>/**
* Created by Methol on 2015/4/10.
* 根据A数组的大小,输出 1 ~ A.length 的全排列
*/
public class Main {
static int A[] = new int[5];
/*
根据A数组的大小,输出 1 ~ A.length 的全排列
@param cur 当前元素的位置
*/
public static void next_permutation(int cur) {
if (cur == A.length) { //递归边界
//在这里打印输出,也就是说在这里写具体要操作的函数,A中有排列好的序列
for (int i = 0; i < A.length; i++) {
System.out.print(A[i] + " ");
}
System.out.println();
} else {
for (int i = 0; i <= A.length; i++) {
boolean ok = true;
for (int j = 0; j < cur; j++) {
if (A[j] == i) ok = false; //如果i已经在A[0]~A[cur-1]出现过,则不能再选
}
if (ok) {
A[cur] = i;
next_permutation(cur + 1); //递归调用
}
}
}
}
public static void main(String[] args) {
next_permutation(0);
}
}
/**
* Created by Methol on 2015/4/10.
* 输出数组P中元素的全排列。
*/
public class Main {
static int A[] = new int[5];
static int P[] = {1,2,3};
/*
输出数组P中元素的全排列。
@param cur 当前元素的位置
*/
public static void next_permutation(int cur) {
if (cur == P.length) { //递归边界
//在这里打印输出,也就是说在这里写具体要操作的函数,A中有排列好的序列
for (int i = 0; i < P.length; i++) {
System.out.print(A[i] + " ");
}
System.out.println();
} else {
for (int i = 0; i < P.length; i++) {
if(i==0 || P[i]!=P[i-1]) {
int c1 = 0 ,c2 = 0;
//统计A[0]~A[cur-1]中P[i]出现的次数
for (int j = 0; j < cur ; j++)
if(A[j] == P[i]) c1++;
//统计P中P[i]出现的次数
for (int j = 0; j < P.length; j++)
if( P[i] == P[j] ) c2++;
if(c1 < c2){ //当c1<c2的时候说明还有可以选的,递归调用
A[cur] = P[i];
next_permutation(cur+1);
}
}
}
}
}
public static void main(String[] args) {
next_permutation(0);
}
}
]]>/**
* Created by Methol on 2015/4/8.
*/
public class Main {
static int a[] ={0,0,0};
public static void show(){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+" ");
}
System.out.println();
}
public static void f(int i){
if(i>=a.length){
//这里写操作
show();
return;
}
a[i] = 0;
f(i+1);
a[i] = 1;
f(i+1);
}
public static void main(String []args){
f(0);
}
}
输出结果:
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
题目
有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数,如下图:
从第一行开始,每次可以往做下或右下走一格,直到走到最下行,把沿途经过的数全部加起来,如何走才能使得这个和最大。
分析
可以从下往上来分析
如下:
1
3 2
4 10 1
4 3 2 20
从下开始往上更新。
第1次更新为:
1
3 2
8 13 21
4 3 2 20
第2次更新:
1
16 23
8 13 21
4 3 2 20
第3次更新:
24
16 23
8 13 21
4 3 2 20
/*
递归
4
1
3 2
4 10 1
4 3 2 20
*/
import java.util.Scanner;
public class Main {
static int [][]a = new int [100][100]; //用来存储数字三角形的数据
static int [][]c = new int [100][100]; //用来存路径
static int n ;
static int count;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
int i,j;
for(i=0;i<n;i++){
for(j=0;j<=i;j++){
a[i][j] = scanner.nextInt();
}
}
System.out.println(fun(0, 0));
System.out.println("计算了"+count+"次");
//输出走的路径
System.out.print("路径如下:(0,0)");
int t = 0;
for(i=1;i<n;i++){
System.out.print("->("+i+","+c[i-1][t]+")");
t = c[i-1][t];
}
}
public static int fun(int i,int j){
if(i == n-1)
return a[i][j]; //如果到了最下面一层,就返回当前这个数
count++;
int t1 = fun(i+1, j);
int t2 = fun(i+1, j+1);
if(t1>t2){
c[i][j] = j; //表示当前这个位置是从下一行哪个数据来的
return a[i][j]+t1;
}else {
c[i][j] = j+1; //表示当前这个位置是从下一行哪个数据来的
return a[i][j]+t2;
}
//return a[i][j] + Math.max(fun(i+1, j), fun(i+1, j+1));
}
}
/*
* 递推
*/
import java.util.Scanner;
public class Main {
static int [][]a = new int [4][4];
static int [][]d = new int [4][4];
static int n ;
static int count;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
int i,j;
for(i=0;i<n;i++){
for(j=0;j<=i;j++){
a[i][j] = scanner.nextInt();
d[i][j] = a[i][j];
}
}
for(i=n-1;i>=1;i--){
for(j=0;j<i;j++){
count++;
d[i-1][j]+=Math.max(d[i][j], d[i][j+1]);
}
}
System.out.println(d[0][0]);
System.out.println("计算了"+count+"次");
}
}
/*
* 记忆化搜索
*/
import java.util.Scanner;
public class Main {
static int [][]a = new int [100][100];
static int [][]d = new int [100][100];
static int n ;
static int count;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
a[i][j] = scanner.nextInt();
}
}
System.out.println(fun(0, 0));
System.out.println("计算了"+count+"次");
}
public static int fun(int i,int j){
if(i == n-1)
return a[i][j]; //如果到了最下面一层,就返回当前这个数
if(d[i][j]!=0) return d[i][j];
count++; //统计 计算了多少次
d[i+1][j] = fun(i+1, j);
d[i+1][j+1] = fun(i+1, j+1);
return a[i][j] + Math.max(d[i+1][j], d[i+1][j+1]);
}
}
]]>#include <iostream>
#include <algorithm>
using namespace std;
const int size=8;
int eq[]={0,1,2,3,4,5,6,7};
long cc=0;
int eightqueen(){
while(next_permutation(eq,eq+8)){
bool ok=true;
int i,j;
//不可能存在同行同列,判断是不是相同对角线就可以了
for(i=0;i<size && ok;i++){
for(j=0;j<size && ok;j++){
if(i==j) continue;
if( (i-eq[i]==j-eq[j])||(i+eq[i]==j+eq[j]) ) //判断是不是在同一对角线
ok=false;
}
}
if(ok){
cc++;
for(i=0;i<size;i++){
cout<<eq[i];
}
cout<<endl;
}
}
return cc;
}
int main(){
cout<<endl<<eightqueen()<<endl;
return 0;
}
]]>