Ad Hoc(二)

社区检测

上面我们已经有了每条船之间的熟悉度的公式,也就是每条船已经建立起来了联系,接下来我们就要使用已有的熟悉度,对现有的船进行社区的识别。

社区检测算法LOUVain

参考论文:《Fast unfolding of communities in large networks》
参考链接:算法原理
开发环境:anaconda
官方文档:API
语言:python
安装依赖包:conda install python-louvain

安装完成后,来运行一个实例

包名是community,但在pypi上引用python louvain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import community as community_louvain
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import networkx as nx

# load the karate club graph
G = nx.karate_club_graph()

# compute the best partition
partition = community_louvain.best_partition(G)

# draw the graph
pos = nx.spring_layout(G)
# color the nodes according to their partition
cmap = cm.get_cmap('viridis', max(partition.values()) + 1)
nx.draw_networkx_nodes(G, pos, partition.keys(), node_size=40,
cmap=cmap, node_color=list(partition.values()))
nx.draw_networkx_edges(G, pos, alpha=0.5)
plt.show()

会显示图片:
在这里插入图片描述

1
community.best_partition(graph, partition=None, weight='weight', resolution=1.0, randomize=None, random_state=None)

使用Louvain启发式计算最大化模块化的图节点的划分(或尝试…)
这是最高模块化的划分,也就是由Louvain算法生成的树状图的最高划分。
返回 字典,分区(节点:对应的社区编号),社区编号从0到社区数
系数

  • graph networkx.Graph
    分解后的networkx图
    对于neworkx.Graph图,这个我也是真的很无语了(哈哈哈)
    先来看一下官方的解读吧图相关的学习链接
    官方应该是提供了一部分的测试用例,来帮助你生成一部分图。
    但是我们要将自己的数据填充到里面去,我们就需要自己生成一个networkx.Graph

    • 首先,创建一个空的图
      1
      G = nx.Graph()

    然后向空图中添加一些节点:

1
2
3
4
5
6
7
8
9
#单个节点的添加
G.add_node('1')
#通过一个节点集合添加
G.add_nodes_from(['2', '3'])
#添加没有权重的边
G.add_edge(n1, n2, object=x)
G.add_edges_from([('1', '2'), ('1', '3')])
#添加一个带有权重的边
G.add_weighted_edges_from([(1, 2, 0.125), (1, 3, 0.75), (2, 4, 1.2), (3, 4, 0.375)])
  • partition dict, optiona
    算法将开始使用这个节点分区。这是一个字典,其中键是它们的节点,值是社区
  • weight:str, optional
    图中用作权重的键。默认为“权重”
  • resolution:double, optional
    会改变社区的大小
    • randomize:boolean, optional
      将随机化节点评估顺序和社区评估顺序,以在每次调用时获得不同的分区
    • random_state:int, RandomState instance or None, optional (default=None)
      如果int,random_state是随机数生成器使用的种子;如果RandomState实例,random_state是随机数生成器;如果没有,则random number generator是使用的RandomState实例np.随机.

      将我们的数据分别填充进两个列表中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
shipid = open("/Users/gorge/Desktop/newshipData/out/r1_shipid.txt")
rela = open("/Users/gorge/Desktop/newshipData/out/r1_relation.txt");
links_data = []
for line in rela.readlines():
dataline = line.strip('\n').split("[")[0].split(" ")
famil=0
if str(dataline[2])!='0':
shijian = line.strip('\n').split("[")[1][:-1].split(",")
#先算一个总的时间T
T=0
for a in shijian:
if a!='':
T=T+(int(a.strip(" ")))
for x in shijian:
if x!='':
yy = int(a.strip(" "))
famil=(famil+(yy**2/T))
links_data.append((str(dataline[0]), str(dataline[1]), round(famil,2)))

nodes_data = []
G = nx.Graph()
dataline2 = shipid.readlines()[0].split(" ")
for id_q in dataline2:
if str(id_q)!='':
nodes_data.append(str(id_q))

将添加好的边以及边之间的关系进行添加

1
2
G.add_nodes_from(nodes_data)
G.add_weighted_edges_from(links_data)

使用算法进行社区的区分

1
partition = community_louvain.best_partition(G)

其他的一些参数设置

  • 补充一下字典中的初始化 函数
    1
    comm.fromkeys([str(i) for i in range(max(partition.values())+1)],[])

使用from keys 这个函数第一个参数为 默认的key值,第二个值为默认的value的值
在使用过程中发现,默认的value值如果为列表对象的话,初始值的设置为同一个对象,也就是说底层的指针,指向同一个地址

  • louvain中的模块度
    因为我们的实在需要一个评价社区划分的一个标准,经过查找资料发现,模块度可以用来评判社区检测的结果。
    因为louvain本来就是通过模块度的增益分社区的, 通过阅读community的源码,找到了对模块度的定义。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70

    def modularity(partition, graph, weight='weight'):
    """Compute the modularity of a partition of a graph

    Parameters
    ----------
    partition : dict
    the partition of the nodes, i.e a dictionary where keys are their nodes
    and values the communities
    graph : networkx.Graph
    the networkx graph which is decomposed
    weight : str, optional
    the key in graph to use as weight. Default to 'weight'


    Returns
    -------
    modularity : float
    The modularity

    Raises
    ------
    KeyError
    If the partition is not a partition of all graph nodes
    ValueError
    If the graph has no link
    TypeError
    If graph is not a networkx.Graph

    References
    ----------
    .. 1. Newman, M.E.J. & Girvan, M. Finding and evaluating community
    structure in networks. Physical Review E 69, 26113(2004).

    Examples
    --------
    >>> import community as community_louvain
    >>> import networkx as nx
    >>> G = nx.erdos_renyi_graph(100, 0.01)
    >>> partition = community_louvain.best_partition(G)
    >>> modularity(partition, G)
    """
    if graph.is_directed():
    raise TypeError("Bad graph type, use only non directed graph")

    inc = dict([])
    deg = dict([])
    links = graph.size(weight=weight)
    if links == 0:
    raise ValueError("A graph without link has an undefined modularity")

    for node in graph:
    com = partition[node]
    deg[com] = deg.get(com, 0.) + graph.degree(node, weight=weight)
    for neighbor, datas in graph[node].items():
    edge_weight = datas.get(weight, 1)
    if partition[neighbor] == com:
    if neighbor == node:
    inc[com] = inc.get(com, 0.) + float(edge_weight)
    else:
    inc[com] = inc.get(com, 0.) + float(edge_weight) / 2.

    res = 0.
    for com in set(partition.values()):
    ss = (inc.get(com, 0.) / links) - \
    (deg.get(com, 0.) / (2. * links)) ** 2
    print(ss)
    res += ss
    print(res)
    return res

我们可以通过将源码直接下载下来使用,为了保持文件的结构,我这里使用的是pycharm进行编辑的。
有了源码,其实我们也可以通过已经安装的community_louvain进行调用

1
2
cxc = community_louvain.modularity(partition, G)
print("社区的模块度为:",cxc)

这个函数一共有两个参数,上面源码中介绍的已经很清楚了
partition 为已经分出来的社区,

1
{'66660': 0, '55555': 1, '53995': 2, '12912': 3, '25417': 4, '68714': 5, '65773': 5, '50698': 6, '46344': 1, '29743': 1, '25510': 7, '11669': 3, '48048': 6, '59550': 8, '61259': 9, '46705': 0, '56825': 3, '14418': 3, '50140': 3, '66883': 3, '26371': 4, '47501': 1, '49980': 1, '31548': 6, ...}

partion是根据 {节点id : 社区名}进行标记社区的
G为输入的networkx graph图

1
2
3
4
5
6
7
G = nx.Graph()
其中G包括两部分组成:
节点的集合
['66660', '55555', '53995', '12912', ...]
节点之间关系的集合
[('66660', '31602', 15.0), ('66660', '66140', 1.95), ('66660', '41621', 10.14),...]
其中第一个元组代表的意思是 节点66660与节点31602的熟悉度为15.0

因此,在使用同一批进行

社区可视化

现在社区已经分出来了,使用python的库 networkx也可以画出社区关系图,但是画出来的图如下:
在这里插入图片描述
这个图是静态的,而且数据多了之后,可以发现区分的不是很明显,而且不能直观的表示出点的标签(这一部分我暂时没有探索出来,所以我就换了一个工具进行可视化处理)

Echarts

这是一个基于we b界面的可视化工具,在之前的记录中,也有提及过。这个画出来的模板是
在这里插入图片描述
可以看出来,这个图片看起来更友好一些,这里因为只能展示静态的图片,这个其实是一个动态的,当鼠标放在每个点上可以显示当前点的信息,而且更清晰一些。

工具

我这里使用的是webstrom 进行编辑的

需要引入的文件

1
2
3
<script src="./node_modules/js/echarts.min.js"></script>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="./node_modules/echarts/dist/extension/dataTool.js"></script>
  • 其中echarts.min.js的生成是由Echarts的官网进行定制的,我们可以登陆echarts的官网,然后点击下载按钮,选择在线定制,选择自己要定制的图表,然后在下面点击下载即可。
    在这里插入图片描述
    出现这个界面就表明定制成功了。

  • 对于query.min.js 大家可以使用在线的,直接使用上面的链接就可以了,这样就要保证在有网络的情况下使用,也可以去官网下载jquery.min.js的文件,这是j query所必须的依赖文件。

  • datatool.js 这个是我在下载echarts的包的时候就已经有了的。这个包的具体操作有点记不清楚了。很久之前做的。

数据格式

这个图使用的是json这个数据格式
解读一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"type": "force",
"categories": [{
"name": "HTMLElement",
"keyword": {},
"base": "HTMLElement"
}]
"nodes": [
{
"name": "AnalyserNode",
"value": 1,
"category": 4
}]
"links": [
{
"source": 169,
"target": 137
}
]
}

我上面只是举了一个例子
可以发现先是categories 表示社区的类别,每个社区的名称是什么,name的作用是显示标签用的,base是用来点击社区的一个成员的时候显示用的。
nodes 是社区的点,name是社区成员的名字,value 是用来显示该成员的权重,category是用来显示该成员是属于哪一个社区的,该项的值从0 - n表示上面分类的index
links 是表示社区成员之间是不是有关系,source源,target 目标节点,同样的道理他们对应的值 为nodes的index的值。
因此,我们如果想要使用这个工具,我们还需要自己写一个数据的转换工具。

数据的转换工具

分类格式的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
comm = {}
#comm = comm.fromkeys([str(i) for i in range(max(partition.values())+1)],[])
for i in range(max(partition.values())+1):
comm[i] = []
for key,values in partition.items():
comm[values].append(key)
# for key,values in comm.items():
# print("第",key,"个社区,成员个数为",len(values),"成员为",values)

# 将数据转换为json格式
# 分类
categories = []
for i in range(len(comm)):
sc = {}
sc["name"]=i
sc["keyword"]={}
sc["base"] = i
categories.append(sc)
print(categories)

节点格式的转换

1
2
3
4
5
6
7
8
9
# 节点的转换
json_nodes = []

for i in nodes_data:
sc = {}
sc["name"]= i;
sc["value"]=1
sc["category"]= partition[i]
json_nodes.append(sc)

节点关系的转换

1
2
3
4
5
6
json_links = []
for i in links_data:
sc = {}
sc["source"] = ship_idmap[i[0]]
sc["target"] = ship_idmap[i[1]]
json_links.append(sc)

数据以json的格式进行存储

1
2
3
4
5
6
7
ww = open("/Users/gorge/Desktop/www.json","a")
jsonxx = {}
jsonxx["type"]="force";
jsonxx["categories"]=categories
jsonxx["nodes"] = json_nodes
jsonxx["links"] = json_links
ww.write(str(jsonxx))

数据可视化的时候的调整

  • 符号的调整
    经过上面的处理,现在我们已经有一个json格式的数据文件了,但是j son里面是不会出现单引号的,这时候我们的数据格式是:
1
'type': 'force', 'categories': [{'name': 0, 'keyword': {}, 'base': 0},

这个样子在json里面会报错的。我们在用代码写入的时候,python的字典、链表等都需要转换为字符串的格式进行写入,在这个转化的过程中,python会将所有的双引号以及单引号都转化为单引号。因此,我们写入的时候,是单引号。
这里我们就不用代码在转译了,我们就直接使用webStorm中自带的工具,Edit->Find->Replace 进行单引号与双引号的替换即可。

  • 颜色的处理
    使用模型自带的颜色是一共有9种颜色,在gexf文件中指定每个node的所属类别后,如果不为这些node自定义颜色的话,会在生成的画像中会为每一个类别默认设置一个颜色,每个类别的node拥有同一种颜色。
    可以看到明显有一个问题就是当你的分类数量大于9时,颜色会再次重复回去,这样对于分类任务来说是没法接受的。
    既然在生成关系图的设置中没有任何关于默认颜色的设置,那么这个颜色设定必然在自己所引用的echarts.min.js库中,所以只要耐心一点在echarts.min.js库中找到对应的部分,然后修改echarts.min.js库的源码这个问题就解决了。
    这个大约在16123行
    在网上找了一个颜色库,将默认的颜色库替换掉即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
color: [
'#c23531', '#2f4554', '#61a0a8', '#d48265', '#91c7ae', '#749f83',
'#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3', 'rgb(250, 235, 215, 1)',
'rgb(240, 248, 255, 1)', 'rgb(0, 255, 255, 1)', 'rgb(245, 245, 220, 1)', 'rgb(255, 228, 196, 1)',
'rgb(127, 255, 212, 1)', 'rgb(240, 255, 255, 1)', 'rgb(0, 0, 0, 1)', 'rgb(255, 235, 205, 1)',
'rgb(0, 0, 255, 1)', 'rgb(138, 43, 226, 1)', 'rgb(165, 42, 42, 1)', 'rgb(222, 184, 135, 1)',
'rgb(95, 158, 160, 1)', 'rgb(127, 255, 0, 1)', 'rgb(210, 105, 30, 1)', 'rgb(255, 127, 80, 1)',
'rgb(100, 149, 237, 1)', 'rgb(255, 248, 220, 1)', 'rgb(220, 20, 60, 1)', 'rgb(0, 255, 255, 1)',
'rgb(0, 0, 139, 1)', 'rgb(0, 139, 139, 1)', 'rgb(184, 134, 11, 1)', 'rgb(169, 169, 169, 1)',
'rgb(0, 100, 0, 1)', 'rgb(169, 169, 169, 1)', 'rgb(189, 183, 107, 1)', 'rgb(139, 0, 139, 1)',
'rgb(85, 107, 47, 1)', 'rgb(255, 140, 0, 1)', 'rgb(153, 50, 204, 1)', 'rgb(139, 0, 0, 1)',
'rgb(233, 150, 122, 1)', 'rgb(143, 188, 143, 1)', 'rgb(72, 61, 139, 1)', 'rgb(47, 79, 79, 1)',
'rgb(47, 79, 79, 1)', 'rgb(0, 206, 209, 1)', 'rgb(148, 0, 211, 1)', 'rgb(255, 20, 147, 1)',
'rgb(0, 191, 255, 1)', 'rgb(105, 105, 105, 1)', 'rgb(105, 105, 105, 1)', 'rgb(30, 144, 255, 1)',
'rgb(178, 34, 34, 1)', 'rgb(255, 250, 240, 1)', 'rgb(34, 139, 34, 1)', 'rgb(255, 0, 255, 1)',
'rgb(220, 220, 220, 1)', 'rgb(248, 248, 255, 1)', 'rgb(255, 215, 0, 1)', 'rgb(218, 165, 32, 1)',
'rgb(128, 128, 128, 1)', 'rgb(0, 128, 0, 1)', 'rgb(173, 255, 47, 1)', 'rgb(128, 128, 128, 1)',
'rgb(240, 255, 240, 1)', 'rgb(255, 105, 180, 1)', 'rgb(205, 92, 92, 1)', 'rgb(75, 0, 130, 1)',
'rgb(255, 255, 240, 1)', 'rgb(240, 230, 140, 1)', 'rgb(230, 230, 250, 1)', 'rgb(255, 240, 245, 1)',
'rgb(124, 252, 0, 1)', 'rgb(255, 250, 205, 1)', 'rgb(173, 216, 230, 1)', 'rgb(240, 128, 128, 1)',
'rgb(224, 255, 255, 1)', 'rgb(250, 250, 210, 1)', 'rgb(211, 211, 211, 1)', 'rgb(144, 238, 144, 1)',
'rgb(211, 211, 211, 1)', 'rgb(255, 182, 193, 1)', 'rgb(255, 160, 122, 1)', 'rgb(32, 178, 170, 1)',
'rgb(135, 206, 250, 1)', 'rgb(119, 136, 153, 1)', 'rgb(119, 136, 153, 1)', 'rgb(176, 196, 222, 1)',
'rgb(255, 255, 224, 1)', 'rgb(0, 255, 0, 1)', 'rgb(50, 205, 50, 1)', 'rgb(250, 240, 230, 1)',
'rgb(255, 0, 255, 1)', 'rgb(128, 0, 0, 1)', 'rgb(102, 205, 170, 1)', 'rgb(0, 0, 205, 1)',
'rgb(186, 85, 211, 1)', 'rgb(147, 112, 219, 1)', 'rgb(60, 179, 113, 1)', 'rgb(123, 104, 238, 1)',
'rgb(0, 250, 154, 1)', 'rgb(72, 209, 204, 1)', 'rgb(199, 21, 133, 1)', 'rgb(25, 25, 112, 1)',
'rgb(245, 255, 250, 1)', 'rgb(255, 228, 225, 1)', 'rgb(255, 228, 181, 1)', 'rgb(255, 222, 173, 1)',
'rgb(0, 0, 128, 1)', 'rgb(253, 245, 230, 1)', 'rgb(128, 128, 0, 1)', 'rgb(107, 142, 35, 1)',
'rgb(255, 165, 0, 1)', 'rgb(255, 69, 0, 1)', 'rgb(218, 112, 214, 1)', 'rgb(238, 232, 170, 1)',
'rgb(152, 251, 152, 1)', 'rgb(175, 238, 238, 1)', 'rgb(219, 112, 147, 1)', 'rgb(255, 239, 213, 1)',
'rgb(255, 218, 185, 1)', 'rgb(205, 133, 63, 1)', 'rgb(255, 192, 203, 1)', 'rgb(221, 160, 221, 1)',
'rgb(176, 224, 230, 1)', 'rgb(128, 0, 128, 1)', 'rgb(255, 0, 0, 1)', 'rgb(188, 143, 143, 1)',
'rgb(65, 105, 225, 1)', 'rgb(139, 69, 19, 1)', 'rgb(250, 128, 114, 1)', 'rgb(244, 164, 96, 1)',
'rgb(46, 139, 87, 1)', 'rgb(255, 245, 238, 1)', 'rgb(160, 82, 45, 1)', 'rgb(192, 192, 192, 1)',
'rgb(135, 206, 235, 1)', 'rgb(106, 90, 205, 1)', 'rgb(112, 128, 144, 1)', 'rgb(112, 128, 144, 1)',
'rgb(255, 250, 250, 1)', 'rgb(0, 255, 127, 1)', 'rgb(70, 130, 180, 1)', 'rgb(210, 180, 140, 1)',
'rgb(0, 128, 128, 1)', 'rgb(216, 191, 216, 1)', 'rgb(255, 99, 71, 1)', 'rgb(64, 224, 208, 1)',
'rgb(238, 130, 238, 1)', 'rgb(245, 222, 179, 1)', 'rgb(255, 255, 255, 1)', 'rgb(245, 245, 245, 1)',
'rgb(255, 255, 0, 1)', 'rgb(154, 205, 50, 1)'
],

这样我们在去运行测试一下,就可以得到我们的社区分类的输出图形的可视化了
在这里插入图片描述
这样处理过后,我们的社区的可视化现在就相对很好了一些。

到此为止,对于社区检测的 关系图的输入,以及社区检测出来的社区的可视化分析我们就完成了,接下来我们需要做的是关系图的边的权重公式的优化,以及社区检测算法的优化。

测量社区分区算法分出来的社区的稳定性

现在我们一共有9天的数据(暂时还没有将更多的数据处理出来,因此我们就先用9天的数据分成3个3天进行测试社区的稳定性)

  • 社区的稳定性 定义为(同一批数据使用同一个社区划分算法,划分出来的社区 的个数相同,并且每一个社区的成员保持稳定)
    |这里的函数为 FileRead 中的div3d,控制函数为getFen3(String path,String targe) |

社区检测算法Infomap

算法简介
工具使用:在进行infomap工具安装的时候,网络上有很多的教程。

  • 第一种方式:因为我使用的是anaconda ,所以就先尝试了anaconda的安装方式安装连接,这个方式安装的时候,我没有安装成功。
  • 第二种方式:在线运行,这种方式在使用起来还是非常方便的,数据集可以直接在线上传,然后分区出来的结果可以在线可视化,缺点就是可修改性比较差
  • 第三种方式:使用pip安装,这种方式我还没有尝试。

    简单来介绍一下在线infomap的使用

    在这里插入图片描述
    点击进去,我们可以看到,这个界面,在使用的时候,只需要点击load network这个按钮将数据加载进去即可。
    数据格式可以是加权图,也可以是无权图
  • 带权图的格式
    第一行的数据表示为66660 节点与31620节点 存在边,边的权重为15.0

    1
    2
    3
    4
    5
    6
    7
    66660 31602 15.0
    66660 66140 1.95
    66660 41621 10.14
    55555 29743 6.0
    55555 49980 6.0
    55555 48640 363.0
    55555 42866 9.92
  • 无权图的格式
    第一行的数据表示为66660 节点与31620节点 存在边

    1
    2
    3
    4
    5
    6
    7
    66660 31602
    66660 66140
    66660 41621
    55555 29743
    55555 49980
    55555 48640
    55555 42866

接下来点击Run infomap这个按钮即可,下面显示框会出现运行报告

最左边的是open in networknavigator这个按钮,这个会在线显示分类的结果。
这个按钮下面有两个选项,可以选择clu 这个按钮,这个按钮是展示的最后最终的分类,还有一个按钮是Ftree这个按钮,这个会显示每一层的社区检测算法的结果。

最后的分类结果为
在这里插入图片描述
我们还可以点击每一个大的社区,查看这个大的社区下面小的社区
在这里插入图片描述
infomap检测的社区是分层次的。

  • Ftree 报告
    目前为止,对于这个社区检测工具的使用已经完成。但是我们现在还不清楚检测结果的Ftree报告
    这个报告主要分为两部分
    • 第一部分
      1
      2
      3
      4
      5
      # path flow name node_id
      1:1:1 0.0235485 "51031" 51031
      1:1:2 0.0213197 "25310" 25310
      1:1:3 0.020168 "26306" 26306
      1:1:4 0.0192412 "25771" 25771

第一行的意思是节点51031的节点id是51031,属于的社区层次为第一个社区中的第一号社区作为社区中的1号成员
现在flow的这个值,现在还没搞清楚

  • 第二部分为
    1
    2
    3
    4
    5
    *Links undirected
    #*Links path enterFlow exitFlow numEdges numChildren
    *Links root 0 0 24 7
    1 2 5.27517e-06
    1 4 6.39821e-05

enterFlow和exitFLow的值现在还没搞清楚

friendship 计算

通过大量阅读论文,找到了一篇跟我们场景比较像的论文《An Enhanced Community-based Routing Assisted by Ferry in Opportunistic
Networks》,我们对我们的friendship 的计算公式进行了改进。用两条船最后一次相遇的时刻减去第一次相遇的时刻,作为船的总的相遇间隔,然后乘二作为分子。分母是将每一次相遇的间隔的平方和累加。
然后对我们的计算公式,以及统计函数进行了改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import sys
ship = open("/Users/gorge/Desktop/newshipData/input/re50.txt")
info = open("/Users/gorge/Desktop/newshipData/r50port_ship.txt","a")
xx = ship.readlines()
T = 12957
res = []
count = 0;
for x in xx:
v = x.strip("\n").split(" ")
if len(v)>2:
v = v[2:]
SUM = 0
if v[0]!='12960':
#print(v)
count+=1;
for vi in v:
SUM+=(((int(vi)**2)/2))
if SUM ==0.0:
SUM = T-3
else:
sx =T/SUM
res.append(sx)
info.write(str(sx)+"\n")
print(max(res))
print(min(res))
print(count)

绘制friendship的累积分布函数图

其中,最重要的是hist函数,其中familddd表示的是一个一维数组,也就是我们那要统计的信息,可以通过参数cucumulative来调节,默认为False,画出的是PDF,那么True画出的便是CDF直方图。PDF(figure1)可以观察到整个数据在横轴范围内的分布,CDF(figure2)则可以看出不同的数据分布间的差异性,也可以观察到整个数据的增长趋势和波动情况。
观察两种数据分布的差异,可能使用直方图就不是很直观,各种直方柱会相互重叠,我们只需更改直方图的图像类型,令histtype=‘step’,就会画出一条曲线来。
港口对应的船舶数目的分布关系图的统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ship = open("/Users/gorge/Desktop/newshipData/input/re50.txt")

xx = ship.readlines()
count=0;
port_shipc = {}
for i in range(47):
port_shipc[str(i+1)] =0
print(port_shipc)
for x in xx:
v = x.strip("\n").split(" ")
if len(v)>3 and v[2]!='12960':
port_shipc[v[0]] = (port_shipc[v[0]]+1)
print(port_shipc)
sd =0
for i in range(47):
sd=sd+port_shipc[str(i+1)];
tt = open("/Users/gorge/Desktop/newshipData/port_ship/"+str(i+1)+".txt","a")
lii = "50 "+str(port_shipc[str(i+1)])+"\n";
tt.write(lii)
print(sd)

下面使用matalab绘制的上述统计的关系图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
tic;
clear all;
data_dir = 'D:\MATLAB\port_ship\*.txt';
namelist = dir(data_dir)
len = length(namelist);
for i =1:len
file_name{i} = namelist(i).name;
x = load(file_name{i});
H = plot(x(:,1),x(:,2));
hold on;
end
legend("1号港口","2号港口","3号港口","4号港口","5号港口","6号港口","7号港口","8号港口","9号港口","10号港口","11号港口","12号港口","13号港口","14号港口","15号港口","16号港口","17号港口","18号港口","19号港口","20号港口","21号港口","22号港口","23号港口","24号港口","25号港口","26号港口","27号港口","28号港口","29号港口","30号港口","31号港口","32号港口","33号港口","34号港口","35号港口","36号港口","37号港口","38号港口","39号港口","40号港口","41号港口","42号港口","43号港口","44号港口","45号港口","46号港口");
LABEL_X = '通信半径/海里'; %X轴内容
LABEL_Y = '船舶数量/条'; %Y轴内容
%获取Y轴
hY = ylabel(LABEL_Y,'FontSize',15);
hX = xlabel(LABEL_X,'FontSize',15);%同上
set(hY, 'FontSize',FONT_SIZE_LABEL);
set(hY,'Fontname','Times New Roman');
set(hY, 'Color', 'k');
set(hY, 'Interpreter','latex');
set(hX, 'FontSize', FONT_SIZE_LABEL); %坐标轴名称大小
set(hX,'Fontname','Times New Roman'); %坐标轴字体
set(hX, 'Color', 'k'); %坐标轴字体颜色
set(hX, 'Interpreter','latex'); %坐标轴格式

在这里插入图片描述

friendship的CDF关系图

1
2
3
4
5
6
7
tic;
clear all;
x=load('r50port_ship.txt')
[h,stats] = cdfplot(x)
title('Friendship Between Ships and Ports')
%xlim([0 0.0005])
legend('R50 CDF','Standard Normal CDF','Location','best')

在这里插入图片描述

船舶之间关系的统计

我们定义了一个船舶之间熟悉度的关系的公式,公式的含义是在一定的时间段下船舶相遇的平均等待时间,船舶的相遇定义为两个船之间的距离,小于一倍的通信半径。又在上面熟悉度的基础上进行了熟悉度的改进以及重新的统计计算。

船舶与港口之间关系的统计

船舶与港口之间的关系是将港口看作是一条船,然后计算每条船与港口之间的熟悉度进行定义的。

三个梯队船舶统计以及通信半径确定

  • 确定第一梯队 第二梯队 第三梯队的比例依次为3 : 6 : 1
  • 第一梯队的船舶的确定方式为将港口看作是一条船舶的轨迹,然后计算船舶与各个港口的熟悉度,根据船舶回港口的次数, 统计回港口的船的数量以及船舶的ID 主要使用的文件为re5 表示的就是船舶与港口的关系
  • 第二梯队船舶的统计方式为计算出船舶与船舶之间的熟悉度关系,在所有的船号中,先将第一梯队的船号给记录下来,然后将第一梯队的每艘船统计相对应的相遇的船舶,然后在每一个第一梯队对应的相遇船舶中剔除掉,第一梯队的船舶。(如果不删除则要展现的是港口 - 对应第一梯队的船-对应第二梯队的船的相遇)
    具体的实现方法为:
    先使用python根据port_ship 的关系对应的文件是rex.txt ,统计出船与港口的关系,读取这一个文件,统计出第一梯队的船,保存到文件中,然后使用JAVA 语言读取第一梯队的文件,将船号保存到List中,并且使用Map使用第一梯队的船号作为Key ,Value设置为一个空的List ,进行初始化一个Map 集合,然后,读取船舶与船舶之间的关系的文件,rx_relation.txt 统计的船舶与船舶之间的关系,这个文件中包含三个信息,0 表示的是目标船A,1表示的目标船B,(1: ] 分别表示的是船A 与船B 等待下一次相遇的时间间隔。
    我们要按行遍历这个文件,首先要判断船A 与 船B 是不是全都属于第一梯队,这里使用and 判断,如果是船A 与船B中有一个是属于第一梯队 ,第二个不属于第一梯队,那么就找出不属于第一梯队的那个船,将该船号添加到Map的list中,这样就建立起了第一梯队的船与第二梯队的船的对应关系,这时候的第二梯队的船 是有重复船的。这样就可以将第二梯队与第一梯队的船建立起来了联系,写入到txt文件中。
  • 第三梯队的船 就是祛除掉第一梯队的船 与 第二梯队的船 剩下的船

    统计第一梯队的船舶的船舶id以及相应通信半径下的船舶的数量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    ship = open("/Users/gorge/Desktop/newshipData/input/re50.txt")
    thepsn = open("/Users/gorge/Desktop/firstGroupShip/thepostship50number.txt", "a")
    thePs = open("/Users/gorge/Desktop/firstGroupShip/thepostship50.txt", "a")
    ships = open("/Users/gorge/Desktop/firstGroupShip/ships50.txt","a")
    xx = ship.readlines()
    count = 0;
    port_shipc = {}
    for i in range(47):
    port_shipc[str(i + 1)] = 0
    ship_portc = {}
    for i in range(47):
    ship_portc[str(i + 1)] = []
    shipc = {}
    for x in xx:
    v = x.strip("\n").split(" ")
    if len(v) > 3 and v[2] != '12960':
    port_shipc[v[0]] = (port_shipc[v[0]] + 1)
    ship_portc[v[0]].append(str(v[1]).split(".")[0])
    shipc[v[1].split(".")[0]] = 0
    for key, value in port_shipc.items():
    l = str(key) + " " + str(value) + "\n";
    print(l)
    thepsn.write(l)
    for key1, value1 in ship_portc.items():
    l1 = str(key1)
    for vx in value1:
    l1 = l1 + " " + vx;
    l1 = l1 + "\n";
    print(l1)
    thePs.write(l1)
    for key,vlaue in shipc.items():
    ships.write(str(key)+"\n")

数据可视化 Echarts 树状图json数据格式生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
ship = open("/Users/gorge/Desktop/newshipData/input/re50.txt")
Treejson = open("/Users/gorge/Desktop/range50.json","a")
xx = ship.readlines()
count = 0;
port_shipc = {}
for i in range(47):
port_shipc[str(i + 1)] = 0
ship_portc = {}
for i in range(47):
ship_portc[str(i + 1)] = []

for x in xx:
v = x.strip("\n").split(" ")
if len(v) > 3 and v[2] != '12960':
port_shipc[v[0]] = (port_shipc[v[0]] + 1)
ship_portc[v[0]].append(str(v[1]).split(".")[0])

# 主要使用的是根据ship_portc进行拼接
# 首先创建一个父类 map
father = {}
father["children"] = []
father["name"] = "港口"
# 先将港口的名字拼接进去
for i in range(47):
oil = {}
oil["children"] = []
oil["name"] = "港口"+str(i+1);
for sx in ship_portc[str(i + 1)]:
oil2 = {}
oil2["children"] = []
oil2["name"] = str(sx);
oil["children"].append(oil2)
father["children"].append(oil)
Treejson.write(str(father))

最后我们就得到了第一梯队与港口的关系,分别为5,10,20,50海里的关系。
在这里插入图片描述

统计第一梯队的船对应相遇的第二梯队的船

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
getTwoGroupShip("C:/Users/ouc/Desktop/firstGroupShip/ships5.txt","C:/Users/ouc/Desktop/result/r5_relation.txt","C:/Users/ouc/Desktop/twoGroupShip/one_twoGroupShips5.txt");

*
* @param fistGroupPath 船舶与港口关系的文件 rex.txt
* @param shiprePath 船舶与船舶之间关系的文件路径 rx_relation.txt
* @param twog 第二梯队的船的保存路径
*/
public static void getTwoGroupShip(String fistGroupPath,String shiprePath,String twoGrouppath) {
//首先是读取第一梯队的船 船号最为key list作为value
FileRead tool = new FileRead();
getTenMin wtool = new getTenMin();
List<String> theFirstGroup = tool.readFileLine(fistGroupPath);
//创建一个第一梯队对应的船的组
Map<String, List<String>> fistmap = new HashMap<String, List<String>>();
for (int i = 0; i < theFirstGroup.size(); i++) {
fistmap.put(theFirstGroup.get(i),new LinkedList<String>());
}
List<List<String>> shiprela = tool.readFILE3(shiprePath);
for(int i =0;i<shiprela.size();i++) {
List<String> line = shiprela.get(i);
if(Integer.parseInt(line.get(2))>0) {
if(!line.get(2).equals("12960")) {
String x1 = line.get(0).split("\\.")[0];
String x2 = line.get(1).split("\\.")[0];

//判断船A 与船B是不是都属于第一梯队
if(theFirstGroup.contains(x1)&&!theFirstGroup.contains(x2)) {
fistmap.get(x1).add(x2);
System.out.println(x1+" "+x2);
}
if(!theFirstGroup.contains(x1)&&theFirstGroup.contains(x2)) {
fistmap.get(x2).add(x1);
System.out.println(x1+" "+x2);
}
}
}
}
String value = wtool.WriteValue3(fistmap);
wtool.writerFile(twoGrouppath, value);
}

统计第二梯队的船的数量以及ID

1
2
3
4
5
6
7
8
9
10
11
12
13
#统计第二梯队的船数目(重复的算一次)
ship = open("C:/Users/ouc/Desktop/twoGroupShip/one_twoGroupShips20.txt")
ship_id = open("C:/Users/ouc/Desktop/twoGroupShip/Twoships20.txt","a")
page = ship.readlines();
ff = {}
for x in page:
if len(x)>0:
xx = x.strip("\n").split(" ")[1:]
for v in xx:
ff[v]=0
print(len(ff))
for k in ff.keys():
ship_id.write(str(k)+"\n")

统计第一梯队的船对应的第二梯队船的数目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ship = open("C:/Users/ouc/Desktop/twoGroupShip/one_twoGroupShips20.txt")
ship_write = open("C:/Users/ouc/Desktop/twoGroupShip/one_twoShips20number.txt","a")
page = ship.readlines();
ff = {}
for x in page:
if len(x)>0:
cx = x.strip("\n").split(" ")[0]
xx = x.strip("\n").split(" ")[1:]
for v in xx:
ff[cx]=len(xx)
for key,value in ff.items():
s = str(key)+" "+str(value)+"\n"
ship_write.write(s)
print(ff)
统计船舶与港口相遇次数 、每次相遇花费时间、以及完成相遇需要使用的时间的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//读取港口的文件,计算船与港口的伴随关系 
public static List<String> getMeetportWith(List<String> firstline ,List<List<String>> second) {
//定义一个存储上一个时刻的变量
double pretime = 0.0;
//定义一个变量用来 存储船的相遇次数
int count = 0;
String secshipid = second.get(0).get(1);
String firshipid = firstline.get(0);
System.out.println("港口"+firshipid+"相遇"+secshipid+"getMeetCount() start");
List<String> lStrings = new LinkedList<String>();
lStrings.add(firshipid);
lStrings.add(secshipid);
//初始化一个用来记录两条船时间间隔的链表
List<String> banshui = new LinkedList<String>();
//定义一个变量用来存储在船相遇的时候,伴随了几个间隔
int b = 0;
//定义一个旗帜来表示这段间隔是不是船开始伴随了
boolean flag = false;
//定一个变量来判断表示两个船是不是第一次相遇
boolean firstmeetTime = true;
//定义一个变量来存储第一次相遇的时刻
double firtimeet = 0;
//定义一个变量用来保存 最后一次相遇的时刻
double endtimeet = 0;
if(second.size()>=4) {
for(int i=0;i<second.size()-1;i++) {
//获得第二条船的第i时刻的记录
List<String> secondline = second.get(i);
//记录一下当前时刻
double curtime = Double.parseDouble(secondline.get(0));
//获得第一条船的第i时刻的 x y
double firstx = Double.parseDouble(firstline.get(0));
double firsty = Double.parseDouble(firstline.get(1));
//获得第二条船的第i时刻的x y
double secondx = Double.parseDouble(secondline.get(2));
double secondy = Double.parseDouble(secondline.get(3));
//计算一下 两个点之间的距离 与 船舶的二倍的通信呢半径比较一下
double x = Math.pow((secondx-firstx), 2);
double y = Math.pow((secondy-firsty), 2);
double distance = Math.sqrt(x+y);
//设置一下船的通信范围
double contactRange = 5*1.87;
if(distance<=contactRange) {
if(firstmeetTime) {
firtimeet = curtime;
firstmeetTime = false;
}
endtimeet = curtime+3;
if(curtime-pretime>3) {
pretime = curtime;
count++;
flag= true;
b++;
}else {
pretime = curtime;
flag = true;
b++;
//System.out.println("b"+b);
}
}else {
flag=false;
}
if(!flag && b!=0) {
// System.out.println(flag);
banshui.add(String.valueOf(b*3));
b=0;
}
}
}
if(b!=0) {
//System.out.println("out"+b);
banshui.add(String.valueOf(b*3));
}
//伴随的次数
lStrings.add(String.valueOf(count));
//每次伴随的时间
lStrings.add(String.valueOf(banshui));
//完成伴随需要花费的总的时间
lStrings.add(String.valueOf(endtimeet-firtimeet));
System.out.println("船"+firshipid+"相遇"+secshipid+"次数为:"+count);
System.out.println("getMeetCount() end");
return lStrings;
}

统计船舶与港口相遇次数以及熟悉度相互对应的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import sys
ship = open("C:/Users/ouc/Desktop/result/re100.txt")
ship_number= open("C:/Users/ouc/Desktop/result/re100meet.txt");
info = open("C:/Users/ouc/Desktop/result/number_100rean.txt","a")
xx = ship.readlines()
yy = ship_number.readlines()
T = 12957

count = 0;
N_R = {}
for x in xx:
v = x.strip("\n").split(" ")
if len(v)>2:
POR = v[0]
shipId = v[1].split(".")[0]
id = str(POR)+"_"+str(shipId)
v = v[2:]
SUM = 0
if v[0]!='12960':
N_R[id] = []
#print(v)
count+=1;
for vi in v:
SUM+=(((int(vi)**2)/2))
if SUM ==0.0:
SUM = T-3
else:
sx =T/SUM
N_R[id].append(sx)
#info.write(str(sx)+"\n")
for y in yy:
vx = y.strip("\n").split(" ")
if int(vx[2])>0:
POR = vx[0]
shipId = vx[1].split(".")[0]
id = str(POR) + "_" + str(shipId)
n = vx[2]
if id in N_R.keys():
N_R[id].append(n)
for key,value in N_R.items():
xc = key
if len(value)==2:
v1 = value[0]
v2 =value[1]
xc=xc+" "+str(v1)+" "+str(v2)+"\n"
info.write(str(xc))
print(len(N_R))

分港口统计第一梯队的船对应的第二梯队的船舶的ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
one_two = open("C:/Users/ouc/Desktop/twoGroupShip/one_twoGroupShips100.txt")
postship = open("C:/Users/ouc/Desktop/firstGroupShip/thepostship100.txt")
page = one_two.readlines();
page2 = postship.readlines()

# 首先将第一港口的船号 与 第二港口的船号 存储到一map里面
oe_map = {}
for x in page:
sx = x.strip("\n").split(" ")
if len(sx) >2:
oe_map[sx[0]] = sx[1:]
else:
oe_map[sx[0]] = []
# 读取港口与第一梯队的船对应关系
for y in page2:
sy = y.strip("\n").split(" ")
port = sy[0]
oe_ship = sy[1:]
tt = open("C:/Users/ouc/Desktop/one_two_100ship/"+str(port)+".txt","a")
for oo_s in oe_ship:
if oo_s in oe_map.keys():
ss = str(oo_s)+" "
for ui in oe_map[oo_s]:
ss= ss+" "+str(ui)
ss=ss+"\n"
tt.write(ss)

港口与第一梯队的船以及第二梯队的船的对应关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
ship = open("/Volumes/17854111053/期末复习/result/re50.txt")
Treejson = open("/Users/gorge/Desktop/range50.json","a")
one_twoID = open("/Volumes/17854111053/期末复习/twoGroupShip/one_twoGroupShips50.txt")
xx = ship.readlines()
count = 0;
port_shipc = {}
for i in range(47):
port_shipc[str(i + 1)] = 0
ship_portc = {}
for i in range(47):
ship_portc[str(i + 1)] = []

for x in xx:
v = x.strip("\n").split(" ")
if len(v) > 3 and v[2] != '12960':
port_shipc[v[0]] = (port_shipc[v[0]] + 1)
ship_portc[v[0]].append(str(v[1]).split(".")[0])
#创建一个第一梯队的船号对应的第二梯队的船的船号
yy = one_twoID.readlines()
one_twoDir = {}
for iy in yy:
iyy = iy.strip("\n").split(" ")
one_twoDir[iyy[0]]=[]
if len(iyy)>1:
iyy2 = iyy[1:]
for sd in iyy2:
one_twoDir[str(iyy[0])].append(str(sd))
# 主要使用的是根据ship_portc进行拼接
# 首先创建一个父类 map
father = {}
father["children"] = []
father["name"] = "港口"
# 先将港口的名字拼接进去
for i in range(47):
oil = {}
oil["children"] = []
oil["name"] = "港口"+str(i+1);
for sx in ship_portc[str(i + 1)]:
oil2 = {}
oil2["children"] = []
for sds in one_twoDir[str(sx)]:
oil3 = {}
oil3["children"] = []
oil3["name"]=str(sds)
oil2["children"].append(oil3)
oil2["name"] = str(sx);
oil["children"].append(oil2)
father["children"].append(oil)
#print(father)
Treejson.write(str(father))

分港口统计第二梯队的船的数量

1
2
3
4
5
6
7
8
9
10
11
12
13
nd = {}
dfs = open("C:/Users/ouc/Desktop/twonumber100.txt","a")
for i in range(1,48):
ss = open("C:/Users/ouc/Desktop/one_two_100ship/one_two_100ship/"+str(i)+".txt")
page = ss.readlines()
nd[i]=0;
for x in page:
ll = x.strip("\n").split(" ")
if len(ll)>1:
llx = ll[1:]
nd[i]=len(llx)
dfs.write(str(i)+" "+str(nd[i])+"\n")
print(nd)

聚类算法

一维数据进行聚类,使用设置阈值间隔的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import numpy as np
from pandas import Series
def threshold_cluster(Data_set,threshold):
#统一格式化数据为一维数组
stand_array=np.asarray(Data_set).ravel('C')
# print(stand_array)
#为一维数组赋值上索引
stand_Data=Series(stand_array).sort_values()
#print(stand_Data)
# 创建索引数组 创建分类结果的数组
index_list,class_k=[],[]
# 判断函数是不是还有数据,如果有数据的话就可以继续便利了
while stand_Data.any():
# 如果只有一个数据的话 就直接将数据拼接到 index_list 以及 class_k中
if len(stand_Data)==1:
index_list.append(list(stand_Data.index))
class_k.append(list(stand_Data))
# 将数据stand_Data进行清空
stand_Data=stand_Data.drop(stand_Data.index)
# 如果多与两个数据的话 就执行下面的代码
else:
# 先将第一个数据的index 放置到 第一个分类中
class_data_index=stand_Data.index[0]
#print(class_data_index,"---")
# 将第一个的数据 放置到第一个分类中
class_data=stand_Data[class_data_index]
# 将已经分类的数据删除掉
stand_Data=stand_Data.drop(class_data_index)
# 判断已经分类的数据与剩下没有分类数据的差值 然后将差值 转化为一维数组
if (abs(stand_Data-class_data)<=threshold).any():
args_data=stand_Data[abs(stand_Data-class_data)<=threshold]
stand_Data=stand_Data.drop(args_data.index)
index_list.append([class_data_index]+list(args_data.index))
class_k.append([class_data]+list(args_data))
else:
index_list.append([class_data_index])
class_k.append([class_data])
return index_list,class_k
file = open("/Users/gorge/Desktop/thepostship100number.txt")
Data_set = []
page = file.readlines()
for sc in page:
df= sc.strip("\n").split(" ")
k = df[0]
v = df[1]
Data_set.append(int(v))
index_list, class_k = threshold_cluster(Data_set, 5)
print(index_list)
print(class_k)
第二梯队的船相遇了多少第一梯队的船,与第一梯队的船相遇了多少次 熟悉度是怎么样的
  • 第二梯队的船相遇第二梯队的船多少次

    • 首先是祛除重复的统计出第二梯队的船,放在 Twoships.txt文件中,然后读取第一梯队与第二梯队对应关系的one_twoGroupShipsx.txt的文件,如果该文件第二列以后的ID包含现在关注的第二梯队的船的ID,我们就将 该第一梯队的id,添加到对应的第二梯队的ID下面。这样就完成了第一步统计第二梯队的船相遇第二梯队的船多少次。
  • 第二梯队的船与第一梯队的船相遇的次数是怎么样的

    • 根据船与船之间相遇关系的文件rx_relation.txt的文件,只要船A的ID以及船B的ID能够与我们上面统计的第二梯队与第一梯队的船对应上(这里的对应上有两种情况 船A的ID属于第一梯队以及船B的id属于第二梯队 或者 船A的ID属于第二梯队以及船B的id属于第一梯队 )这样我们就将这相遇次数 添加进去。
    • 在这个频率下对应的熟悉度的大小是怎么样的
      • 读取rx_waitRealation.txt这个文件,计算船舶与船舶之间的熟悉度的大小关系。按照与统计第二梯队的船与第一梯队的船的相遇次数的方式进行匹配熟悉度
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        ship = open("C:/Users/ouc/Desktop/twoGroupShip/one_twoGroupShips20.txt")
        ship_id = open("C:/Users/ouc/Desktop/twoGroupShip/Twoships20.txt")
        ## 用来存储第二梯队的船相遇第一梯队的船的数目
        two_one_nunmber = open("C:/Users/ouc/Desktop/twoGroupShip/two_one_nunmber20.txt","a")
        page_ship = ship.readlines();
        page_id = ship_id.readlines();
        twog = {}
        twoc = {}

        for id in page_id:
        id_1 = id.strip("\n")
        twoc[str(id_1)] = 0
        for x in page_ship:
        if len(x) > 0:
        x_1 = x.strip("\n").split(" ")[0]
        xx = x.strip("\n").split(" ")[1:]
        if xx.__contains__(id_1):
        k1 = str(id_1)+"_"+str(x_1)
        twog[k1] =[]
        twoc[str(id_1)] = twoc[str(id_1)]+1
        for k,v in twoc.items():
        two_one_nunmber.write(str(k)+" "+str(v)+"\n")
        # 第二梯队的船与第一梯队的船相遇的次数是怎么样的
        two_oneship = open("C:/Users/ouc/Desktop/result/r20_relation.txt")
        two_onepage = two_oneship.readlines()
        twog_key = twog.keys()
        for vd in two_onepage:
        line = vd.strip("\n").split(" ")
        x1 = line[0].split(".")[0]+"_"+line[1].split(".")[0]
        x2 = line[1].split(".")[0] + "_" + line[0].split(".")[0]
        n = int(line[2])
        if twog_key.__contains__(x1) or twog_key.__contains__(x2):
        if twog_key.__contains__(x1):
        twog[x1].append(n)
        if twog_key.__contains__(x2):
        twog[x2].append(n)

        # 计算相应的熟悉度并进行添加
        ship_wait = open("C:/Users/ouc/Desktop/result/r20_waitRelation.txt")
        info = open("C:/Users/ouc/Desktop/twoGroupShip/r20_two_one_concretely.txt","a")
        xx = ship_wait.readlines()
        T = 12957
        count = 0;
        for x in xx:
        v =x.strip("\n").split(" ")
        x1 = v[0].split(".")[0] + "_" + v[1].split(".")[0]
        x2 = v[1].split(".")[0] + "_" + v[0].split(".")[0]
        if twog_key.__contains__(x1) or twog_key.__contains__(x2):
        if len(v)>2:
        v = v[2:]
        SUM = 0
        if v[0]!='12960':
        count+=1;
        for vi in v:
        SUM+=(((int(vi)**2)/2))
        if SUM ==0.0:
        SUM = T-3
        else:
        sx =T/SUM
        if twog_key.__contains__(x1):
        twog[x1].append(sx)
        if twog_key.__contains__(x2):
        twog[x2].append(sx)

        for k,v in twog.items():
        strx = k.split("_")[0]+" "+k.split("_")[1]
        for ix in v:
        strx =strx+" "+str(ix)
        strx =strx+"\n"
        info.write(strx)

树状图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ship = open("C:/Users/ouc/Desktop/twoGroupShip/r20_two_one_concretely.txt")
Treejson = open("C:/Users/ouc/Desktop/twoGroupShip/r20.json","a")
xx = ship.readlines()

eds = {}
for xd in xx:
line = xd.strip("\n").split(" ")
if eds.keys().__contains__(line[0]):
fir = {}
fir[line[1]] = []
fir[line[1]].append(line[2])
fir[line[1]].append(line[3])
eds[line[0]].append(fir)
else:
eds[line[0]]=[]
father = {}
father["children"] = []
father["name"] = "The Two Group ship"
#
for k,v in eds.items():
one = {}
one["children"] = []
one["name"] = str(k)
for scx in v:
for xsf in scx.keys():
num = {}
num["children"] = []
num["name"] = xsf
cishu = {}
cishu["children"] = []
cishu["name"] = scx[xsf][0]
num["children"].append(cishu)
cf = {}
cf["children"] = []
cf["name"] = scx[xsf][1]
num["children"].append(cf)
one["children"].append(num)
father["children"].append(one)
print(father)
Treejson.write(str(father))

###

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//统计船与港口的相遇与不相遇的时间节点  
public static List<String> getMeetportTime(List<String> firstline ,List<List<String>> second) {
//定义一个存储上一个时刻的变量
double pretime = 0.0;
//定义一个变量用来 存储船的相遇次数
int count = 0;
String secshipid = second.get(0).get(1);
String firshipid = firstline.get(0);
System.out.println("港口"+firshipid+"相遇"+secshipid+"getMeetCount() start");
List<String> lStrings = new LinkedList<String>();
lStrings.add(firshipid);
lStrings.add(secshipid);
//初始化一个用来记录两条船相遇时间 节点的链表[1,2]
List<String> timenode = new LinkedList<String>();
//定义一个变量用来存储在船相遇的时候,伴随了几个间隔
int b = 0;
//定义一个旗帜来表示这段间隔是不是船开始伴随了
boolean flag = false;
//定一个变量来判断表示两个船是不是第一次相遇
boolean firstmeetTime = true;
//定义一个变量来存储第一次相遇的时刻
double firtimeet = 0;
//定义一个变量用来保存 最后一次相遇的时刻
double endtimeet = 0;
if(second.size()>=4) {
for(int i=0;i<second.size()-1;i++) {
//获得第二条船的第i时刻的记录
List<String> secondline = second.get(i);
//记录一下当前时刻
double curtime = Double.parseDouble(secondline.get(0));
//获得港口的第i时刻的 x y
double firstx = Double.parseDouble(firstline.get(0));
double firsty = Double.parseDouble(firstline.get(1));
//获得第二条船的第i时刻的x y
double secondx = Double.parseDouble(secondline.get(2));
double secondy = Double.parseDouble(secondline.get(3));
//计算一下 两个点之间的距离 与 船舶的二倍的通信半径比较一下
double x = Math.pow((secondx-firstx), 2);
double y = Math.pow((secondy-firsty), 2);
double distance = Math.sqrt(x+y);
//设置一下船的通信范围
double contactRange = 20*1.87;
if(distance<=contactRange) {
if(firstmeetTime) {
firtimeet = curtime;
firstmeetTime = false;
}
endtimeet = curtime+3;
if(curtime-pretime>3) {
pretime = curtime;
count++;
flag= true;
b++;
}else {
pretime = curtime;
flag = true;
b++;
//System.out.println("b"+b);
}
}else {
flag=false;
}
if(!flag && b!=0) {
// System.out.println(flag);
timenode.add(String.valueOf("["+(pretime-b*3)+","+pretime+"]"));
b=0;
}
}
}
if(b!=0) {
//System.out.println("out"+b);
timenode.add(String.valueOf("["+(pretime-b*3)+","+pretime+"]"));
}
//伴随的次数
lStrings.add(String.valueOf(count));
//每次伴随的时间节点对
lStrings.add(String.valueOf(timenode));
//完成伴随需要花费的总的时间
lStrings.add(String.valueOf(endtimeet-firtimeet));
System.out.println("船"+firshipid+"相遇"+secshipid+"次数为:"+count);
System.out.println("getMeetCount() end");
return lStrings;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import matplotlib.pyplot as plt
filepath = open("C:/Users/ouc/Desktop/result/re20meetTimeNode2.txt")
filepath2 = "C:/Users/ouc/Desktop/picture/"
page = filepath.readlines()
ddct = {}
for t in range(24574560,24587517,3):
ddct[t] = 0
for line in page:
lines = line.strip("\n").split(" ")
if lines[2]!='0':
xd = lines[3:-1]
p = filepath2+lines[0]+"_"+lines[1]+'.png';
for v in xd:
vv = v.strip("[").strip("]").strip(",").split("_")
x1 = int(vv[0])
x2 = int(vv[1])
for ss in range(x1,x2,3):
ddct[ss] = 1
print(vv)
x = []
y = []
for key, val in ddct.items():
x.append(key)
y.append(val)
plt.plot(x, y)
plt.savefig(p,dpi=300)
plt.close()

计算两种熟悉度的数值 并进行相关统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
meet =  open("C:/Users/ouc/Desktop/result/re20meet.txt")
wa = open("C:/Users/ouc/Desktop/result/re20.txt")
ress = open("C:/Users/ouc/Desktop/result/resss.txt","a+")
# 初始化一个meet 次数
n_map = {}
c_map={}
meet_ship = meet.readlines()
for line in meet_ship:
ls = line.strip("\n").split(" ")
if str(ls[2])!="0":
k = str(ls[0])+"_" + str(ls[1].split(".")[0])
n_map[k] = ls[2]
wa_ship = wa.readlines()
wa_map = {}
for line in wa_ship:
ls = line.strip("\n").split(" ")
if str(ls[2])!="12960":
k = str(ls[0]) + "_" + str(ls[1].split(".")[0])
fg = ls[2:]
if len(fg)>0 and n_map.__contains__(k):
wa_map[k] = []
for ys in fg:
wa_map[k].append(ys)
### 第一个公式
def gef(ti,n):
sum = 0;
for x in ti:
sum = sum+int(x);
T = sum/len(ti)
f = 0;
for y in ti:
f = f+(int(y)-T)**2
f=f/len(ti)
return n/(f**0.005);
# 第二个公式
def getsd(loc,T):
fd = 0
for i in range(1,len(loc)):
fd =fd+ ((loc[i]-loc[i-1])**2)/2
return T/fd
#
for key,item in wa_map.items():
ti = item
n = int(n_map[key])
if len(ti)>0:
z = gef(ti,n)
if str(n)!="1":
sddx = str(key).split("_")[0]+" "+str(key).split("_")[1]+" "+str(z)+" "+str(n)+"\n"
ress.write(sddx)

# import time
# st = "[3432]]"
# s = st.strip("[").strip("]")
# print(s)
# time.sleep(4)
# print(len(s))

港口聚簇分类后对港口进行重新编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def getNewPort():
port_map = {}
port_map[1] = 1
port_map[2] = 2
port_map[3] = 3
port_map[4] = 4
port_map[5] = 4
port_map[6] = 5
port_map[7] = 6
port_map[8] = 7
port_map[9] = 8
port_map[10] = 9
port_map[11] = 9
port_map[12] = 9
port_map[13] = 10
for i in range(14,22):
port_map[i]=11
port_map[22] = 12
port_map[32] = 13
port_map[24] = 14
port_map[26] = 14
port_map[35] = 15
port_map[34] = 16
port_map[23] = 17
port_map[33] = 17
port_map[43] = 18
port_map[27] = 19
port_map[28] = 19
port_map[29] = 19
port_map[37] = 19
port_map[30] = 20
port_map[36] = 21
port_map[38] = 22
port_map[46] = 22
port_map[39] = 23
port_map[40] = 24
port_map[31] = 25
port_map[41] = 26
port_map[42] = 26
port_map[43] = 26
port_map[44] = 26
return port_map
ff = getNewPort()
print(ff)

问题

原始文件 大小11G,这11G数据是所有的船的数据都混合在一起的,使用Python 进行按照船的编号进行分文件,将同一条船的数据放在一个txt 文件下,分出来的单条船组成的文件的大小,要比原始文件的大小小很多,程序也没有报错!

划分网格 统计船舶在相应海域的密度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import os
rootpath = "C:/Users/ouc/Desktop/data/New201609/Sample1"
targetpath = open("C:/Users/ouc/Desktop/result/201609s.txt","a+")
ls = os.listdir(rootpath)
arr = [[0 for col in range(1000)] for row in range(0,1000)]
def getY(lon1,lat1,lon2,lat2,lat3):
k = (lat2-lat1)/(lon2-lon1)
b = lat2-k*lon2
return (lat3-b)/k
#设置经纬度的范围
minLat = 25
maxLat = 35
minLon = 120
maxLon = 130
Ilon = (maxLon-minLon)/1000
Ilat = (maxLat-minLat)/1000
print(Ilon)

print(len(arr))
for p in ls:
path = rootpath+"/"+p;
page = open(path).readlines()
for line in page:
l = line.strip("\n").split(" ");
lon = float(l[4])/600000
lat = float(l[3])/600000
if lon>=120 and lon<=130 and lat<=35 and lat>=25:
x = int((lon - minLon) / Ilon)
y = int((lat - minLat) / Ilat)
lony =0
if lat>=32.62170 and lat<=35:
lony = getY(121.39759,34.040604,121.898542,32.62170,lat)
if lat<32.62170 and lat>=32.02698:
lony = getY(121.898542,32.62170,122.742584,32.02698,lat)
if lat<32.02698 and lat>=30.68529:
lony = getY(122.742584,32.02698,123.836783,30.68529,lat)
if lat<30.68529 and lat>=28.312783:
lony = getY(123.836783,30.68529,122.613561,28.312783,lat)
if lat < 28.312783 and lat >= 25.625886:
lony = getY(122.613561,28.312783, 120,25.625886, lat)
if lony < lon:
print(lony,":",lon)
arr[y][x] = arr[y][x] + 1;

mx = 0
for i in arr:
li =""
for j in i:
li=li+(" "+str(j))
if j>mx:
mx = j
targetpath.write(li+"\n")
print(mx)
统计船所属的港口

三种I_O模型知识梳理

这块知识一直感觉挺不好理解的,现有的介绍的文章感觉总结的不是很全,所以自己总结了一版!

一、基本概念

  • BIO(Basic Input Output) :就是传统的I/O操作,同步阻塞IO,当BufferReader 读取输入流中的数据的时候,如果没有读取到有效的数据,程序将在此处阻塞该线程的执行。这里的B有两个意思 一个是Basic 另一个意思为Block 翻译为阻塞。服务器实现模式为一个连接一个线程,即客户端有连接请求的时候,
  • NIO:(New Input Output) :同步非阻塞I/O,也可以翻译为Non-Block Input/Output,新的IO使用了不同的方式来处理输入/输出,新IO采用内存映射文件的方式来处理输入输出,新IO将文件或者文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了。服务器实现模式为一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求的时候才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这需要用户进行不停的去询问。
  • AIO:(Asynchronous Input Output):异步非阻塞的IO操作,AIO是在Java7中引入了NIO的改进版NIO2,这是异步IO模型。异步能力最大的一个特点就是异步能力,是一种在读写操作结束之前允许进行其他操作的I/O操作。应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

二、同步与异步,阻塞与非阻塞的理解

  • 阻塞
    阻塞其实就是等待的意思,调用者发起请求如果不能立即拿到结果就一直等,等到结果完成。
  • 非阻塞
    非阻塞就是调用者发起请求后会立即返回,没有等待的过程,但是要自己不断去检查是否已有结果。
    • 同步
      当前线程发起了一个调用或者请求,然后当前线程需要等待该调用结束返回结果才能继续往下进行其他操作。
    • 异步
      当前线程发起了一个调用请求或请求,然后当前线程不需要等待调用的执行结果就可以继续往下执行。
      下面我们将阻塞与异步结合在一起进行理解一下:
      • 同步阻塞:你到饭馆点餐,然后在那里等着,还要一边喊:好了没!饭好了可以吃了。
      • 同步非阻塞:在饭馆点完餐,就去遛狗了,不过遛了一会,就回饭馆喊一声:好了没!
      • 异步阻塞: 在饭馆点完餐,就去遛狗了,遛狗的时候,饭馆给你打电话说饭做好了,让您亲自去拿。
      • 异步非阻塞: 在饭馆点完餐,就去遛狗了,遛狗的时候,饭馆给你打电话说饭做好了,饭馆打电话说,我们知道您的位置,一会给您送过去,安心遛狗就可以了。

什么是I/O?

上面我们对阻塞/非阻塞,异步/同步进行了理解,下面在来了解一下什么是IO

计算机体系结构

冯诺伊曼计算机的体系结构
冯诺依曼的计算机体系结构分为输入设备,计算器,控制器,运算器以及输出设备,这里的IO指的就是 输入输出设备。
为了保证输入输出的安全性以及稳定性,一个进程的地址空间分为用户空间以及内核空间

  • 用户空间 我们平时的应用程序都是运行在用户空间,是用户程序运行的地方。
  • 内核空间 内核空间才能进行系统级别的资源有关的操作,是内核代码的运行空间。
    用户空间与内核空间是相互隔离的,我们想要进行IO操作是要依赖内核空间的能力,但是用户空间的程序是不能直接访问内核空间的,要执行IO操作的时候,由于没有执行权限就要发起系统调用请求来请求操作系统帮忙完成。
    我们常见的IO主要分为磁盘IO(读写文件)网络IO(网络请求)
    在应用程序的角度来看,我们的应用程序其实就是对操作系统的内核发起IO调用,操作系统负责的内核执行具体的IO操作。也就是说,我们的应用程序其实就是发起了Io操作而已,具体执行IO的还是操作系统的内核。

    NIO的基本概念理解

    关于IO的基本概念,我已经在另外一篇文章中进行了总结,这里只对NIO的几个关键词进行总结。
    channel 与buffer是新IO中的两个核心对象。
  • Channel (通道)
    通道是对传统输入输出系统的模拟,在NIO中,所有的数据都需要通过通道进行传输,Channel与传统的InputStream以及OutputStream的主要区别在与它提供了一个map方法,可以通过map方法直接将“一块数据”映射到内存中,这样就实现了传统的输入输出是面向流处理的,但是NIO是面向块处理的。而且通道不同于流的地方还在于通道是双向的,底层的操作系统的通道一般都是全双工的。代表连接到数据源的通道。
  • Buffer(缓冲区)
    Buffer可以理解成一个容器,它的本质是一个数组,发送到channel中的对象都被存放在Buffer中,从channel中读取的数据也必须先放在Buffer中。我们不能直接对channel进行操作,Buffer的主要作用就是用来与NIO进行交互的,数据是从通道中读入缓冲区,从缓冲区写入通道中的。

    channel 与Buffer的关系

    channel 是通道的含义,数据不可以直接在channel上进行处理,必须要把数据读取到缓冲区Buffer中,从通道中读取数据到缓冲区中,从缓冲区写入数据到通道。
    • 多路复用器 Selector 多路复用器
      java NIO 三大基本组件Channel、Buffer、Selector,三大组件的关系
      Selector 提供选择已经就绪的任务的能力。
      Selector 轮询注册在其上的Channel,如果某个Channel发生多谢请求并且Channel就处于就绪状态,会被Selector轮询出来。然后通过SelectionKey可以获取就绪Channel集合,进行后续的I/O操作。一个Selector 可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以就没有最大句柄1024/1024的限制,所以只需要一个线程负责轮询就可以接入成千上万的客户端。
      selector与channel的关系
      channel与buffer的关系

      三种常见的IO的理解

      BIO

      在这里插入图片描述
      同步阻塞模型中,应用程序发起read之后,会一直阻塞,直到内核将数据拷贝到用户空间。

      NIO 模型

      java中的NIO可以理解为I/O多路复用模型。
      在这里插入图片描述
      NIO同步非阻塞,一个线程在同步的进行轮询检查,Selector 不断的轮询注册在其上的Channel,某个Channel 上面连续发生读写连接请求,这个Channel就处于就绪状态,被Selector轮询出来,然后通过SelectionKey就可以获取就绪Channel的集合,进行后续的操作。
      I/O多路复用操纵
      IO多路复用模型中,线程首先发起select调用,询问内核数据是否已经准备就绪,等内核把数据准备好了,用户线程再发起read调用,read调用的过程(数据从内核空间-> 用户空间)还是阻塞的。
      在这里插入图片描述

AIO模型

在这里插入图片描述

是在NIO的基础上引入异步通道的概念,实现异步非阻塞式的IO处理。AIO 不需要通过多路复用器对注册的通道进行轮询操作即可实现异步读写。NIO采用轮询的方式,一直在轮询的询问stream中的数据是否准备就绪,如果准备就绪就发起处理,但是AIO就不需要了,AIO框架在windows下使用windows IOCP技术,在Liunx下使用epoll多路复用技术模拟异步IO,即:应用程序向操作系统注册IO监听,然后继续做自己的事情。操作系统发生IO事件,并且准备好数据后,在主动的通知应用程序,触发相应的函数(这是以一种订阅者模式进行改造)。由于应用程序不是“轮询”方式而是订阅-通知方式,所以不在需要selector 轮询,由channel 通道直接到操作系统注册监听。

应用场景

  • BIO
    适用于连接数目比较少且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,编程比较复杂,程序简单易于理解。
  • NIO
    适用于连接数目比较多的情况下,并且连接比较短(轻操作)的架构。比如聊天服务器。
  • AIO
    适用于连接数目比较多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂

    调用总结

    在这里插入图片描述

2021年华为软件精英挑战赛总结

很幸运得了一个 前64名,但是整个过程是非常紧张的
相关赛题,以及代码
这是在海大上研究生以来第一次正式参加比赛,距离上次参加类似的比赛已经过去将近三年了,这种参加比赛的感觉依旧还是没有改变。
参加这次比赛大约用了12天的时间,比赛只允许最多三个人参加比赛。第一次参加华为精英杯挑战赛,还是缺少一些经验的。过程就是刚开始先拿到了题目,团队中三个人各自研究各自的,然后每个人根据自己的理解,去写自己对这个题目的理解,然后开始根据自己的思路开始写。
(其实这时候,我们对题目的理解还不是很好,每个人的理解方式也不一样~~)这儿我们做的还不是很好(可以先统一看题目,然后一块根据题目的意思进行讲解,先对题目进行一个透彻的分析~~然后一个团队在开始进行工作)这样就能更高效的写出准确的代码,团队的作用没有发挥出来。对题目进行一个准确的理解,并且商定好解决方案,是要比各自为战更能得到一个好的结果与效率。
对于这一次比赛,缺乏相关的理论知识,在算法设计上相对来说比较简单的,有时候价格的降低,其实只是对参数的多次调整,才获得的结果,并没有能真正的理解结果产生的原因。对于这个比赛,其实有一定相关计算机编程基础都会得到一定的答案,但是答案却不一定是最优解,所以我们很快就写了一个解决方案,这个方案的花费是不低的,后期还是要调的。其实,写这么一个过程并不难,主要是思路是不是清晰,解决方案是不是合理。

解题思路

我们队伍的解题思路是对于购买方案:既然最终的目的是花费最少,我们在购买服务器的时候的选择就是首先购买能满足虚拟机部署条件的,服务器的日均花费最少(固定价格+每天的电费* 使用的天数)/天数
。这样首先对已经提供的服务器按照我们的日均花费最少进行一个从小到大的排序。选择的时候,就会优先选择能满足条件的花费最少的服务器。我们队伍是一条一条命令解析的,这样其实是很难优化的。按照题目的意思应该是一天结束后,对这一天的命令进行统一解析,统一购买。哎~~刚开始的时候,也没考虑好这些问题。每进来一台虚拟机,我们都会查看当前的服务器是否能进行部署,部署的策略就是均衡服务器的内存,让服务器能够最大限度的利用内存。对于迁移策略,刚开始是没有考虑迁移策略的,这也是前期设计上的不足,迁移策略是后期又更改代码加上去的。迁移的策略就是对于整个服务器集群上的所有服务器包含的虚拟机的数量,如果该服务器上只有一台虚拟机,我们就对这台服务器上的虚拟机进行迁移,被迁移到的服务器上已有的服务器数量不能小于一,因为我们迁移的目的是能够空出更大的内存以及CPU,然后节省下当天的电费。这样在每天开始购买服务器或者删除虚拟机之前,就对之前的服务器集群的状态进行一次更改,然后在释放内存以及内核。我们的思路比较简单。

成长

经过这次比赛,也让我成长了一些,在与团队成员进行交流的时候,认识到了自己的一些不足的地方,数学知识,算法有待加强,自己在编写程序的时候,设置的变量名称一定要能表达该变量所传达的意思。避免随意起名。在整个过程中,还感觉到自己的逻辑思维还是有待加强的,在编写的时候,明明感觉没有错误了,但是在真正运行的时候还是会出现问题,然后在去查找错误,是非常浪费时间的,不如一开始就思考好,编写的逻辑,尽量减少自己逻辑上的错误。通过与团队成员的交流,也学到了许多良好的学习方法以及编程习惯。
在这里插入图片描述

多线程实战之批处理一个文件夹下的文件总结

项目背景

现有4个G的船舶轨迹数据,按照船号存储在一个文件夹下,我需要对这些数据进行处理一下,按照单线程的方式,处理起来有点浪费时间,为了提高效率,提高电脑CPU的利用率,打算将数据使用多线程的方式进行处理一下。

项目代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package InitShip;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class FileThread extends Thread{
Tool tool = new Tool();
private final static CountDownLatch countDownL = new CountDownLatch(10);
private int fileIndex;
private List<String> fileList;
private String filepath="";
private String movepath="";

public String getFilepath() {
return filepath;
}
public void setFilepath(String filepath) {
this.filepath = filepath;
}
public String getMovepath() {
return movepath;
}
public void setMovepath(String movepath) {
this.movepath = movepath;
}
public int getFileIndex() {
return fileIndex;
}
public void setFileIndex(int fileIndex) {
this.fileIndex = fileIndex;
}
public List<String> getFileList() {
return fileList;
}
public void setFileList(List<String> fileList) {
this.fileList = fileList;
}
@Override
public void run() {
for(int i=0;i<fileList.size();i++) {
if(i%10 == fileIndex) {
//下面三行代码写逻辑代码的地方,可以将想要更改的文件逻辑添加在这个地方
List<List<String>> allList =tool.readFILEN(filepath+fileList.get(i));
String value = tool.WriteValue(allList);
//readFile.renameTo(new File(movepath+readFile.getName()));
tool.writerFile(movepath+fileList.get(i),value);
}
countDownL.countDown();
}
}
public static void main(String [] args) throws InterruptedException {
String filepath = "C:\\Users\\ouc\\Desktop\\data\\202101perupd_test\\";
String movepath = "C:\\Users\\ouc\\Desktop\\data\\202101perupd\\";
File file = new File(filepath);
String [] fileList = file.list();
List<String> fList = new ArrayList<String>();
for(int i=0;i<fileList.length;i++) {
fList.add(fileList[i]);
}
for(int i=0;i<10;i++) {
FileThread fileThread = new FileThread();
fileThread.setFileIndex(i);
fileThread.setFileList(fList);
fileThread.setFilepath(filepath);
fileThread.setMovepath(movepath);
fileThread.start();
}
countDownL.await();
}
}

相关知识记录

文件读写操作

CountDownLatch

在java.util.concurrent.CountDownLatch;包下的一个工具类,countDownLatch这个类使得一个线程或者多个线程等待其他线程各自执行完毕之后在执行。

两种使用场景

  • 让多个线程等待
  • 让单个线程等待

    构造器

    1
    2
    //参数count为计数值
    public CountDownLatch(int count){}

主要方法

1
2
3
4
5
6
//调用await()方法的线程会被挂起,它会等待直到count的值为0的时候才继续执行
public void await()
//和await()类似,只不过等待一定的时间后count的值还没有变为0的话就继续执行
public boolean await(long timeout,TimeUnit unit)
//执行将count的值减一
public void countDown(){};

让多个线程等待,模拟并发

并发线程一起执行,例如在指定的时刻进行秒杀,线程准备就绪后,进行等待,直到秒杀时刻的到来,然后一起执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FileThread extends Thread{

private final static CountDownLatch countDownL = new CountDownLatch(1);
@Override
public void run() {
try {
countDownL.await();
String parter ="["+Thread.currentThread().getName()+"]";
System.out.println(parter+"开始执行");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String [] args) throws InterruptedException {
for(int i=0;i<5;i++) {
new FileThread().start();
}
Thread.sleep(2000);
countDownL.countDown();
}
}

让单个线程等待,多个线程任务完成后,进行汇总合并

很多时候,并发任务存在前后依赖关系,例如:多个数据操作完成后,需要数据check,这是在多个线程任务完成后,进行汇总合并的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private  final static CountDownLatch countDownL = new CountDownLatch(5);
@Override
public void run() {
System.out.println("finish"+Thread.currentThread().getName());
countDownL.countDown();
}
public static void main(String [] args) throws InterruptedException {
for(int i=0;i<5;i++) {
new FileThread().start();
}
//Thread.sleep(2000);
countDownL.await();
System.out.println("主线程:在所有的任务完成后,执行");
}

工作原理

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。
调用await()方法的线程会被阻塞,直到计数器减到0的时候,才能继续往下执行。
调用await()进行阻塞等待的线程,他们被阻塞,这样所有的线程都能在同一个起跑线上
countDown()方法则是将计数器减1;当减为0的时候,就继续往下执行。

项目代码解读

首先创建了一个CountDownLatch的工具,初始值为10,设置一个文件的index,以及文件路径的列表,该类继承了Thread,因此需要重写run()方法,这里我们要使用的是第二种场景,让单线程,等待多线程的过程,虽然我们后续没有其他任务要继续执行。在run 方法里面,线程开始执行我们编写好的逻辑代码,执行完成一个之后,countDown就会减一,countDownLatch的值设置为全局的,因此,我们创建的10个线程,每次执行都会使得countDown减一,run()方法里面的if(i%10 == fileIndex) 表示了10个线程,需要负责的文件的数量。这样就实现了10个线程同时进行文件操作的过程。

Win10 系统MySQL8.0 忘记管理员密码,重新设置新的密码

首先使用管理员权限登录CMD

右键系统菜单–>选择Windows PowerShell(管理员)(A)启动管理员cmd

查看现有进程是否含有mysql

使用tasklist能列出所有的进程,和相应的信息。如果包含Mysql的开头的进程,一律杀死即可
tskill能查杀进程,语法很简单:tskill 程序名!!

切换命令行路径到Mysql的bin目录下

我是一步一步退出在重新进入的。

1
2
3
4
5
6
7
PS C:\Windows\system32> cd ..
PS C:\Windows> cd ..
PS C:\> ls
PS C:\> cd '.\Program Files\'
PS C:\Program Files> cd .\MySQL\
PS C:\Program Files\MySQL> cd '.\MySQL Server 8.0\'
PS C:\Program Files\MySQL\MySQL Server 8.0> cd bin

执行跳过权限认证的命令

8版本与前面的版本不一样,语法上是有区别的,注意区分

1
mysqld --console --skip-grant-tables --shared-memory

错误及处理

我在运行的时候,出现了下面的错误,错误原因是 console没有安装

1
2
3
4
5
6
2021-02-05T02:45:36.329602Z 0 [System] [MY-010116] [Server] C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld.exe (mysqld 8.0.22) starting as process 17040
2021-02-05T02:45:36.332291Z 0 [Warning] [MY-010091] [Server] Can't create test file C:\Program Files\MySQL\MySQL Server 8.0\data\mysqld_tmp_file_case_insensitive_test.lower-test
2021-02-05T02:45:36.332400Z 0 [Warning] [MY-010091] [Server] Can't create test file C:\Program Files\MySQL\MySQL Server 8.0\data\mysqld_tmp_file_case_insensitive_test.lower-test
2021-02-05T02:45:36.332547Z 0 [ERROR] [MY-013276] [Server] Failed to set datadir to 'C:\Program Files\MySQL\MySQL Server 8.0\data\' (OS errno: 2 - No such file or directory)
2021-02-05T02:45:36.346381Z 0 [ERROR] [MY-010119] [Server] Aborting
2021-02-05T02:45:36.346516Z 0 [System] [MY-010910] [Server] C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld.exe: Shutdown complete (mysqld 8.0.22) MySQL Community Server - GPL.

下面就对这个错误进行处理哦!

  • 首先执行 mysqld --initialize --user=mysql --console
  • 执行成功后执行mysqld -install 会提示Service successfully installed.
  • 重新执行 mysqld --console --skip-grant-tables --shared-memory 即可。
    这时候当前这个命令行就不可以使用了。

    使用管理员权限在重新开一个命令行

    进入到Mysql的安装目录执行
    1
    PS C:\Program Files\MySQL\MySQL Server 8.0\bin> mysql

这时候,就能成功的登录到mysql里面,我们来测试一下,是不是登录成功了

1
2
3
4
5
6
7
8
9

mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |

可以发现我们已经登录成功。
然后执行下面的语句

1
2
3
mysql> use mysql;
Database changed
mysql> show tables;

老的版本是执行select user,host,password from user; 但是会报错password是一个无效的字段。在mysql8.0中password已经被替代了
因此我们要执行select user,host,authentication_string from user;
最后执行:

1
UPDATE mysql.user SET authentication_string='' WHERE user='root' and host='localhost';

关闭两个CMD 重启mysql

我们关闭刚刚打开的两个管理员登录的cmd,然后在重新使用管理员权限打开一个cmd,输入

1
2
3
PS C:\Windows\system32> net start mysql
MySQL 服务正在启动 .
MySQL 服务已经启动成功。

启动成功之后,输入

1
PS C:\Windows\system32> mysql -u root

这时候,我们就进入到了mysql的界面了,在输入

1
2
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'root';
Query OK, 0 rows affected (0.01 sec)

这里的root 是我设置的测试用户名与密码,要根据自己的自行调整。

使用普通用户权限打开cmd

1
C:\Users\ouc>mysql -uroot -p

这样我们就可以使用新的密码成功的登录数据库了!

这里还是要提醒大家,尽量建立一个密码本,可以将密码都记录下来,这样就不用整天这么麻烦的因为这种小事情,捣鼓啦

Maven

简介

Maven的主要作用是进行项目管理以及统一维护jar包

Maven的管理的项目需要有着相同的项目结构

  • 有一个pom.xml 用于维护当前项目都使用了哪些jar包
  • 所有的java代码都放在src/main/java的下面
  • 所用的测试代码都放在src/test/java的下面

统一维护jar包

如果含有不同的项目,这三个项目的结构都不是maven的风格,就会各自维护一套jar包,而这些jar包是相同的,而maven的项目,会把这些jar包都放在“仓库”里面,然后哪个项目需要用到哪个jar包,就只需要给出jar包的名称还有版本号。

配置

现在maven的最新版本为3.8.3这一版本,下载binary的这一个zip文件就可以了。Mac下 需要下载apache-maven-xxxx-bin.tar.gz 这个文件
(在使用mac 配置的时候,注意环境变量 export v=/Users 等号之间不能留有空格)

仓库

所谓的仓库就是用于存放项目需要的jar包的。maven采用的是一个仓库,多个项目,多个项目共享一个仓库。

仓库默认的位置

1
2
3
4
5
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>

修改仓库默认的保存位置

1
<localRepository>Users/gorge/java_jar</localRepository>

修改默认的下载路径

maven 会默认从maven官方提供的服务器下载jar包。
而官方服务器在国外,所以下载起来会比较慢,而且不稳定。
所以我们将下载路径设置为阿里云的。

1
2
3
4
5
6
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>

Eclipse上配置Maven

Mac版本的为: Eclipse ->Preferences
然后
在这里插入图片描述
然后点击Add -> Directory (这个为对应的maven的安装路径)
将已经安装好的maven添加进去就可以啦!

设置仓库路径

这里我们要选择上面图中的User Setting 这个选项
,配置Global Settings 和 User Settings都使用我们上面的配置文件
然后在点击一下Reindex ,这个按钮确保我们的Local Repository 的配置为我们上面配置的仓库默认的保存位置 Users/gorge/java_jar

接下来在Eclipse中创建一个Maven的项目就可以了

Maven添加包

在Maven项目中找到pom.xml 文件,然后在jar包网站上jar包 找到自己要添加的jar包
在这里插入图片描述
将这个dependency添加到pom.xml文件的dependencies 中就可以啦!!记得点击保存。

Eclipse上创建java web项目

在这里插入图片描述
与前面创建java se项目不同的地方在于,这一步的时候我们要选择maven-archetype-webapp这个选项。
此时能得到的maven web 项目,不过有两个问题

  1. 没有java源代码目录
  2. index.jsp报错

    创建java 源代码目录

    在上一步的截图中可以发现,没有地方存放java源文件,这个时候就需要按照如下步骤做:
    右键项目->属性->Java Build Path->Libraries->选中JRE System Library->Edit->Workspace default JRE(jdk8)->Finish 就可以了

index.jsp 报错

这个报错 的原因是因为缺少servlet.jar包的支持,我们只需要按照上面导入jar包的步骤进行导入就可以了。

将项目部署到tomcat上面

与java web项目的操作是一样的,直接在项目上进行右键点击,然后Run As -> Run on Server 就可以了!

Maven的父子项目

通过 maven 可以创建父子-聚合项目。 所谓的父子项目,即有一个父项目,有多个子项目。
这些子项目,在业务逻辑上,都归纳在这个父项目下,并且一般来说,都会有重复的jar包共享。
所以常用的做法会把重复的 jar 包都放在父项目下进行依赖,那么子项目就无需再去依赖这些重复的 jar 包了。

首先,我们要先按照上述流程创建一个Maven Project 项目,这个项目是作为父项目的。

然后修改配置文件

主要是修改两部分的配置文件

  • 修改默认包
1
2
这里默认的是jar,我们应该修改为pom,这样才可以作为父项目存在
<packaging>pom</packaging>
  • 修改hutool jar 包的依赖,用于后来子项目里观察对其的调用
    需要在depend ices 中添加如下代码
    1
    2
    3
    4
    5
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.3.1</version>
    </dependency>

修改 pom.xml 之后, 右键点击项目 -> Maven -> Update Project -> OK 进行更新。 否则项目会有红点~

创建子项目

接下来创建maven 子项目。 maven 子项目有叫做模块 module.
为了能够在 parentMavenProject 的基础上创建其子项目,首先右键点击 parentMavenProject, 然后点击 New -> Other -> Maven -Maven Module -> Next

子项目的pom.xml

可以看到其中有如下parent

1
2
3
4
5
<parent>
<groupId>cn.how2j</groupId>
<artifactId>parentMavenProject</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

父项目的pom.xml

再次打开父项目的pom.xml, 会发现其多了一个

1
2
3
<modules>
<module>childMavenProject</module>
</modules>

泛型总结与补充

当把对象放进java集合中的时候,集合就会忘记这个对象的数据类型,从而保证了通用性,但是也产生了一些问题。
对于java集合,在编译的时候是不检查类型的,因此取出集合的时候是需要进行强制类型转换的,可能会引发ClassCastException异常。JDK5之后,发展起来的。

所谓泛型就是在定义类、接口、方法的时候使用类型形参,这个类型形参将在声明变量、创建对象、调用方法的时候使用类型形参,这个类型形参将在声明变量、创建对象、调用方法的时候动态的指定(即传入实际的类型参数、可以称为类型实参)。

更新

1
2
3
ArrayList<String> s = new ArrayList<String>();
//JDK7之前是不支持的
ArrayList<String> s = new ArrayList<>();
  • 泛型的实质是:允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用。几乎所有可使用普通类型的地方都可以使用这种类型形参。
  • 类名中添加了泛型之后,在写类的构造器的时候,不需要添加
  • 泛型具有继承性
  • 不管泛型的实际类型参数是什么,他们在运行的时候总有同样的类.在内存中也只占用一块内存,因此在静态方法,静态代码块,静态变量的声明过程中,都不允许使用类型形参。
1
2
3
ArrayList<String> s = new ArrayList<>();
ArrayList<Integer> s2 = new ArrayList<>();
System.out.println(s.getClass()==s2.getClass()); //true
  • 由于系统中并不会真正的生成泛型类,所以instanceof 运算符后不能使用泛型类

    使用类型通配符

    为了表示各种类型List的父类,我们使用类型统配符,类型统配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?> (意思是未知类型元素的List)。这个问号(?)被称为统配符,它的元素类型可以匹配任何类型。

下面这个程序中使用的时候需要注意,

1
2
3
ArrayList<?> s = new ArrayList<String>();
s.add(new Object());
// 这样写会出错的

因为,add方法是有类型参数E作为集合的元素类型,所以传递给add的参数必须是E类的对象或者子类,但是添加null是可以的,因为null是所有引用类型的实例。
如果调用get方法是可以的,其返回值是一个未知类型,但是可以肯定的是它总是一个Object。

类型通配符的上限

如果我们不想List<?>是所有类型的父亲类,只想表示它是某一类泛型List的父类。可以使用下面例子

1
2
// shape是 一个抽象类 ,那么list中就可以使用继承自shape的类
List<? extends Shape>

因为我们不知道这个受限制通配符的具体类型,因此,我们也不能将Shape类的子类添加到List中。

设定类型形参的上限

1
2
// Number 是List 类型形参的上限
List<T extends Number>

泛型方法

我们在定义方法的时候,可以使用类型形参,所谓的泛型方法就是在声明方法的时候定义一个或多个类型形参。使用格式如下:
修饰符 <T,S> 返回值类型 方法名(形参列表 ){
//方法体
}

多个形参类型之间以逗号隔开,所有的类型形参声明放在方法修饰和方法返回值类型之间

例如:

1
2
3
4
5
static <T> void fromArrayTocollection(T[] a,Collection<T> c){
for(T O : a){
c.add(o)
}
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Generical {
static <T> void addArray(T[] a,Collection <T> c) {
for(T o:a) {
c.add(o);
}
}
public static void main(String [] args) {
Object [] oa = new Object[10];
Collection <Object> coll = new ArrayList<>();
addArray(oa,coll);
String [] ss = new String[3];
Collection<String> coll2 = new ArrayList<>();
addArray(ss,coll2);
}
}

方法声明的形参只能在该方法里面使用,而接口、类声明中定义的类型形参则可以在整个接口以及类中使用。

泛型方法和类型统配符的区别

大多数时候都可以使用泛型方法来代替类型统配符。例如:

1
2
3
4
5
boolean containAll(Collection<?> c);
boolean addAll(Collection<? extends E> c)
可以 替换成
<T> boolean containAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c)

上面两个方法中类型形参T只使用了一次,类型形参T使用的产生的唯一效果是可以在不同的调用点传入不同的实际类型。类型统配符就是被设计用来支持灵活的子类化的。
泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系,如果没有依赖关系就不要使用泛型。

泛型构造器

Java 允许在构造器签名中声明类型形参。

1
2
3
4
5
6
7
8
9
10
11
public class Foo {
public <T> Foo(T t){
System.out.println(t);
}
}

//测试
public static void main(String [] args) {
new Foo("CEHSI");
new Foo(20);
}

Java7新增加的“菱形”

前面介绍的Java7新增“菱形”语法,它允许调用构造器时在构造器后使用一对尖括号来代表泛型信息。但如果程序显示指定了泛型构造器中声明的类型形参的实际类型,则不可以使用“菱形”语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Foo<E> {
public <T> Foo(T t){
System.out.println(t);
}
}


public class Generical {
public static void main(String [] args) {
Foo<String> mc1 = new Foo<>(5);
Foo<String> mc2 = new <Integer>Foo<String>(5);
//编译会错误 因为即指定了泛型构造器中的类型形参是Integter类型,又想使用“菱形”语法
Foo<String> mc3 = new <Integer>Foo<>(5);
}
}

设定统配符下限

为了表达A元素与B元素相同或者是B元素的父类,这种约束关系,java允许设定统配符的下限<? super Type> 这个统配符表示它必须是Type本身或者是Type 的父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static <T> T copy(Collection<? super T> dest,Collection<T> src) {
T last = null;
for(T ele : src) {
last = ele;
dest.add(ele);
}
return last;
}
public static void main(String [] args) {
List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
li.add(5);
Integer d = copy(ln,li);
System.out.println(d);
}

泛型方法与方法重载

因为泛型既可以允许设定统配符的上限,也允许设定统配符的下限,但是在Jdk-1.8 之后就不支持。这里的jdk1.8是我现在使用的版本。在jdk1.8之前具体到哪一个版本,没有测试。

1
2
3
4
5
public static <T> void copy(Collection<T> dest,Collection<? extends T> src) {
}
public static <T> T copy(Collection<? super T> dest,Collection<T> src) {
return null;
}

擦除和转换

在严格的泛型代码里面,带泛型声明的类总应该带着类型参数。但是为了与老的JAVA代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型参数。如果没有为这个泛型类指定实际的类型参数,则该类型参数被称作是原始类型,默认是声明该参数的第一个上限类型。

当把一个具有泛型信息的对象赋值给另一个没有泛型信息的变量的时候,所有在尖括号之间的类型信息都将会被扔掉。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Apple <T extends Number>{
T size;
public Apple() {}
public Apple(T size) {
this.size = size;
}
public void setSize(T size) {
this.size = size;
}
public T getSize() {
return this.size;
}
}
1
2
3
4
5
6
7
8
Apple<Integer> a = new Apple<>(6);
//a 的getsize方法返回Integer
Integer as = a.getSize();
//把a 的对象赋值给Apple变量,丢失尖括号里面的里类型信息
Apple b = a;
//b 只能知道size的类型是Number
Number size1 = b.getSize();
//Integer size2 = b.getSize();

可以看到上面的一个示例,我们首先创建了一个Apple的类,是带有泛型声明为Integer的,其类型的上限是Number,这个类型形参用来定义Apple类的size变量,然后又创建了一个Apple的类,该类是没有泛型的,当我们把带有泛型信息的a对象的泛型信息赋值给不带有泛型信息的b对象的时候,即所有尖括号里面的信息都会丢失—–因为Apple 的类型形参的上限是Number类,编译器依然知道b的getsize()方法返回的是Number类型,但是具体是哪一个就不清楚了。

当我们将一个有着泛型类型的类a,赋值给没有泛型类型的类b的时候,就会出现丢失类的变量类型信息的现象,称为擦除。

1
2
3
4
5
6
7
8
9
Apple<Integer> a = new Apple<>(6);
//a 的getsize方法返回Integer
Integer as = a.getSize();
//把a 的对象赋值给Apple变量,丢失尖括号里面的里类型信息
Apple b = a;
//b 只能知道size的类型是Number
Number size1 = b.getSize();
Apple<Double> c = b;
Double size2 = (Double)c.getSize();

我们在将没有泛型类型的类b,赋值给一个带有泛型类型的类c,这时候是可以编译通过的,只是会出现“未经检查的转换”警告。实际上引用的还是Apple的这个类型,当我们尝试通过强制转换把它转换成一个Double的类型的时候,就会出现运行时异常java.lang.ClassCastException

泛型与数组

java5的泛型有一个很重要的设计原则,如果一段代码在编译的时候没有提出“[unchecked]”未经检验的异常的时候,那么程序在运行的时候是不会引发ClassCastException的异常的。

1
2
//这种操作是错误的原因是 数组不能包含类型变量或者类型形参 
List<String> [] test = new List<String>[10];

如果我们把上面的形式改为下面的格式,则为:

1
2
//这个在java 5 中在编译的时候会出现 [unchecked] 异常
List<String> [] test = new List[10];

在java 7 中是允许出现这个情况的。

1
2
3
4
5
List<String> [] test = new ArrayList[10];
List<String> li = new ArrayList<String>();
li.add("2");
test[0] = li;
System.out.println(test[0].get(0));

Java 8 允许使用无上限的泛型通配符,但是在Java 5中,如果使用无上限的泛型通配符是需要进行 强制类型转换的。

1
2
3
4
5
6
7
8
9
10
11
//java允许使用无上限的泛型统配符
List<?> [] test = new ArrayList<?>[10];
List<String> li = new ArrayList<String>();
li.add("2");
List<Integer> li2 = new ArrayList<Integer>();
li2.add(3);
test[0] = li;
test[1] = li2;
System.out.println(test[0].get(0));
System.out.println(test[1].get(0));
}

内网穿透

教程内网穿透
在windows上面设置的时候,需要添加一个远程连接用户

配置一个含有公网ip的linux为代理服务器,配置windows为客户端

教程

配置一个含有公网ip的linux为代理服务器,配置mac os 为客户端

教程

类的加载机制与反射机制

JVM和类

Java命令运行某一个Java程序的时候,该命令将会启动一个Java虚拟机进程,不管该Java程序多么复杂,都处于JVM虚拟机里面,所有的线程、所有的变量都处于同一个进程里面。

JVM终止的条件:

  • 程序正常运行结束
  • 遇到未捕获的异常或错误而结束
  • 平台强制结束了JVM
  • 运行的时候使用了System.exit()和Runtime.getRuntime().exit()代码 处结束程序

类的加载或初始化

如果该类还没有被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该程序进行初始化。

类加载

类的加载是指类加载器将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序使用任何类的时候,系统都会为之创建一个java.lang.Class对象。

class是作为类的二进制数据,通常有以下几种来源:

  • 本地系统加载class文件
  • 从jar包 加载class 文件
  • 通过网络加载class文件
  • 把一个java源文件动态编译,并执行加载
    类加载器通常无须等到使用的时候才加载,虚拟机规范允许系统预先加载某些类

类的连接

当类被加载之后,系统会为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。
类的连接分为 下面三个阶段:

  • 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
  • 准备: 类准备阶段则负责为类的静态Field分配内存,并设置默认初始值
  • 解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化

类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化。在Java类中对静态Field指定初始值有两种方式:

  • 声明静态Field时指定初始值
  • 使用静态初始化块为静态Field指定初始值
    静态初始化块都将被当成类的初始化语句
    类的初始化的步骤:
  1. 先判断一下某一个类是不是加载和连接,先进行加载和连接操作
  2. 判断该类的直接父类有没有被初始化,先初始化其父类。每次先初始化的都是Object类
  3. 假如类中有初始化语句,则系统依次执行这些初始化语句

    类初始化的时机

  • 创建类的实例(new,反射,反序列化)
  • 调用某个类的静态方法
  • 使用某个类或者接口的静态Field ,或者为该Field进行赋值
  • 使用反射方式来强制创建某个类的或者接口的对应java.lang.Class 对象。
  • 初始化某个类的子类
  • 直接使用java.exe 命令来运行某个主类

    类加载器

    类加载器负责将class文件加载到内存中,并且为之生成对应的java.lang.Class对象。开发中很少用到类加载机制,但是了解这个机制,能更好的满足我们的需求。
    一个类只要加载进JVM过一次,就不会在加载。同一个类的定义为(包名-类名)作为标识是一样的。一个对象的唯一标识使用的,同一个类+ 类加载器的编号。只有这三个全部相同了才能属于同一个类。
    当JVM启动的时候,会形成3个类加载器组成的初始类加载器层次结构。
  • Bootstrap ClassLoader:根类加载器
    负责加载java的核心类。
  • Extension ClassLoader: 扩展类加载器
    扩展类加载器负责加载JRE的扩展项目中JAR包的类,通过这种方式,就可以为JAVA扩展核心类以外的新功能,只要我们将自己开发的类打包成JAR包,然后放入JAVA_HOME/jre/lib/ext 路径即可。
  • System ClassLoader:系统类加载器
    他负责在JVM启动的时候加载来自java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类的路径。

    类加载机制

    JVM的类加载机制主要有如下3种:
  • 全盘负责
    就是当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其他Class 也将由该类负责载入。
  • 父类委托
    先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类的时候才尝试从自己的类路径中加载该类。
  • 缓存机制
    缓存机制将会保证所有加载过的Class 都会被缓存,当程序中需要使用某个Class的时候,类加载器先从缓存中搜寻该Class,只有当缓存区不存在该class对象的时候,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。

    自定义类加载器

    开发者通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。可以实现如下功能:
  • 执行代码之前自动验证数字签名
  • 根据用户提供的密码解密代码,从而实现代码混淆来避免反编译Class文件
  • 根据用户需求来动态的加载类
  • 根据应用需求把其他数据以字节码的形式加载到应用中

    URLClassLoader类

    通过该类既可以从本地文件系统获取二进制文件来加载该类,也可以从远程主机获取二进制文件来加载该类。
    主要提供了两构造器:
  • URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径来查询并加载类
  • URLClassLoader(URL[] urls ,ClassLoader parent) 使用指定的父类加载器创建一个ClassLoader对象
    • URL file前缀表示从本地文件系统加载
    • http 前缀表示从互联网通过HTTP 加载访问来加载
    • ftp 为前缀 表明从互联网通过ftp 访问加载

      通过反射查看类的信息

      程序在运行时会出现两种类型,运行时类型与编译时类型。程序在运行的时候接收到外部传入的一个对象,该对象的编译时类型时Object,但是程序又需要调用该对象运行时类型的方法,为了在运行的时候发现对象和类的真实信息,可以通过下面两种方法:
  • 假设在编译时和运行时完全知道类型的具体信息,这种情况下可以通过使用instanceof运算符进行判断,在进行强制类型转换。
  • 在编译的时候根本无法知道该对象和类属于哪些类,程序只能通过运行时信息来发现该对象和类的真实信息,就必须使用反射。

    获取Class 对象

  • 使用Class 类的forName(String clazzName) 静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名
  • 调用某个类的class属性来获取该类对应的Class 对象例如:Person.class
  • 调用某个对象的getClass()方法。该方法是java.lang.Object 类的一个方法
  • 调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所以所有的java对象都可以调用该方法。
    前两种都是根据类来获取指定类的Class对象,第二种方法有如下优点:
  • 安全性。程序在编译阶段就可以检查Class对象是不是存在
  • 程序的性能更好。因为无需调用方法,因此性能更好

    从class中获取信息

    Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,主要使用方式查询API。
    获取构造器的时候无须传入构造器名-同一个类的所有构造器的名字都是相同,所以要确定一个构造器只要指定形参列表即可。

    使用反射生成并操作对象

    创建对象

    通过反射生成对象有两种方式:
  • 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class必须要实现默认的构造器

根据配置文件创建 对象池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package Tool;

import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class test {
//定义一个对象池,前面是对象名,后面是实际的对象
private Map<String, Object> objectPool = new HashMap<String, Object>();
//定一个创建对象的方法
//该对象只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject (String clazzName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName(clazzName);
//使用clazz 默认的构造器创建实例
return clazz.newInstance();
}
//该方法根据指定文件来初始化对象池
//根据配置文件来创建对象
public void initPool(String fileName) {
try {
FileInputStream fis = new FileInputStream(fileName);
Properties pr = new Properties();
pr.load(fis);
for(String name : pr.stringPropertyNames()) {
System.out.println(name);
//每取出一对 key-value对就根据value创建一个对象,根据对象值 填充到对象池中
objectPool.put(name,createObject(pr.getProperty(name)));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("读取"+fileName+"异常");
}

}
public Object getObject(String name) {
return objectPool.get(name);

}
public static void main(String[] args) {
test pf = new test();
pf.initPool("./src/instance.properties");
Object x1 = pf.getObject("a");
Object x2 = pf.getObject("b");
System.out.println(x1.equals(x2));
//System.out.println(pf.getObject("b"));
}
}

  • 先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance()方法来创建该对象对应的实例。
    如果不想利用默认构造器来创建java对象,而是使用指定的构造器来创建java对象,则需要利用Constructor对象,每一个Constructor对应一个构造器。
    这个的使用步骤为:
    • 首先获得一个类的Class
    • 通过Class获取该类对应的constructor
    • 调用Constructor的newInstance()方法来创建Java对象。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //获取class对象
      try {
      Class<?> cla = Class.forName("javax.swing.JFrame");
      Constructor con = cla.getConstructor(String.class);
      //调用方法创建对象
      Object obj = con.newInstance("ceshi");
      System.out.println(obj);
      } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }

第一种方式来创建对象是比较常见的方式,因为在java中很多都是通过读取配置文件信息来创建java对象的。

调用方法

当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或者指定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package Tool;

import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class test {
//定义一个对象池,前面是对象名,后面是实际的对象
private Map<String, Object> objectPool = new HashMap<String, Object>();
private Properties pr = new Properties();
//定一个创建对象的方法
//该对象只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject (String clazzName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName(clazzName);
//使用clazz 默认的构造器创建实例
return clazz.newInstance();
}
//该方法根据指定文件来初始化对象池
//根据配置文件来创建对象
public void initPool(String fileName) {
try {
FileInputStream fis = new FileInputStream(fileName);

pr.load(fis);
for(String name : pr.stringPropertyNames()) {
System.out.println(name);
//每取出一对 key-value对就根据value创建一个对象,根据对象值 填充到对象池中
if(!name.contains("%")) {
objectPool.put(name,createObject(pr.getProperty(name)));
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("读取"+fileName+"异常");
}
}
public void initProperty() {
for(String name : pr.stringPropertyNames()) {
// 每次取出来一个key-value 对,如果key 中包含百分号(%)
//即可认为该key 是用来为对象的Field赋值的
/*
* %的前半为对象名字,后半为Field设置值
* 程序将会调用相对应的setter来为Field设置值
*/
if(name.contains("%")) {
//将配置文件按照%进行分割
String [] objAndProp = name.split("%");
//取出需要设置的Field值的目标对象
Object target = getObject(objAndProp[0]);
//该Field对应的setter方法名为
String mtdName = "set"+objAndProp[1].substring(0,1).toUpperCase()+objAndProp[1].substring(1);
Class<?> targetClass = target.getClass();
//获取该属性对应的setter方法
try {
Method mtd = targetClass.getMethod(mtdName, String.class);
//通过Method 的invoke 方法执行setter 方法
//将config.getProperty(name) 的属性值作为调用setter的方法实参
mtd.invoke(target,pr.getProperty(name));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

public Object getObject(String name) {
return objectPool.get(name);
}
public static void main(String[] args) {
test test = new test();
test.initPool("./src/instance.properties");
test.initProperty();
System.out.println(test.getObject("a"));
}
}

使用Method的invoke()方法来调用对应的方法的时候,Java程序会要求必须有调用该方法的权限,如果某个程序想要调用某个对象的private方法, 那么可以先调用Method方法的如下方法:

  • setAccessible(bool flag) 值为true的时候,说明该Method方法在使用的时候要取消java语言的访问呢检查,值为falser的时候,则要进行权限的检查。该方法不只是属于method ,对于constructor、field都是可以调用的

    访问属性值

  • getField(String name)
  • getFields()
  • getDeclaredField(String name)
  • getDeclaredFields()
  • setXxx(Object obj,Xxx val) 将obj对象的field设置成val值,Xxx对应八种基本类型,val是要设置的对象的值。如果属性类型是引用类型,则取消set后面的Xxx
  • getXxx()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Person1 {
    private String name;
    private int age;
    public String toString() {
    return "Person[name:"+name+",age:"+age+"]";
    }
    }

    public void test5() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
    Person1 p = new Person1();
    Class<Person1> clazz = Person1.class;
    Field nameField = clazz.getDeclaredField("name");
    nameField.setAccessible(true);
    nameField.set(p, "gorge");
    Field ageField = clazz.getDeclaredField("age");
    ageField.setAccessible(true);
    ageField.setInt(p, 12);
    System.out.println(p);
    }

操作数组

在java.lang.reflect包下还提供了一个Arrays类,Arrays对象可以代表所有数组,程序可以通过使用Arrays来动态的创建数组。

  • static Object newInstance(class<?> type, int len) 创建一个类型为type,长度为len的数组
  • static xxx getXxx(Obj array, int index) 返回array数组中第index个元素。其中x x x是各种基本数据类型
  • static void setXxx(Obj array, int index,xxx val)将arrays数组中第index个元素的值设置为val,如果是引用类型则直接使用set()
1
2
3
4
5
6
7
Object arr = Array.newInstance(String.class,4);
//为数组赋值
Array.set(arr,3,"java");
Array.set(arr, 2,"java ee");
Object one = Array.get(arr,2);
Object two = Array.get(arr,3);
System.out.println(one+":"+two);

使用反射生成JDK动态代理

在java的java.lang.reflect 包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。
如果在程序中为一个或多个接口动态的生成实现类,就可以使用proxy来创建动态代理类;如果需要为一个或多个接口动态的创建实例,也可以使用Proxy来创建动态代理实例。

  • static Class<?> getProxyClass(ClassLoader loader,Class <?> … interfaces): 创建一个动态代理类所对应的class对象,该代理类将实现interface 所指定的多个接口。第一个ClassLoade参数指定生成动态代理类的类加载器。
  • static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interface指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。

    通过动态代理类生成动态代理对象

    先生成一个动态代理类,然后在通过动态代理类,来创建动态代理对象
1
2
3
4
5
6
7
//创建一个InvokationHandler对象
InvocationHandler handler = new MyInvokationHandler();
//通过代理类来创建代理对象
Class proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class[] {Person.class});
Constructor ctor = proxyClass.getConstructor(new Class[] {InvocationHandler.class});
Person p2 = (Person)ctor.newInstance(new Object[] {handler});
p2.sayHello("nnd");

直接生成动态代理对象

  • 首先,我们要先创建一个接口
1
2
3
4
public interface Person {
void walk();
void sayHello(String name);
}
  • 然后实现InvocationHandle接口,因为在执行方法的时候,都会被替换成调用该invoke()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//生成动态代理对象
public class MyInvokationHandler implements InvocationHandler {
/*
* 执行动态代理对象的所有方法的时候,都会被替换成执行如下的invoke方法
* 其中:
* proxy:代表动态代理对象 例如:Person.class.getClassLoader()
* method:代表正在执行的方法
* args:代表调用目标方法的时候传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("---正在执行的方法:"+method);
//这里可以判断一下执行的是什么方法,然后对该方法进行 重新编辑
if(method.equals(Person.class.getMethod("walk", null))) {
System.out.println("正在执行walk方法");
}
if(args!=null) {
System.out.println("下面是执行该方法的时候传入的参数:");
for(Object obj:args) {
System.out.println(obj);
}
}else {
System.out.println("该方法没有实际的参数");
}
return null;
}
  • 创建一个动态代理对象
1
2
3
4
5
6
7
8
public static void main(String [] args) {
//创建一个InvokationHandler对象
InvocationHandler handler = new MyInvokationHandler();
//使用指定的InvocationHandler 来生成一个动态代理对象
Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(),new Class[] {Person.class} , handler);
p.walk();
p.sayHello("HELLO");
}

一般我们通过第二种方式创建类的对象就可以了,在编写框架或者底层基础代码的时候,动态代理的作用就非常大了。

动态代理和AOP

在开发一个实际的应用软件系统的时候,总会存在相同的代码段重复出现的情况,这种情况下,如果通过复制粘贴的方式,则对于后期的维护是非常困难的,如果通过将这个方法写出一个方法,然后调用的方式执行,则会使得程序的耦合性提高。因此,我们就可以通过动态代理的方式进行。
具体方式如下:

  • 创建一个对象接口
1
2
3
4
public interface Dog {
void info();
void run();
}
  • 创建接口的实现类
1
2
3
4
5
6
7
8
9
10
public class GunDog implements Dog {
@Override
public void info() {
System.out.println("我是一只猎狗");
}
@Override
public void run() {
System.out.println("我奔跑很快");
}
}
  • 将重复的方法写到一个工具类中
1
2
3
4
5
6
7
8
class DogUtil {
public void method() {
System.out.println("------通用方法一");
}
public void method2() {
System.out.println("------通用方法二");
}
}
  • 实现InvocationHandler方法,并设置主调对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyInvokationHandler implements InvocationHandler {
//需要被代理的对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DogUtil du = new DogUtil();
//执行DogUtil对象中的method方法
du.method();
Object result = method.invoke(target, args);
//执行DogUtil 对象中的method2方法
du.method2();
return result;
}
}
  • 创建代理工厂为指定的target 生成动态代理对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class MyProxyFactory {
    //为指定的target生成动态代理对象
    public static Object getProxy(Object target) {
    MyInvokationHandler handler = new MyInvokationHandler();
    //为handler设置target对象
    handler.setTarget(target);
    //创建并返回一个动态代理
    return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), handler);
    }
    }
  • 测试

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String [] args) {
Dog target = new GunDog();
//以指定target 来创建动态代理对象
Dog dog = (Dog)MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}

当我们使用proxy生成一个动态代理的时候,往往并不会凭空产生一个动态代理,通常都是为指定的目标对象生成动态代理。
这种动态的代理在AOP(面向切面的编程)中被称为AOP代理,AOP代理可以代替目标对象,AOP代理包含了目标对象的全部方法,但是AOP代理中的方法与目标对象的方法存在差异,AOP代理的方法可以在执行目标方法之前或者之后插入一些有用的处理。

反射和泛型

如果Class对应的类暂时未知,则使用Class<?>。通过在反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static <T> T getInstance(Class<T> cls) {
try {
return cls.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public static void main(String [] args) {
//获取实例无需进行类型转换
Date d = getInstance(Date.class);
JFrame f = getInstance(JFrame.class);
}

使用反射来获取泛型信息

对于基本的类型,可以通过泛型,直接查看

1
2
3
4
public int dfsdf ;
Field t = Test.class.getField("dfsdf");
Class<?> TYPE = t.getType();
System.out.println(TYPE);

但是如果该Field是有泛型类型的类型,如Map<String,Integer>类型,则不能准确的得到这个类型。
要想获取泛型类型,则需要先使用

  • getGenericType() 获得实例的泛型类型
    然后在将Type 对象强制类型转换为ParameterizedType 对象,该对象代表被参数化的类型,也就是增加了泛型限制的类型。
  • getRawType(): 返回没有泛型信息的原始类型
  • getActualTypeArguments() 返回泛型参数类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private Map<String,Integer> score;
public static void main(String [] args) throws NoSuchFieldException, SecurityException {
Class<?> clazz = Test.class;
Field f = clazz.getDeclaredField("score");
Class<?> a = f.getType();
//下面将看到输出Map类型,但是map的泛型类型,则获取不到
System.out.println(a);
//获得Field实例f的泛型类型
Type gType = f.getGenericType();
//如果这个类型是ParameterizedType 对象
if(gType instanceof ParameterizedType) {
//强制类型转换
ParameterizedType ptype = (ParameterizedType)gType;
//获取原始类型
Type rType = ptype.getRawType();
System.out.println("原始类型为:"+rType);
//获取泛型类型的泛型参数
Type[] tArgs = ptype.getActualTypeArguments();
System.out.println("泛型类型是:");
for(int i=0;i<tArgs.length;i++) {
System.out.println("第"+i+"个泛型类型是"+tArgs[i]);
}
}else {
System.out.println("获取类型错误");
}
}

注意上面说有的类型都是来自java.lang.reflect包下的。