一个效果非常不错的JAVA数据库连接池
虽然现在用APACHE COMMONS DBCP可以非常方便的建立数据库连接池,
但是像这篇文章把数据库连接池的内部原理写的这么透彻,注视这么完整,
真是非常难得,让开发人员可以更深层次的理解数据库连接池,真是非常感
谢这篇文章的作者。
但是像这篇文章把数据库连接池的内部原理写的这么透彻,注视这么完整,
真是非常难得,让开发人员可以更深层次的理解数据库连接池,真是非常感
谢这篇文章的作者。
1
import
java.sql.Connection;
2
import
java.sql.DatabaseMetaData;
3
import
java.sql.Driver;
4
import
java.sql.DriverManager;
5
import
java.sql.SQLException;
6
import
java.sql.Statement;
7
import
java.util.Enumeration;
8
import
java.util.Vector;
9![]()
10![]()
public
class
ConnectionPool
{
11![]()
12
private String _jdbcDriver = ""; // 数据库驱动
13![]()
14
private String _dbUrl = ""; // 数据 URL
15![]()
16
private String _dbUsername = ""; // 数据库用户名
17![]()
18
private String _dbPassword = ""; // 数据库用户密码
19![]()
20
private String _testTable = ""; // 测试连接是否可用的测试表名,默认没有测试表
21![]()
22
private int _initialConnections = 20; // 连接池的初始大小
23![]()
24
private int _incrementalConnections = 5;// 连接池自动增加的大小
25![]()
26
private int _maxConnections = 50; // 连接池最大的大小
27![]()
28
private Vector _connectionVector = null; // 存放连接池中数据库连接的向量 , 初始时为 null
29![]()
30
// 它中存放的对象为 PooledConnection 型
31![]()
32![]()
/** *//**
33
*
34
* 构造函数
35
*
36
*
37
*
38
* @param jdbcDriver
39
* String JDBC 驱动类串
40
*
41
* @param dbUrl
42
* String 数据库 URL
43
*
44
* @param dbUsername
45
* String 连接数据库用户名
46
*
47
* @param dbPassword
48
* String 连接数据库用户的密码
49
*
50
*
51
*
52
*/
53![]()
54
public ConnectionPool(String jdbcDriver, String dbUrl, String dbUsername,
55![]()
String dbPassword)
{
56
57
this._jdbcDriver = jdbcDriver;
58![]()
59
this._dbUrl = dbUrl;
60![]()
61
this._dbUsername = dbUsername;
62![]()
63
this._dbPassword = dbPassword;
64![]()
65
}
66![]()
67![]()
/** *//**
68
*
69
* 返回连接池的初始大小
70
*
71
*
72
*
73
* @return 初始连接池中可获得的连接数量
74
*
75
*/
76![]()
77![]()
public int getInitialConnections()
{
78![]()
79
return this._initialConnections;
80![]()
81
}
82![]()
83![]()
/** *//**
84
*
85
* 设置连接池的初始大小
86
*
87
*
88
*
89
* @param 用于设置初始连接池中连接的数量
90
*
91
*/
92![]()
93![]()
public void setInitialConnections(int initialConnections)
{
94![]()
95
this._initialConnections = initialConnections;
96![]()
97
}
98![]()
99![]()
/** *//**
100
*
101
* 返回连接池自动增加的大小
102
*
103
*
104
*
105
* @return 连接池自动增加的大小
106
*
107
*/
108![]()
109![]()
public int getIncrementalConnections()
{
110![]()
111
return this._incrementalConnections;
112![]()
113
}
114![]()
115![]()
/** *//**
116
*
117
* 设置连接池自动增加的大小
118
*
119
* @param 连接池自动增加的大小
120
*
121
*/
122![]()
123![]()
public void setIncrementalConnections(int incrementalConnections)
{
124![]()
125
this._incrementalConnections = incrementalConnections;
126![]()
127
}
128![]()
129![]()
/** *//**
130
*
131
* 返回连接池中最大的可用连接数量
132
*
133
* @return 连接池中最大的可用连接数量
134
*
135
*/
136![]()
137![]()
public int getMaxConnections()
{
138![]()
139
return this._maxConnections;
140![]()
141
}
142![]()
143![]()
/** *//**
144
*
145
* 设置连接池中最大可用的连接数量
146
*
147
*
148
*
149
* @param 设置连接池中最大可用的连接数量值
150
*
151
*/
152![]()
153![]()
public void setMaxConnections(int maxConnections)
{
154![]()
155
this._maxConnections = maxConnections;
156![]()
157
}
158![]()
159![]()
/** *//**
160
*
161
* 获取测试数据库表的名字
162
*
163
*
164
*
165
* @return 测试数据库表的名字
166
*
167
*/
168![]()
169![]()
public String getTestTable()
{
170![]()
171
return this._testTable;
172![]()
173
}
174![]()
175![]()
/** *//**
176
*
177
* 设置测试表的名字
178
*
179
* @param testTable
180
* String 测试表的名字
181
*
182
*/
183![]()
184![]()
public void setTestTable(String testTable)
{
185![]()
186
this._testTable = testTable;
187![]()
188
}
189![]()
190![]()
/** *//**
191
*
192
*
193
*
194
* 创建一个数据库连接池,连接池中的可用连接的数量采用类成员
195
*
196
* initialConnections 中设置的值
197
*
198
*/
199![]()
200![]()
public synchronized void createPool() throws Exception
{
201![]()
202
// 确保连接池没有创建
203![]()
204
// 如果连接池己经创建了,保存连接的向量 connections 不会为空
205![]()
206![]()
if (_connectionVector != null)
{
207![]()
208
return; // 如果己经创建,则返回
209![]()
210
}
211![]()
212
// 实例化 JDBC Driver 中指定的驱动类实例
213![]()
214
Driver driver = (Driver) (Class.forName(this._jdbcDriver).newInstance());
215![]()
216
DriverManager.registerDriver(driver); // 注册 JDBC 驱动程序
217![]()
218
// 创建保存连接的向量 , 初始时有 0 个元素
219![]()
220
_connectionVector = new Vector();
221![]()
222
// 根据 initialConnections 中设置的值,创建连接。
223![]()
224
createConnections(this._initialConnections);
225![]()
226
System.out.println(" 数据库连接池创建成功! ");
227![]()
228
}
229![]()
230![]()
/** *//**
231
*
232
* 创建由 numConnections 指定数目的数据库连接 , 并把这些连接
233
*
234
* 放入 connections 向量中
235
*
236
*
237
*
238
* @param numConnections
239
* 要创建的数据库连接的数目
240
*
241
*/
242![]()
243
@SuppressWarnings("unchecked")
244![]()
private void createConnections(int numConnections) throws SQLException
{
245![]()
246
// 循环创建指定数目的数据库连接
247![]()
248![]()
for (int x = 0; x < numConnections; x++)
{
249![]()
250
// 是否连接池中的数据库连接的数量己经达到最大?最大值由类成员 maxConnections
251![]()
252
// 指出,如果 maxConnections 为 0 或负数,表示连接数量没有限制。
253![]()
254
// 如果连接数己经达到最大,即退出。
255![]()
256
if (this._maxConnections > 0
257![]()
&& this._connectionVector.size() >= this._maxConnections)
{
258![]()
259
break;
260![]()
261
}
262![]()
263
// add a new PooledConnection object to connections vector
264![]()
265
// 增加一个连接到连接池中(向量 connections 中)
266![]()
267![]()
try
{
268![]()
269
_connectionVector.addElement(new PooledConnection(newConnection()));
270![]()
271![]()
} catch (SQLException e)
{
272![]()
273
System.out.println(" 创建数据库连接失败! " + e.getMessage());
274![]()
275
throw new SQLException();
276![]()
277
}
278![]()
279
System.out.println(" 数据库连接己创建 
");
280![]()
281
}
282![]()
283
}
284![]()
285![]()
/** *//**
286
*
287
* 创建一个新的数据库连接并返回它
288
*
289
*
290
*
291
* @return 返回一个新创建的数据库连接
292
*
293
*/
294![]()
295![]()
private Connection newConnection() throws SQLException
{
296![]()
297
// 创建一个数据库连接
298![]()
299
Connection conn = DriverManager.getConnection(_dbUrl, _dbUsername,
300
_dbPassword);
301![]()
302
// 如果这是第一次创建数据库连接,即检查数据库,获得此数据库允许支持的
303![]()
304
// 最大客户连接数目
305![]()
306
// connections.size()==0 表示目前没有连接己被创建
307![]()
308![]()
if (_connectionVector.size() == 0)
{
309![]()
310
DatabaseMetaData metaData = conn.getMetaData();
311![]()
312
int driverMaxConnections = metaData.getMaxConnections();
313![]()
314
// 数据库返回的 driverMaxConnections 若为 0 ,表示此数据库没有最大
315![]()
316
// 连接限制,或数据库的最大连接限制不知道
317![]()
318
// driverMaxConnections 为返回的一个整数,表示此数据库允许客户连接的数目
319![]()
320
// 如果连接池中设置的最大连接数量大于数据库允许的连接数目 , 则置连接池的最大
321![]()
322
// 连接数目为数据库允许的最大数目
323![]()
324
if (driverMaxConnections > 0
325![]()
&& this._maxConnections > driverMaxConnections)
{
326![]()
327
this._maxConnections = driverMaxConnections;
328![]()
329
}
330![]()
331
}
332![]()
333
return conn; // 返回创建的新的数据库连接
334![]()
335
}
336![]()
337![]()
/** *//**
338
*
339
* 通过调用 getFreeConnection() 函数返回一个可用的数据库连接 ,
340
*
341
* 如果当前没有可用的数据库连接,并且更多的数据库连接不能创
342
*
343
* 建(如连接池大小的限制),此函数等待一会再尝试获取。
344
*
345
*
346
*
347
* @return 返回一个可用的数据库连接对象
348
*
349
*/
350![]()
351![]()
public synchronized Connection getConnection() throws SQLException
{
352![]()
353
// 确保连接池己被创建
354![]()
355![]()
if (_connectionVector == null)
{
356![]()
357
return null; // 连接池还没创建,则返回 null
358![]()
359
}
360![]()
361
Connection conn = getFreeConnection(); // 获得一个可用的数据库连接
362![]()
363
// 如果目前没有可以使用的连接,即所有的连接都在使用中
364![]()
365![]()
while (conn == null)
{
366![]()
367
// 等一会再试
368![]()
369
wait(250);
370![]()
371
conn = getFreeConnection(); // 重新再试,直到获得可用的连接,如果
372![]()
373
// getFreeConnection() 返回的为 null
374![]()
375
// 则表明创建一批连接后也不可获得可用连接
376![]()
377
}
378![]()
379
return conn;// 返回获得的可用的连接
380![]()
381
}
382![]()
383![]()
/** *//**
384
*
385
* 本函数从连接池向量 connections 中返回一个可用的的数据库连接,如果
386
*
387
* 当前没有可用的数据库连接,本函数则根据 incrementalConnections 设置
388
*
389
* 的值创建几个数据库连接,并放入连接池中。
390
*
391
* 如果创建后,所有的连接仍都在使用中,则返回 null
392
*
393
* @return 返回一个可用的数据库连接
394
*
395
*/
396![]()
397![]()
private Connection getFreeConnection() throws SQLException
{
398![]()
399
// 从连接池中获得一个可用的数据库连接
400![]()
401
Connection conn = findFreeConnection();
402![]()
403![]()
if (conn == null)
{
404![]()
405
// 如果目前连接池中没有可用的连接
406![]()
407
// 创建一些连接
408![]()
409
createConnections(_incrementalConnections);
410![]()
411
// 重新从池中查找是否有可用连接
412![]()
413
conn = findFreeConnection();
414![]()
415![]()
if (conn == null)
{
416![]()
417
// 如果创建连接后仍获得不到可用的连接,则返回 null
418![]()
419
return null;
420![]()
421
}
422![]()
423
}
424![]()
425
return conn;
426![]()
427
}
428![]()
429![]()
/** *//**
430
*
431
* 查找连接池中所有的连接,查找一个可用的数据库连接,
432
*
433
* 如果没有可用的连接,返回 null
434
*
435
*
436
*
437
* @return 返回一个可用的数据库连接
438
*
439
*/
440![]()
441![]()
private Connection findFreeConnection() throws SQLException
{
442![]()
443
Connection conn = null;
444![]()
445
PooledConnection pConn = null;
446![]()
447
// 获得连接池向量中所有的对象
448![]()
449
Enumeration enumerate = _connectionVector.elements();
450![]()
451
// 遍历所有的对象,看是否有可用的连接
452![]()
453![]()
while (enumerate.hasMoreElements())
{
454![]()
455
pConn = (PooledConnection) enumerate.nextElement();
456![]()
457![]()
if (!pConn.isBusy())
{
458![]()
459
// 如果此对象不忙,则获得它的数据库连接并把它设为忙
460![]()
461
conn = pConn.getConnection();
462![]()
463
pConn.setBusy(true);
464![]()
465
// 测试此连接是否可用
466![]()
467![]()
if (!testConnection(conn))
{
468![]()
469
// 如果此连接不可再用了,则创建一个新的连接,
470![]()
471
// 并替换此不可用的连接对象,如果创建失败,返回 null
472![]()
473![]()
try
{
474![]()
475
conn = newConnection();
476![]()
477![]()
} catch (SQLException e)
{
478![]()
479
System.out.println(" 创建数据库连接失败! " + e.getMessage());
480![]()
481
return null;
482![]()
483
}
484![]()
485
pConn.setConnection(conn);
486![]()
487
}
488![]()
489
break; // 己经找到一个可用的连接,退出
490![]()
491
}
492![]()
493
}
494![]()
495
return conn;// 返回找到到的可用连接
496![]()
497
}
498![]()
499![]()
/** *//**
500
*
501
* 测试一个连接是否可用,如果不可用,关掉它并返回 false
502
*
503
* 否则可用返回 true
504
*
505
*
506
*
507
* @param conn
508
* 需要测试的数据库连接
509
*
510
* @return 返回 true 表示此连接可用, false 表示不可用
511
*
512
*/
513![]()
514![]()
private boolean testConnection(Connection conn)
{
515![]()
516![]()
try
{
517![]()
518
// 判断测试表是否存在
519![]()
520![]()
if (_testTable.equals(""))
{
521![]()
522
// 如果测试表为空,试着使用此连接的 setAutoCommit() 方法
523![]()
524
// 来判断连接否可用(此方法只在部分数据库可用,如果不可用 ,
525![]()
526
// 抛出异常)。注意:使用测试表的方法更可靠
527![]()
528
conn.setAutoCommit(true);
529![]()
530![]()
} else
{// 有测试表的时候使用测试表测试
531![]()
532
// check if this connection is valid
533![]()
534
Statement stmt = conn.createStatement();
535![]()
536
stmt.execute("select count(*) from " + _testTable);
537![]()
538
}
539![]()
540![]()
} catch (SQLException e)
{
541![]()
542
// 上面抛出异常,此连接己不可用,关闭它,并返回 false;
543![]()
544
closeConnection(conn);
545![]()
546
return false;
547![]()
548
}
549![]()
550
// 连接可用,返回 true
551![]()
552
return true;
553![]()
554
}
555![]()
556![]()
/** *//**
557
*
558
* 此函数返回一个数据库连接到连接池中,并把此连接置为空闲。
559
*
560
* 所有使用连接池获得的数据库连接均应在不使用此连接时返回它。
561
*
562
*
563
*
564
* @param 需返回到连接池中的连接对象
565
*
566
*/
567![]()
568![]()
public void returnConnection(Connection conn)
{
569![]()
570
// 确保连接池存在,如果连接没有创建(不存在),直接返回
571![]()
572![]()
if (_connectionVector == null)
{
573![]()
574
System.out.println(" 连接池不存在,无法返回此连接到连接池中 !");
575![]()
576
return;
577![]()
578
}
579![]()
580
PooledConnection pConn = null;
581![]()
582
Enumeration enumerate = _connectionVector.elements();
583![]()
584
// 遍历连接池中的所有连接,找到这个要返回的连接对象
585![]()
586![]()
while (enumerate.hasMoreElements())
{
587![]()
588
pConn = (PooledConnection) enumerate.nextElement();
589![]()
590
// 先找到连接池中的要返回的连接对象
591![]()
592![]()
if (conn == pConn.getConnection())
{
593![]()
594
// 找到了 , 设置此连接为空闲状态
595![]()
596
pConn.setBusy(false);
597![]()
598
break;
599![]()
600
}
601![]()
602
}
603![]()
604
}
605![]()
606![]()
/** *//**
607
*
608
* 刷新连接池中所有的连接对象
609
*
610
*
611
*
612
*/
613![]()
614![]()
public synchronized void refreshConnections() throws SQLException
{
615![]()
616
// 确保连接池己创新存在
617![]()
618![]()
if (_connectionVector == null)
{
619![]()
620
System.out.println(" 连接池不存在,无法刷新 !");
621![]()
622
return;
623![]()
624
}
625![]()
626
PooledConnection pConn = null;
627![]()
628
Enumeration enumerate = _connectionVector.elements();
629![]()
630![]()
while (enumerate.hasMoreElements())
{
631![]()
632
// 获得一个连接对象
633![]()
634
pConn = (PooledConnection) enumerate.nextElement();
635![]()
636
// 如果对象忙则等 5 秒 ,5 秒后直接刷新
637![]()
638![]()
if (pConn.isBusy())
{
639![]()
640
wait(5000); // 等 5 秒
641![]()
642
}
643![]()
644
// 关闭此连接,用一个新的连接代替它。
645![]()
646
closeConnection(pConn.getConnection());
647![]()
648
pConn.setConnection(newConnection());
649![]()
650
pConn.setBusy(false);
651![]()
652
}
653![]()
654
}
655![]()
656![]()
/** *//**
657
*
658
* 关闭连接池中所有的连接,并清空连接池。
659
*
660
*/
661![]()
662![]()
public synchronized void closeConnectionPool() throws SQLException
{
663![]()
664
// 确保连接池存在,如果不存在,返回
665![]()
666![]()
if (_connectionVector == null)
{
667![]()
668
System.out.println(" 连接池不存在,无法关闭 !");
669![]()
670
return;
671![]()
672
}
673![]()
674
PooledConnection pConn = null;
675![]()
676
Enumeration enumerate = _connectionVector.elements();
677![]()
678![]()
while (enumerate.hasMoreElements())
{
679![]()
680
pConn = (PooledConnection) enumerate.nextElement();
681![]()
682
// 如果忙,等 5 秒
683![]()
684![]()
if (pConn.isBusy())
{
685![]()
686
wait(5000); // 等 5 秒
687![]()
688
}
689![]()
690
// 5 秒后直接关闭它
691![]()
692
closeConnection(pConn.getConnection());
693![]()
694
// 从连接池向量中删除它
695![]()
696
_connectionVector.removeElement(pConn);
697![]()
698
}
699![]()
700
// 置连接池为空
701![]()
702
_connectionVector = null;
703![]()
704
}
705![]()
706![]()
/** *//**
707
*
708
* 关闭一个数据库连接
709
*
710
*
711
*
712
* @param 需要关闭的数据库连接
713
*
714
*/
715![]()
716![]()
private void closeConnection(Connection conn)
{
717![]()
718![]()
try
{
719![]()
720
conn.close();
721![]()
722![]()
} catch (SQLException e)
{
723![]()
724
System.out.println(" 关闭数据库连接出错: " + e.getMessage());
725![]()
726
}
727![]()
728
}
729![]()
730![]()
/** *//**
731
*
732
* 使程序等待给定的毫秒数
733
*
734
*
735
*
736
* @param 给定的毫秒数
737
*
738
*/
739![]()
740![]()
private void wait(int mSeconds)
{
741![]()
742![]()
try
{
743![]()
744
Thread.sleep(mSeconds);
745![]()
746![]()
} catch (InterruptedException e)
{
747![]()
748
}
749![]()
750
}
751![]()
752![]()
/** *//**
753
*
754
*
755
*
756
* 内部使用的用于保存连接池中连接对象的类
757
*
758
* 此类中有两个成员,一个是数据库的连接,另一个是指示此连接是否
759
*
760
* 正在使用的标志。
761
*
762
*/
763![]()
764![]()
class PooledConnection
{
765![]()
766
Connection connection = null;// 数据库连接
767![]()
768
boolean busy = false; // 此连接是否正在使用的标志,默认没有正在使用
769![]()
770
// 构造函数,根据一个 Connection 构告一个 PooledConnection 对象
771![]()
772![]()
public PooledConnection(Connection connection)
{
773![]()
774
this.connection = connection;
775![]()
776
}
777![]()
778
// 返回此对象中的连接
779![]()
780![]()
public Connection getConnection()
{
781![]()
782
return connection;
783![]()
784
}
785![]()
786
// 设置此对象的,连接
787![]()
788![]()
public void setConnection(Connection connection)
{
789![]()
790
this.connection = connection;
791![]()
792
}
793![]()
794
// 获得对象连接是否忙
795![]()
796![]()
public boolean isBusy()
{
797![]()
798
return busy;
799![]()
800
}
801![]()
802
// 设置对象的连接正在忙
803![]()
804![]()
public void setBusy(boolean busy)
{
805![]()
806
this.busy = busy;
807![]()
808
}
809![]()
810
}
811![]()
812
}
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
84
85
86
87
88
89
90
91
92
93

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

110
111
112
113
114
115
116
117
118
119
120
121
122
123

124
125
126
127
128
129
130
131
132
133
134
135
136
137

138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200

201
202
203
204
205
206

207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244

245
246
247
248

249
250
251
252
253
254
255
256
257

258
259
260
261
262
263
264
265
266
267

268
269
270
271

272
273
274
275
276
277
278
279


280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295

296
297
298
299
300
301
302
303
304
305
306
307
308

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351

352
353
354
355

356
357
358
359
360
361
362
363
364
365

366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397

398
399
400
401
402
403

404
405
406
407
408
409
410
411
412
413
414
415

416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441

442
443
444
445
446
447
448
449
450
451
452
453

454
455
456
457

458
459
460
461
462
463
464
465
466
467

468
469
470
471
472
473

474
475
476
477

478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514

515
516

517
518
519
520

521
522
523
524
525
526
527
528
529
530

531
532
533
534
535
536
537
538
539
540

541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568

569
570
571
572

573
574
575
576
577
578
579
580
581
582
583
584
585
586

587
588
589
590
591
592

593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614

615
616
617
618

619
620
621
622
623
624
625
626
627
628
629
630

631
632
633
634
635
636
637
638

639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662

663
664
665
666

667
668
669
670
671
672
673
674
675
676
677
678

679
680
681
682
683
684

685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716

717
718

719
720
721
722

723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740

741
742

743
744
745
746

747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764

765
766
767
768
769
770
771
772

773
774
775
776
777
778
779
780

781
782
783
784
785
786
787
788

789
790
791
792
793
794
795
796

797
798
799
800
801
802
803
804

805
806
807
808
809
810
811
812