0%

主从复制的几种方式

同步复制

所谓的同步复制,意思是 master 的变化,必须等待 slave-1,slave-2,…,slave-n 完成后才能返回。

这样,显然不可取,也不是 MYSQL 复制的默认设置。比如,在 WEB 前端页面上,用户增加了条记录,需要等待很长时间。

异步复制

如同 AJAX 请求一样。master 只需要完成自己的数据库操作即可。至于 slaves 是否收到二进制日志,是否完成操作,不用关心。MYSQL 的默认设置。

半同步复制

master 只保证 slaves 中的一个操作成功,就返回,其他 slave 不管。这个功能,是由 google 为 MYSQL 引入的。

当 master 的二进制日志每产生一个事件,都需要发往 slave,如果我们有 N 个 slave,那是发 N 次,还是只发一次?

如果只发一次,发给了 slave-1,那 slave-2,slave-3,…它们怎么办?

显然,应该发 N 次。实际上,在 MYSQL master 内部,维护 N 个线程,每一个线程负责将二进制日志文件发往对应的 slave。master 既要负责写操作,还的维护 N 个线程,负担会很重。可以这样,slave-1 是 master 的从,slave-1 又是 slave-2,slave-3,…的主,同时 slave-1 不再负责 select。slave-1 将 master 的复制线程的负担,转移到自己的身上。这就是所谓的多级复制的概念。

计算机网络的基本知识

两台计算机设备通过网络进行通信需要满足一些必然的条件: 1. IP地址 2. 协议 3. 端口号

TCP/IP模型是目前应用最为广泛的网络模型.

TCP/IP协议是传输层协议

常见的应用层协议有:

  • DNS 域名系统(Domain Name System,DNS):用于实现网络设备名字到IP地址映射的网络服务。
  • HTTP 超文本传输协议 用于实现WWW服务
  • FTP 文件传输协议
  • TELNET 远程登录协议
  • SMTP 简单邮件传输协议
  • 简单网络管理协议(simple Network Management Protocol,SNMP):用于管理与监视网络设备。

端口

  1. 用来区分不同的应用程序
  2. 端口号的范围为065535,其中01023是系统保留端口
  3. 一个应用可以绑定多个端口:

  1. IP地址和端口号组成了所谓的Socket, Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础
  2. 常用的端口号有: http 80 ftp 21 telnet 23

Java对网路通信的支持

  • InetAddress
    用于标示网络上的硬件资源,也就是IP地址
  • URL
    统一资源定位符 通过URL可以直接读取或写入网络上的数据
  • Socket
    使用TCP协议实现网络通信的Socket相关的类
  • Datagram
    使用UDP协议,将数据保存在数据报中,通过网络进行通信.

InetAddress

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取本机的InetAddress实例
InetAddress address = InetAddress.getLocalHost();
System.out.println("计算名:" + address.getHostName());
System.out.println("IP地址:" + address.getHostAddress());
byte[] bytes = address.getAddress();// 获取字节数组形式的IP地址
System.out.println("字节数组形式的IP:" + Arrays.toString(bytes));
System.out.println(address);// 直接输出InetAddress对象

// 根据机器名获取InetAddress实例
// InetAddress address2=InetAddress.getByName("laurenyang");
InetAddress address2 = InetAddress.getByName("1.1.1.10");
System.out.println("计算名:" + address2.getHostName());
System.out.println("IP地址:" + address2.getHostAddress());

Socket通信与Socket类

TCP协议是面向连接 可靠的 有序的, 以字节流的方式发送数据

基于TCP协议实现网络通信的类

  • 客户端的Socket类
  • 服务端的ServerSocket类

Client.java

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
try {
//1.创建客户端Socket,指定服务器地址和端口
Socket socket=new Socket("localhost", 8888);
//2.获取输出流,向服务器端发送信息
OutputStream os=socket.getOutputStream();//字节输出流
PrintWriter pw=new PrintWriter(os);//将输出流包装为打印流
pw.write("用户名:alice;密码:789");
pw.flush();
socket.shutdownOutput();//关闭输出流
//3.获取输入流,并读取服务器端的响应信息
InputStream is=socket.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(is));
String info=null;
while((info=br.readLine())!=null){
System.out.println("我是客户端,服务器说:"+info);
}
//4.关闭资源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

Server.java

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
/*
* 基于TCP协议的Socket通信,实现用户登陆
* 服务器端
*/
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket=new ServerSocket(8888);
Socket socket=null;
//记录客户端的数量
int count=0;
System.out.println("***服务器即将启动,等待客户端的连接***");
//循环监听等待客户端的连接
while(true){
//调用accept()方法开始监听,等待客户端的连接
socket=serverSocket.accept();
//创建一个新的线程
ServerThread serverThread=new ServerThread(socket);
//启动线程
serverThread.start();

count++;//统计客户端的数量
System.out.println("客户端的数量:"+count);
InetAddress address=socket.getInetAddress();
System.out.println("当前客户端的IP:"+address.getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}

ServerThread.java

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
/*
* 服务器线程处理类
*/
public class ServerThread extends Thread {
// 和本线程相关的Socket
Socket socket = null;

public ServerThread(Socket socket) {
this.socket = socket;
}

//线程执行的操作,响应客户端的请求
public void run(){
InputStream is=null;
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
PrintWriter pw=null;
try {
//获取输入流,并读取客户端信息
is = socket.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String info=null;
while((info=br.readLine())!=null){//循环读取客户端的信息
System.out.println("我是服务器,客户端说:"+info);
}
socket.shutdownInput();//关闭输入流
//获取输出流,响应客户端的请求
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("欢迎您!");
pw.flush();//调用flush()方法将缓冲输出
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//关闭资源
try {
if(pw!=null)
pw.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

多线程Socket通信优化

  1. 完成新客户端的绑定,启动多线程时, 设置优先级, 为设置优先级可能会导致运行时速度非常慢, 可降低优先级
  2. 对于同一个Socket, 如果关闭了输出流,则与该输出流关联的Socket也会被关闭,所以一般不用关闭流, 直接关闭socket即可.
  3. 使用TCP通信传输对象
1
2
3
4
5
6
7
//使用ObjectOutputStream 对象序列化流,传递对象
OutputStream os=socket.getOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(os);
User user=new User();
oos.writeObject(user);
socket.shutdownOutput();
...
  1. 使用TCP通信传输文件
1
2
3
4
5
6
7
BufferedOutputStream fos=new BufferedOutputStream(new FileOutputStream(file));
byte[] buf =new byte[1024];
int len;
while((len=clientInputStream.read(buf))!=-1){
fos.write(buf,0,len);
fos.flush();
}

UDP协议与Datagram

UDP协议(用户数据报协议)是无连接 不可靠 无序的. UDP协议以数据报作为数据传输的载体.
进行数据传输时,首先要将传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的Socket(主机地址和端口号),然后再讲数据报发送出去.

  • DatagramPacket: 表示数据报包
  • DatagramSocket: 进行端对端通信的类

UDPClient.java

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
/*
* 向服务器端发送数据
*/
//1.定义服务器的地址、端口号、数据
InetAddress address=InetAddress.getByName("localhost");
int port=8800;
byte[] data="用户名:admin;密码:123".getBytes();
//2.创建数据报,包含发送的数据信息
DatagramPacket packet=new DatagramPacket(data, data.length, address, port);
//3.创建DatagramSocket对象
DatagramSocket socket=new DatagramSocket();
//4.向服务器端发送数据报
socket.send(packet);

/*
* 接收服务器端响应的数据
*/
//1.创建数据报,用于接收服务器端响应的数据
byte[] data2=new byte[1024];
DatagramPacket packet2=new DatagramPacket(data2, data2.length);
//2.接收服务器响应的数据
socket.receive(packet2);
//3.读取数据
String reply=new String(data2, 0, packet2.getLength());
System.out.println("我是客户端,服务器说:"+reply);
//4.关闭资源
socket.close();

UDPServer.java

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
/*
* 接收客户端发送的数据
*/
//1.创建服务器端DatagramSocket,指定端口
DatagramSocket socket=new DatagramSocket(8800);
//2.创建数据报,用于接收客户端发送的数据
byte[] data =new byte[1024];//创建字节数组,指定接收的数据包的大小
DatagramPacket packet=new DatagramPacket(data, data.length);
//3.接收客户端发送的数据
System.out.println("****服务器端已经启动,等待客户端发送数据");
socket.receive(packet);//此方法在接收到数据报之前会一直阻塞
//4.读取数据
String info=new String(data, 0, packet.getLength());
System.out.println("我是服务器,客户端说:"+info);

/*
* 向客户端响应数据
*/
//1.定义客户端的地址、端口号、数据
InetAddress address=packet.getAddress();
int port=packet.getPort();
byte[] data2="欢迎您!".getBytes();
//2.创建数据报,包含响应的数据信息
DatagramPacket packet2=new DatagramPacket(data2, data2.length, address, port);
//3.响应客户端
socket.send(packet2);
//4.关闭资源
socket.close();

URL

参见 Android 网路编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* URL常用方法
*/
try {
//创建一个URL实例
URL imooc=new URL("http://www.imooc.com");
//?后面表示参数,#后面表示锚点
URL url=new URL(imooc, "/index.html?username=tom#test");
System.out.println("协议:"+url.getProtocol());
System.out.println("主机:"+url.getHost());
//如果未指定端口号,则使用默认的端口号,此时getPort()方法返回值为-1
System.out.println("端口:"+url.getPort());
System.out.println("文件路径:"+url.getPath());
System.out.println("文件名:"+url.getFile());
System.out.println("相对路径:"+url.getRef());
System.out.println("查询字符串:"+url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
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
/*
* 使用URL读取网页内容
*/
try {
//创建一个URL实例
URL url = new URL("http://www.baidu.com");
//通过URL的openStream方法获取URL对象所表示的资源的字节输入流
InputStream is = url.openStream();
//将字节输入流转换为字符输入流
InputStreamReader isr = new InputStreamReader(is, "utf-8");
//为字符输入流添加缓冲
BufferedReader br = new BufferedReader(isr);
String data = br.readLine();//读取数据
while (data != null) {//循环读取数据
System.out.println(data);//输出数据
data = br.readLine();
}
br.close();
isr.close();
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

HttpURLConnection

参见 Android 网路编程

JDBC的使用步骤

注册驱动 只做一次

无需手动注册,DriverManager会通过SPI注入

建立连接Connection

Connection conn = DriverManager.getConnection(url, user, password);

url格式:
JDBC:子协议:子名称//主机名:端口/数据库名?属性名=属性值&…
User,password可以用“属性名=属性值”方式告诉数据库;
其他参数如:useUnicode=true&characterEncoding=GBK

创建执行SQL的语句Statement

Statement

1
2
Statement st = conn.createStatement();  
st.executeQuery(sql);

PreparedStatement

1
2
3
4
String sql = “select * from table_name where col_name=?”;  
PreparedStatement ps = conn.preparedStatement(sql);
ps.setString(1, “col_value”);
ps.executeQuery();

处理执行结果ResultSet

1
2
3
4
5
6
ResultSet rs = statement.executeQuery(sql);  
While(rs.next()){
rs.getString(“col_name”);
rs.getInt(“col_name”);
//…
}

释放资源

释放ResultSet, Statement,Connection.

数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

使用JDBC来实现CRUD的操作

建表

在这里定义了一个用户信息表

1
2
3
4
5
6
7
CREATE TABLE `tb_user_info` (
`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(10) DEFAULT NULL COMMENT '姓名',
`gender` enum('男','女') DEFAULT NULL COMMENT '性别',
`birthday` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '生日',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT='用户信息表'

Entity

与该表对应的Entity为:

JDBC的CRUD

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
public class JDBCDemo {
/**
* database connection
*/
private Connection connection;

public Connection connection(final String url, final String name, final String password) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, name, password);
this.connection = connection;
return connection;
}

public List<UserInfo> list(int p, int ps) throws SQLException {
int start = p * ps;
PreparedStatement preparedStatement = connection.prepareStatement("select * from tb_user_info limit " + start + "," + ps);
ResultSet resultSet = preparedStatement.executeQuery();
ArrayList<UserInfo> list = new ArrayList<UserInfo>();
while (resultSet.next()) {
int userId = resultSet.getInt("user_id");
String name = resultSet.getString("name");
Timestamp birthday = resultSet.getTimestamp("birthday");
String gender = resultSet.getString("gender");
UserInfo userInfo = new UserInfo(userId, name, gender, birthday);
list.add(userInfo);
}
return list;
}

public List<UserInfo> findByName(String name) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("select * from tb_user_info where name = ?");
preparedStatement.setString(1, name);
ResultSet resultSet = preparedStatement.executeQuery();
ArrayList<UserInfo> list = new ArrayList<UserInfo>();
while (resultSet.next()) {
int userId = resultSet.getInt("user_id");
Timestamp birthday = resultSet.getTimestamp("birthday");
String gender = resultSet.getString("gender");
UserInfo userInfo = new UserInfo(userId, name, gender, birthday);
list.add(userInfo);
}
return list;
}

public int addUser(UserInfo userInfo) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("insert into tb_user_info values(default,?,?,?)");
preparedStatement.setString(1, userInfo.getName());
preparedStatement.setString(2, userInfo.getGender());
preparedStatement.setTimestamp(3, userInfo.getBirthday());
int influenceLines = preparedStatement.executeUpdate();
return influenceLines;
}

public int delete(int userId) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("delete from tb_user_info where user_id = ?");
preparedStatement.setInt(1, userId);
int influenceLines = preparedStatement.executeUpdate();
return influenceLines;
}

public int update(UserInfo userInfo) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("update tb_user_info set name=? , gender = ? , birthday = ? where user_id = ?");
preparedStatement.setString(1, userInfo.getName());
preparedStatement.setString(2, userInfo.getGender());
preparedStatement.setTimestamp(3, userInfo.getBirthday());
preparedStatement.setInt(4, userInfo.getUserId());
int influenceLines = preparedStatement.executeUpdate();
return influenceLines;
}
}

测试样例:

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
JDBCDemo jdbcDemo = new JDBCDemo();
jdbcDemo.connection("jdbc:mysql://127.0.0.1:3306/test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=UTF-8",
"root", "123456");
UserInfo obama = new UserInfo(0, "奥巴马", "男", new Timestamp(System.currentTimeMillis()));
UserInfo cirali = new UserInfo(0, "希拉里", "女", new Timestamp(System.currentTimeMillis()));
UserInfo trappes = new UserInfo(0, "特朗普", "女", new Timestamp(System.currentTimeMillis()));
System.out.println("[添加 奥巴马]");
jdbcDemo.addUser(obama);
System.out.println("[添加 希拉里]");

jdbcDemo.addUser(cirali);
System.out.println("[添加 特朗普]");
jdbcDemo.addUser(trappes);

System.out.println("\n 输出 所有用户信息:");
List<UserInfo> list = jdbcDemo.list(0, 10);
if (list != null) {
for (UserInfo userInfo : list) {
System.out.println(userInfo);
}
}

System.out.println("根据姓名查询用户信息");
UserInfo tUserIndo = null;
List<UserInfo> resultList = jdbcDemo.findByName("特朗普");
if (resultList != null) {
tUserIndo = resultList.get(0);
}
System.out.println(tUserIndo);

tUserIndo.setGender("男");

System.out.println("[更新]");
jdbcDemo.update(tUserIndo);

System.out.println(tUserIndo);
System.out.println("\n 输出 所有用户信息:");
list = jdbcDemo.list(0, 10);
if (list != null) {
for (UserInfo userInfo : list) {
System.out.println(userInfo);
}
}
/**
* 释放资源
* @param rs
* @param st
* @param conn
*/
public static void free(ResultSet rs,Statement st,Connection conn){
try{
if(rs != null){
rs.close();
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try{
if(st != null){
st.close();
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}

}

输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[添加 奥巴马]
[添加 希拉里]
[添加 特朗普]

输出 所有用户信息:
UserInfo{userId=10009, name='奥巴马', gender=男, birthday=2016-11-27 10:03:00.0}
UserInfo{userId=10010, name='希拉里', gender=女, birthday=2016-11-27 10:03:00.0}
UserInfo{userId=10011, name='特朗普', gender=女, birthday=2016-11-27 10:03:00.0}
根据姓名查询用户信息
UserInfo{userId=10011, name='特朗普', gender=女, birthday=2016-11-27 10:03:00.0}
[更新]
UserInfo{userId=10011, name='特朗普', gender=男, birthday=2016-11-27 10:03:00.0}

输出 所有用户信息:
UserInfo{userId=10009, name='奥巴马', gender=男, birthday=2016-11-27 10:03:00.0}
UserInfo{userId=10010, name='希拉里', gender=女, birthday=2016-11-27 10:03:00.0}
UserInfo{userId=10011, name='特朗普', gender=男, birthday=2016-11-27 10:03:00.0}

注意

  1. 使用prepareStatement预处理执行SQL语句时,?的索引是从1开始的

JDBC中特殊数据类型的操作问题

第一个是日期问题

JDBC接收的时间类型是sql下的类型,与Java中的类型不同,通常需要转化。

如 java.sql.Date <—> java.util.Date

第二个问题就是大文本数据的问题

读写大文本数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** 
* 插入大文本
*/
static void insert(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into clob_test(bit_text) values(?)";
ps = conn.prepareStatement(sql);
File file = new File("src/com/weijia/type/ClubDemo.java");
Reader reader = new BufferedReader(new FileReader(file));
//ps.setAsciiStream(1, new FileInputStream(file), (int)file.length());//英文的文档
ps.setCharacterStream(1, reader, (int)file.length());
ps.executeUpdate();
reader.close();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs,ps,conn);
}
}
1
2
Clob clob = rs.getClob(1);  
InputStream is = clob.getAsciiStream();

JDBC中事务的概念

JDBC中调用存储过程

JDBC来实现批处理功能

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
static void createBatch() throws Exception{  
//建立一个连接的是很耗时间的
//执行一个sql语句也是很耗时间的
//优化的措施:批处理
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert user(name,birthday,money) values(?,?,?)";
ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);

//打包的话容量也不是越大越好,因为可能会内存溢出的,同时网络传输的过程中也是会进行拆包传输的,这个包的大小是不一定的
//有时候打包的效率不一定就会高,这个和数据库的类型,版本都有关系的,所以我们在实践的过程中需要检验的
for(int i=0;i<100;i++){
ps.setString(1,"jiangwei");
ps.setDate(2,new Date(System.currentTimeMillis()));
ps.setFloat(3,400);
//ps.addBatch(sql);
ps.addBatch();
}
ps.executeBatch();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs, ps, conn);
}
}

JDBC中的滚动结果集和分页技术

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
static void test() throws Exception{  
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
//结果集可滚动的
/**
* 参数的含义:
* ResultSet.RTYPE_FORWORD_ONLY:这是缺省值,只可向前滚动;
ResultSet.TYPE_SCROLL_INSENSITIVE:双向滚动,但不及时更新,就是如果数据库里的数据修改过,并不在ResultSet中反应出来。
ResultSet.TYPE_SCROLL_SENSITIVE:双向滚动,并及时跟踪数据库的更新,以便更改ResultSet中的数据。
ResultSet.CONCUR_READ_ONLY:这是缺省值,指定不可以更新 ResultSet
ResultSet.CONCUR_UPDATABLE:指定可以更新 ResultSet
*/
st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
rs = st.executeQuery("select id,name,money,birthday from user");
//开始的时候这个游标的位置是第一条记录之前的一个位置
//当执行rs.next的时候这个游标的位置就到第一条记录了
/*while(rs.next()){
//print result
}*/
//上面的代码执行之后,这个游标就到最后一条记录的下一个位置了
//所以这里在调用previous方法之后,这个游标就回到了最后一条记录中,所以打印了最后一条记录的值
/*if(rs.previous()){
System.out.println("id="+rs.getInt("id")+"\tname="+rs.getString("name")+"\tbirthday="+rs.getDate("birthday")+"\tmoney="+rs.getFloat("money"));
}*/

//绝对定位到第几行结果集
//这里传递的参数的下标是从1开始的,比如这里查询出来的记录有3条,那么这里的参数的范围是:1-3,如果传递的参数不在这个范围内就会报告异常的
rs.absolute(2);
System.out.println("id="+rs.getInt("id")+"\tname="+rs.getString("name")+"\tbirthday="+rs.getDate("birthday")+"\tmoney="+rs.getFloat("money"));

//滚到到第一行的前面(默认的就是这种情况)
rs.beforeFirst();

//滚动到最后一行的后面
rs.afterLast();

rs.isFirst();//判断是不是在第一行记录
rs.isLast();//判断是不是在最后一行记录
rs.isAfterLast();//判断是不是第一行前面的位置
rs.isBeforeFirst();//判断是不是最后一行的后面的位置

//以上的api可以实现翻页的效果(这个效率很低的,因为是先把数据都查询到内存中,然后再进行分页显示的)

//效率高的话是直接使用数据库中的分页查询语句:
//select * from user limit 150,10;

//以上的api实现的分页功能是针对于那些本身不支持分页查询功能的数据库的,如果一个数据库支持分页功能,上面的代码就不能使用的,因为效率是很低的
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs,st,conn);
}
}

JDBC中的可更新以及对更新敏感的结果集操作

元数据的相关知识

数据库的元数据信息

查询参数的元数据信息

结果集中元数据信息

JDBC中的数据源

JDBC中CRUD的模板模式

Spring框架中的JdbcTemplate

加强版的JdbcTemplate

NamedParameterJdbcTemplate

SimpleJdbcTemplate

事务

批量

常见错误

  • java.sql.SQLException: Incorrect string value: '\xE3\x80\x90\xE9\x80\x9A...' for column 'msg' at row 1
    编码问题: 检查数据库编码,数据表编码,列编码以及连接数据库使用的characterEncoding

链接数据库

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
package com.seecen.stream;
import java.sql.*;
public class TestJDBC {
/**
* 1、实例话驱动类
* 2、建立到数据库的连接
* 3、将数据发送到数据库中
* 4、执行语句(select语句)
* 5、关闭
* @param args
*/
public static void main(String[] args) {
ResultSet rs = null;
Statement stmt = null;
Connection conn = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.1:1521:yuewei", "scott", "tiger");
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from dept");
while(rs.next()) {
System.out.println(rs.getString("deptno"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(rs != null) {
rs.close();
rs = null;
}
if(stmt != null) {
stmt.close();
stmt = null;
}
if(conn != null) {
conn.close();
conn = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

调用存储过程

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
package com.huawei.interview.lym;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;

public class JdbcTest {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Connection cn = null;
CallableStatement cstmt = null;
try {
//这里最好不要这么干,因为驱动名写死在程序中了
Class.forName("com.mysql.jdbc.Driver");
//实际项目中,这里应用DataSource数据,如果用框架,
//这个数据源不需要我们编码创建,我们只需Datasource ds = context.lookup()
//cn = ds.getConnection();
cn = DriverManager.getConnection("jdbc:mysql:///test","root","root");
cstmt = cn.prepareCall("{call insert_Student(?,?,?)}");
cstmt.registerOutParameter(3,Types.INTEGER);
cstmt.setString(1, "wangwu");
cstmt.setInt(2, 25);
cstmt.execute();
//get第几个,不同的数据库不一样,建议不写
System.out.println(cstmt.getString(3));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
/*try{cstmt.close();}catch(Exception e){}
try{cn.close();}catch(Exception e){}*/
try {
if(cstmt != null)
cstmt.close();
if(cn != null)
cn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

JDBC中的PreparedStatement相比Statement的好处

1)提高性能:在使用preparedStatement对象执行sql时候,命令被数据库编译和解析,然后被放到命令缓冲区,然后每当执行同一个preparedStatement时候,他就被再解析一次,但不会在编译,在缓冲区中可以发现预编译的命令,并且可以重新使用。
如果你要写Insert update delete 最好使用preparedStatement,在有大量用户的企业级应用软件中,经常会执行相同的sql,使用preparedStatement会增加整体的性能。
2)安全性:PreparedStatement 可以防止sql注入。

JDBC原理

调用Class.forName("com.mysql.jdbc.Driver"); 加载mysql的驱动类进内存,那么就会在DriverManager中注册自己,注册的意思简单来说就是DriverManager中保持一个Driver引用指向了自己,但是具体的实现可能不同。

然后嗲用DriverManager.getConnection方法得到连接对象, 这里运用到了简单工厂方法,即根据传进去得参数来具体实例化哪个驱动类。

可能是mysql的驱动类, 也可能是Oracle的驱动类, 具体的由传进去的参数来决定。

当得到Connection对象后就没DriverManagerDriver类什么事了。

Connection一个接口,但是它指向了具体的Connection子类对象。

通过Connection中定义的接口,就能够访问数据库了。

所以总得来说,如果要改变当前使用的数据库,那么只需要改变两个地方,

Class.forName(具体的参数)

DriverManager.getConnection(具体的参数)

所以我们可以在配置文件中配置这两个参数,那么我们就可以在程序运行的时候动态地改变所使用的数据库,只需要更改配置文件就行了。

当然了,程序肯定要有数据库第三方jar包。

JDBC 字段判空问题

https://github.com/openjdk/jdk/blob/master/src/java.sql/share/classes/java/sql/ResultSet.java#L222

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Reports whether
* the last column read had a value of SQL {@code NULL}.
* Note that you must first call one of the getter methods
* on a column to try to read its value and then call
* the method {@code wasNull} to see if the value read was
* SQL {@code NULL}.
*
* @return {@code true} if the last column value read was SQL
* {@code NULL} and {@code false} otherwise
* @throws SQLException if a database access error occurs or this method is
* called on a closed result set
*/
boolean wasNull() throws SQLException;

注意: wasNull方法只是判断上一个值是否为空,在使用时需要先获取值,再判空。

错误使用的样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 从ResultSet获取值
**/
public static Object getField(
ResultSet resultSet,
int index,
int type
) {
if(resultSet.wasNull) {
return null;
}

if(type == Type.LONG) {
return resultSet.getLong(index + 1);
} else if(type == Type.NULL) {
return null;
} ...
}

参考文献:

  1. [J2EE学习篇之–JDBC详解 ][http://blog.csdn.net/jiangwei0910410003/article/details/26164629]

什么是内部类?分为哪几种?

内部类是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员,并且依附于外部类而存在的。内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)。
内部类主要有以下几类:成员内部类、局部内部类、静态内部类、匿名内部类。

为什么需要内部类?

典型的情况是,内部类继承自某个类或实现某个接口,内部类的代码操作创建其的外围类的对象。所以你可以认为内部类提供了某种进入其外围类的窗口。
使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。

内部类可以引用它的包含类的成员吗?有没有什么限制?

完全可以。如果不是静态内部类,那没有什么限制!
如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员,例如,下面的代码:

1
2
3
4
5
6
7
8
9
10
11
class Outer
{
static int x;
static class Inner
{
void test()
{
syso(x);
}
}
}

答题时,也要能察言观色,揣摩提问者的心思,显然人家希望你说的是静态内部类不能访问外部类的成员,但你一上来就顶牛,这不好,要先顺着人家,让人家满意,然后再说特殊情况,让人家吃惊。

Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?

可以继承其他类或实现其他接口。不仅是可以,而是必须!

如何调用内部类?


参考文献

  1. Java内存模型与volatile关键字

Java内存管理原理

在Java运行体系结构中,存在Java程序、虚拟机和操作系统三个层次,Java程序与虚拟机交互(即JNI),虚拟机与操作系统间交互!这就保证了Java程序的平台无关性。

程序运行前,JVM向操作系统申请一定的内存空间,称为初始内存空间。

程序运行过程中,Java程序一直向Java虚拟机申请内存,当超过初始内存空间时,Java虚拟机再次向操作系统申请。当虚拟机已申请的内存超过规定的最大内存空间再申请时,将会出现内存溢出错误。

内存的分配

Java内存可以分为方法区、heap(堆)和stack(栈)。

  • 方法区,默认64M,Java虚拟机会将加载的类存入,保存类的结构,静态成员等。
  • heap(堆),默认64M,存放对象持有的数据,保持对原类的引用 。对象的属性值保存在堆中,对象调用的方法保存在方法区。通过new创建的对象都在堆中。方法中的局部变量使用final修饰后,放在堆中。
  • stack(栈), 默认为1M。
  • 基础类型保存在栈中
  • 局部变量保存在栈中
    程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。

在堆上分配内存与赋值异步性

在Java的指令中,创建对象和赋值操作是分开进行的,如instance=new Singleton();语句是分为两步进行的,但是JVM并不保证这两个操作的先后顺序,也就是有可能JVM会为Singleton实例分配空间, 然后直接赋值给 instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:

  1. A、B线程同时进入了第一个if判断
  2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
  3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
  4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
  5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

基础类型

1
2
3
int a=3;
int b=3;
a=4;
  • 编译器先处理int a=3;:
    首先,在栈中创建一个变量为a的引用,然后查找有没有字面量为3的地址。没有,则开辟一个存放3这个字面量的地址,然后将a指向这个地址。

  • 再处理int b=3;:
    首先创建一个变量为b的引用,然后查找发现已经存在字面量为3的地址,将b指向这个地址。

  • 如果此时,再a=4;:
    那么,虚拟机存在是否存在4这个字面量,如果存在,直接将a指向这个地址;否则,创建4这个字面量,将a指向这个字面量的地址。

因此改变了a的值,只是a重新指向别的地方,不会影响b的值。

对象存储

方法的存储

定义如下类

1
2
3
4
5
6
7
8
public class BoxSet{
private int width=100;
private int hight=200;
public BoxSet(int x,int y){
this.width=x;
this.hight=y;
}
}

测试

1
2
BoxSet boxSet; // (1)
boxSet=new BoxSet(50,50); // (2)

两条语句:

  1. 声明变量boxSet:
  • 在栈内存中,为对象的引用变量boxSet分配内存空间。但是boxSet的值为空,称boxSet为一个空对象。空对象不能使用,因为它没有引用任何“实体”。
  1. 会做两件事:
  • 在堆内存中,为类的成员变量widthhight分配内存,并赋值为各数据类型的默认值。
  • 显式初始化: 类定义时的初始化值,即width赋值为100,hight赋值为200
  • 调用构造方法: 为widthhight分别赋值为50和50
  • 返回堆内存中对象的引用给引用变量boxSet

方法存储

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
public class Dog {
Collar c;
String name;
//1. main()方法位于栈上
public static void main(String[] args) {
//2. 在栈上创建引用变量d,但Dog对象尚未存在
Dog d;
//3. 在堆上创建新的Dog对象,并将其赋予d引用变量
d = new Dog();
//4. 将引用变量的一个副本传递给go()方法
d.go(d);
}
//5. 将go()方法置于栈上,并将dog参数作为局部变量(局部变量位于栈上)
void go(Dog dog){
//6. 在堆上创建新的Collar对象,并将其赋予Dog的实例变量
c =new Collar();
}
//7.将setName()添加到栈上,并将dogName参数作为其局部变量
void setName(String dogName){
//8. name的实例对象也引用String对象
name=dogName;
}
//9. 程序执行完成后,setName()将会完成并从栈中清除,
//此时,局部变量dogName也会消失,尽管它所引用的String仍在堆上
}

包装类

2.String StringBuffered 与 StringBuilder 区别

Java中的基本变量都存在包装类,如下表:

| 0 | 基本变量 | 包装类 |
|—- | —- | —- |
|1| int | Integer |
|2| long | Long |
|3| short | Short |
|4| float | Float |
|5| double | Double |
|6| boolean | Boolean |
|7| byte | Byte |
|8| char | Character |

String

1
String s="abc";
  1. 在栈内存中为对象的引用变量s分配内存
  2. 查找字面量"abc",如果存在则将该地址赋值给s; 否则,新建字面量"abc"并将赋值给s。

继续看下面的例子:

1
String s=new String("abc"); //(2)

方式2:

  1. 在栈内存中为对象的引用变量s分配内存
  2. 在堆中新建String对象,并将地址赋值给栈中的s
  3. 在栈内存中,查找字面量”abc”, 如果存在则将字面量地址作为参数传递给赋值给栈中s指向的String对象;否则,新建字面量”abc”.
    因此,在这个过程中共产生了两个对象。
1
2
3
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

  注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

String可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建,这种表达式是可以的!前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

我们再来更进一步,将以上代码改成:

1
2
3
4
5
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1==str2); //false
System.out.println(str1 + "," + str2); //bcd, abc

  这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为”bcd”时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。
  事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。

  再修改原来代码:

1
2
3
4
5
6
7
8
9
10
  String str1 = "abc";
  String str2 = "abc";

  str1 = "bcd";

  String str3 = str1;
  System.out.println(str3); //bcd

  String str4 = "bcd";
  System.out.println(str1 == str4); //true

  str3 这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

  我们再接着看以下的代码。

1
2
3
  String str1 = new String("abc");
  String str2 = "abc";
  System.out.println(str1==str2); //false

  创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

1
2
3
  String str1 = "abc";
  String str2 = new String("abc");
  System.out.println(str1==str2); //false

  创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

  以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

  6). 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

volatile关键字

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

首先, 应该明白计算机内部都做什么了。比如做了一个i++操作,计算机内部做了三次处理:读取-修改-写入。
同样,对于一个long型数据,做了个赋值操作,在32系统下需要经过两步才能完成,先修改低32位,然后修改高32位。
假想一下,当将以上的操作放到一个多线程环境下操作时候,有可能出现的问题,是这些步骤执行了一部分,而另外一个线程就已经引用了变量值,这样就导致了读取脏数据的问题。
通过这个设想,就不难理解volatile关键字了。

volatile可以用在任何变量前面,但不能用于final变量前面,因为final型的变量是禁止修改的。也不存在线程安全的问题。

内存释放

垃圾回收

垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用,以免造成内存泄露。

一般是在CPU空闲或空间不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时机和顺序等。
当没有任何获得线程能访问一个对象时,该对象就符合垃圾回收条件。
垃圾回收器如发现一个对象不能被任何活线程访问时,他将认为该对象符合删除条件,就将其加入回收队列,但不是立即销毁对象,何时销毁并释放内存是无法预知的。垃圾回收不能强制执行,然而Java提供了一些方法(如:System.gc()方法),允许你请求JVM执行垃圾回收,而不是要求,虚拟机会尽其所能满足请求,但是不能保证JVM从内存中删除所有不用的对象。

符合垃圾回收的条件

  1. 空引用
  2. 重新为引用变量赋值:可以通过设置引用变量引用另一个对象来解除该引用变量与一个对象间的引用关系。
  3. 方法内创建的对象:所创建的局部变量仅在该方法的作用期间内存在。一旦该方法返回,在这个方法内创建的对象就符合垃圾收集条件。有一种明显的例外情况,就是方法的返回对象。
  4. 隔离引用:这种情况中,被回收的对象仍具有引用,这种情况称作隔离岛。若存在这两个实例,他们互相引用,并且这两个对象的所有其他引用都删除,其他任何线程无法访问这两个对象中的任意一个。也可以符合垃圾回收条件。

强制垃圾回收

finalize()

java提供了一种机制,使你能够在对象刚要被垃圾回收之前运行一些代码。这段代码位于名为finalize()的方法内,所有类从Object类继承这个方法。由于不能保证垃圾回收器会删除某个对象。因此放在finalize()中的代码无法保证运行。因此建议不要重写finalize();

内存泄露

内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:
首先,这些对象是有被引用的,即在有向树形图中,存在树枝通路可以与其相连;
其次,这些对象是无用的,即程序以后不会再使用这些对象。
如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

1
2
3
4
5
6
7
Vector v = new Vector(10);
for (int i = 1; i < 100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}

//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。 实际上无用,而还被引用的对象,GC就无能为力了(事实上GC认为它还有用),这一点是导致内存泄漏最重要的原因。

优化编程

(1)不要显式调用System.gc()

  此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。

(2)尽量减少临时对象的使用

  临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

(3)对象不用时最好显式置为Null

  一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。

(4)尽量使用StringBuffer,而不用String来累加字符串

由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。

(5)能用基本类型如Int,Long,就不用Integer,Long对象

  基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

(6)尽量少用静态对象变量

  静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

(7)分散对象创建或删除的时间

 集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。


参考文献

  1. Java内存模型与volatile关键字

String.split

正则表达式分割字符串

保留末尾空格与空字符串情形

split(delimiter) 默认会从结果数组中去掉空字符串.

1
2
3
4
5
6
jshell> "abc|def| 123| 456|| ".split("\\|")
$1 ==> String[6] { "abc", "def", " 123", " 456", "", " " }

jshell> "abc|def| 123| 456||".split("\\|")
$4 ==> String[4] { "abc", "def", " 123", " 456" }
// 最后一个元素为空,会踢掉所有的空元素

要关闭这个机制,将重载方法 split(delimiter, limit)limit 设置为负值,如

1
2
jshell> "abc|def| 123| 456||".split("\\|",-1)
$5 ==> String[6] { "abc", "def", " 123", " 456", "", "" }

更详细的介绍:

split(regex) 内部是调用split(regex, 0)的返回结果,参考官方文档 documentation 有这个方法的详细介绍

limit参数控制应用pattern的次数,因此会影响所得数组的长度。
如果限制n大于零,则将最多应用n-1次该pattern,该数组的长度将不大于n,并且该数组的最后一个条目将包含除最后一个匹配的定界符之外的所有输入。
如果n非正数,则该pattern将被尽可能多地应用,并且数组可以具有任何长度。
如果n,则该pattern将被尽可能多地应用,该数组可以具有任何长度,并且尾随的空字符串将被丢弃

1
2
3
// 最多匹配三次
jshell> "abc|def| 123| 456||".split("\\|",3)
$6 ==> String[3] { "abc", "def", " 123| 456||" }

Exception:

空字符串分拆的情况

值得一提的是,只有在拆分机制创建了空字符串,删除尾随的空字符串才有意义。 因此,对于空字符串的分拆"".split(任何东西),因为我们无法进一步拆分"",所以我们将得到结果[""]数组。发生这种情况是因为此处未发生拆分,因此尽管存在空白,但""还是尾随表示原始字符串,而不是拆分过程创建的空字符串。

1
2
jshell> "".split("[,;]")
$7 ==> String[1] { "" }

应用样例

Flink 源码: org.apache.flink.configuration.GlobalConfiguration#loadYAMLResource

1
2
3
4
5
6
7
8
9
10
11
// 解析yaml文件,获取 key-value
// 1. 如果 #号前 没有, 则为注释
// 2. #前有,则使用: 分割

// 从文件读取一行数据, 使用#分割
String[] comments = line.split("#", 2);
String conf = comments[0].trim();

// 2. get key and value
if (conf.length() > 0) {
String[] kv = conf.split(": ", 2);

line.split("#",2):

将一行数据按照#分拆成两部分

1
2
3
4
5
6
7
8
jshell> "# this is comments.".split("#",2)
$11 ==> String[2] { "", " this is comments." }

jshell> " # this is comments.".split("#",2)
$12 ==> String[2] { " ", " this is comments." }

jshell> "abc: 123 # this is comments.".split("#",2)
$13 ==> String[2] { "abc: 123 ", " this is comments." }

踢掉空格

1
2
List<String> list = Splitter.on(pattern).omitEmptyStrings().splitToList("");
System.out.println(list.size()); // 0

StringUtils.split

1
org.apache.commons.lang.StringUtils#splitByWholeSeparator(java.lang.String, java.lang.String, int)

分割字符串,正则表达式作为普通字符串

普通泛型

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
class Point<T>{       // 此处可以随便写标识符号,T是type的名称参数
private T var ; // var的类型由T指定,即:由外部指定
public T getVar(){ // 返回值的类型由外部决定
return var ;
}
public void setVar(T var){ // 设置的类型也由外部决定
this.var = var ;
}
};
public class GenericsDemo06{
public static void main(String args[]){
Point<String> p = new Point<String>() ; // 里面的var类型为String类型
p.setVar("it") ; // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
};
----------------------------------------------------------
class Notepad<K,V>{ // 此处指定了两个泛型类型
private K key ; // 此变量的类型由外部决定
private V value ; // 此变量的类型由外部决定
public K getKey(){
return this.key ;
}
public V getValue(){
return this.value ;
}
public void setKey(K key){
this.key = key ;
}
public void setValue(V value){
this.value = value ;
}
};
public class GenericsDemo09{
public static void main(String args[]){
Notepad<String,Integer> t = null ; // 定义两个泛型类型的对象
t = new Notepad<String,Integer>() ; // 里面的key为String,value为Integer
t.setKey("汤姆") ; // 设置第一个内容
t.setValue(20) ; // 设置第二个内容
System.out.print("姓名;" + t.getKey()) ; // 取得信息
System.out.print(",年龄;" + t.getValue()) ; // 取得信息

}
};

通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Info<T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo14{
public static void main(String args[]){
Info<String> i = new Info<String>() ; // 使用String为泛型类型
i.setVar("it") ; // 设置内容
fun(i) ;
}
public static void fun(Info<?> temp){ // 可以接收任意的泛型对象
System.out.println("内容:" + temp) ;
}
};

受限泛型

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
 class Info<T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo17{
public static void main(String args[]){
Info<Integer> i1 = new Info<Integer>() ; // 声明Integer的泛型对象
Info<Float> i2 = new Info<Float>() ; // 声明Float的泛型对象
i1.setVar(30) ; // 设置整数,自动装箱
i2.setVar(30.1f) ; // 设置小数,自动装箱
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? extends Number> temp){ // 只能接收Number及其Number的子类
System.out.print(temp + "、") ;
}
};
-----------------------------------------------------------
class Info<T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo21{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ; // 声明String的泛型对象
Info<Object> i2 = new Info<Object>() ; // 声明Object的泛型对象
i1.setVar("hello") ;
i2.setVar(new Object()) ;
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? super String> temp){ // 只能接收String或Object类型的泛型
System.out.print(temp + "、") ;
}
};

泛型无法向上转型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 class Info<T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo23{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ; // 泛型类型为String
Info<Object> i2 = null ;
i2 = i1 ; //这句会出错 incompatible types
}
};

泛型接口

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
 interface Info<T>{        // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
};
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = null; // 声明接口对象
i = new InfoImpl<String>("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};
----------------------------------------------------------
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl implements Info<String>{ // 定义泛型接口的子类
private String var ; // 定义属性
public InfoImpl(String var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(String var){
this.var = var ;
}
public String getVar(){
return this.var ;
}
};
public class GenericsDemo25{
public static void main(String arsg[]){
Info i = null; // 声明接口对象
i = new InfoImpl("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Demo{
public <T> T fun(T t){ // 可以接收任意类型的数据
return t ; // 直接把参数返回
}
};
public class GenericsDemo26{
public static void main(String args[]){
Demo d = new Demo() ; // 实例化Demo对象
String str = d.fun("汤姆") ; // 传递字符串
int i = d.fun(30) ; // 传递数字,自动装箱
System.out.println(str) ; // 输出内容
System.out.println(i) ; // 输出内容
}
};

通过泛型方法返回泛型类型实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Info<T extends Number>{ // 指定上限,只能是数字类型
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo27{
public static void main(String args[]){
Info<Integer> i = fun(30) ;
System.out.println(i.getVar()) ;
}
public static <T extends Number> Info<T> fun(T param){//方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定
Info<T> temp = new Info<T>() ; // 根据传入的数据类型实例化Info
temp.setVar(param) ; // 将传递的内容设置到Info对象的var属性之中
return temp ; // 返回实例化对象
}
};

使用泛型统一传入的参数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Info<T>{    // 指定上限,只能是数字类型
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo28{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ;
Info<String> i2 = new Info<String>() ;
i1.setVar("HELLO") ; // 设置内容
i2.setVar("汤姆") ; // 设置内容
add(i1,i2) ;
}
public static <T> void add(Info<T> i1,Info<T> i2){
System.out.println(i1.getVar() + " " + i2.getVar()) ;
}
};

泛型数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GenericsDemo30{
public static void main(String args[]){
Integer i[] = fun1(1,2,3,4,5,6) ; // 返回泛型数组
fun2(i) ;
}
public static <T> T[] fun1(T...arg){ // 接收可变参数
return arg ; // 返回泛型数组
}
public static <T> void fun2(T param[]){ // 输出
System.out.print("接收泛型数组:") ;
for(T t:param){
System.out.print(t + "、") ;
}
}
};

泛型的嵌套设置

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
class Info<T,V>{      // 接收两个泛型类型
private T var ;
private V value ;
public Info(T var,V value){
this.setVar(var) ;
this.setValue(value) ;
}
public void setVar(T var){
this.var = var ;
}
public void setValue(V value){
this.value = value ;
}
public T getVar(){
return this.var ;
}
public V getValue(){
return this.value ;
}
};
class Demo<S>{
private S info ;
public Demo(S info){
this.setInfo(info) ;
}
public void setInfo(S info){
this.info = info ;
}
public S getInfo(){
return this.info ;
}
};
public class GenericsDemo31{
public static void main(String args[]){
Demo<Info<String,Integer>> d = null ; // 将Info作为Demo的泛型类型
Info<String,Integer> i = null ; // Info指定两个泛型类型
i = new Info<String,Integer>("汤姆",30) ; // 实例化Info对象
d = new Demo<Info<String,Integer>>(i) ; // 在Demo类中设置Info类的对象
System.out.println("内容一:" + d.getInfo().getVar()) ;
System.out.println("内容二:" + d.getInfo().getValue()) ;
}
};

泛型方法不一定要通过参数来确定泛型准确类型,可以只通过返回值,比如:

1
2
3
4
5
6
7
8
9
public static <E> ArrayList<E> newArrayList() {
return new ArrayList<E>();
}

public List<PrepaidHistory> queryHistories(Long skyid,PrepaidHistoryType type, Date from, Date end) {

    。。。
return Lists.newArrayList();
}

这样Lists.newArrayList();
智能的知道返回类型为PrepaidHistory

其他

//TODO 继承泛型

1
public class TextFile extends ArrayList<String>{

JNI – Java Native Interface

JNI 是 Java 与本地代码通信的桥梁,因此,也是 Java 调用操作系统 API 的接口。
因为 jvm 也是采用 C 实现的,通过JNI,可以达到调用 jvm 的目的。

JNI调用过程

  1. 当加载 Java 类时,运行静态代码段。 在静态代码段中,调用System.loadLibrary("<jni_lib_name>")方法,加载动态库。
  2. Java 自动在库中查找JNI_OnLoad()函数。 JNI_OnLoad()函数主要有两个作用:
  • 判断 JNI 版本
  • 初始化,如注册函数
  1. 当调用 native 方法时,Java 将去查找注册的函数。有两种注册函数的方法,静态注册和动态注册。
  • 静态注册是指在 C 里面以JNI默认的函数名定义与 java 中对对应的函数
  • 而动态注册是指在调用JNI_OnLoad()函数时,调用(*env)->RegisterNatives()函数,将方法注册。

当Java去查找注册函数,首先判断是不是存在动态注册函数,如果不存在再查找静态注册函数。
如果查找不到,将报出异常。

  1. 执行C 函数,Java接收返回结果。

动态注册

1
2
3
4
static {
System.loadLibrary("native_native");
}
private native void callNativeMethod(int x,double y);
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
static JNINativeMethod gMethods[] = {          
{"callNativeMethod", "(ID)Ljava/lang/String;", (void *)abc}
};
//真正的实现方法
JNIExport jstring abc(JNIEnv *env, jobject obj,jint x,jdouble y){
//TODO 实现
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
//获取JNI环境对象
// if(vm->GetEnv((void**)&env,JNI_VERSION_1_4)!=JNI_OK){ //C++
if ((*vm)->GetEnv(vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) {//C
return -1;
}
//如果要注册, 只需要两步骤, 首先FindClass, 然后RegisterNatives就可以了
//注册本地方法.Load 目标类
char className[20] = {"club/guadazi/wiki/MyFirstActivity"};
// jclass clazz = (env)->FindClass( (const char*)className);// C++
jclass clazz = (*env)->FindClass(env,(const char *)className);//C
//注册本地native方法
if((*env)->RegisterNatives(env,clazz, gMethods, 1)< 0)
{
return -1;
}
//一定要返回版本号, 否则会出错.
result = JNI_VERSION_1_4;
return result;
}

静态注册

在生成的头文件中会含有静态注册函数

1
2
3
JNIExport jstring
Java_club_guadazi_wiki_MyFirstActivity_callNativeMethod(
JNIEnv *env, jobject obj,jint x,jdouble y);

写法是Java+Android工程的包名+Android工程的Activity名+方法名,点号用下划线表示,这个写法很严格。 包名:com_conowen_helloworld, Activity名:HelloWorldActivity, 方法名:helloWorldFromJNI

函数名称可以通过javah生成:

  • 编译该java文件成class文件
  • 利用命令生成h头文件
1
javah MyFirstActivity

java 调用 c

上面的实现即为 java 调用 C

c 调用 java

普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestJNIInstanceVariable {
static {
System.loadLibrary("myjni");
// myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Instance variables
private int number = 88;
private String message = "Hello from Java";
// Native method that modifies the instance variables
private native void modifyInstanceVariable();
public static void main(String args[]) {
TestJNIInstanceVariable test = new TestJNIInstanceVariable();
test.modifyInstanceVariable();
System.out.println("In Java, int is " + test.number);
System.out.println("In Java, String is " + test.message);
}
}
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
#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"
// jni方法的静态实现
JNIEXPORT void JNICALL
Java_TestJNIInstanceVariable_modifyInstanceVariable
(JNIEnv *env, jobject thisObj) {
// Get a reference to this object's class
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
// int
// Get the Field ID of the instance variables "number"
jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
if (NULL == fidNumber) return;
// Get the int given the Field ID
jint number = (*env)->GetIntField(env, thisObj, fidNumber);
printf("In C, the int is %d\n", number);
// Change the variable
number = 99;
(*env)->SetIntField(env, thisObj, fidNumber, number);
// Get the Field ID of the instance variables "message"
jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
if (NULL == fidMessage) return;
// String
// Get the object given the Field ID
jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);
// Create a C-string with the JNI String
const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
if (NULL == cStr) return;
printf("In C, the string is %s\n", cStr);
(*env)->ReleaseStringUTFChars(env, message, cStr);
// Create a new C-string and assign to the JNI string
message = (*env)->NewStringUTF(env, "Hello from C");
if (NULL == message) return;
// modify the instance variables
(*env)->SetObjectField(env, thisObj, fidMessage, message);
}

注意:

  1. c 调用 java 的方法,类似于反射。 首先,需要获得类对象,然后再获取函数 id。
    再调用get 和 set 函数,操作数据。
  2. 如果建立了局部对象, 需要释放空间, 以防数据泄露。

访问静态变量和静态函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestJNIStaticVariable {
static {
System.loadLibrary("myjni"); // nyjni.dll (Windows) or libmyjni.so (Unixes)
}
// Static variables
private static double number = 55.66;
// Native method that modifies the instance variables
private native void modifyStaticVariable();
public static void main(String args[]) {
TestJNIStaticVariable test = new TestJNIStaticVariable();
test.modifyStaticVariable();
System.out.println("In Java, the double is " + number);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <jni.h>
#include <stdio.h>
#include "TestJNIStaticVariable.h"
JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
(JNIEnv *env, jobject thisObj) {
// Get a reference to this object's class
jclass cls = (*env)->GetObjectClass(env, thisObj);
// Read the int static variable and modify its value
jfieldID fidNumber = (*env)->GetStaticFieldID(env, cls, "number", "D");
if (NULL == fidNumber) return;
jdouble number = (*env)->GetStaticDoubleField(env, thisObj, fidNumber);
printf("In C, the double is %f\n", number);
number = 77.88;
(*env)->SetStaticDoubleField(env, thisObj, fidNumber, number);
}

调用实例方法和静态方法

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
public class TestJNICallBackMethod {
static {
System.loadLibrary("myjni");
// myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Native method that calls back the Java methods below
private native void nativeMethod();
// To be called back by the native code
private void callback() {
System.out.println("In Java");
}
private void callback(String message) {
System.out.println("In Java with " + message);
}
private double callbackAverage(int n1, int n2) {
return ((double)n1 + n2) / 2.0;
}
// Static method to be called back
private static String callbackStatic() {
return "From static Java method";
}
public static void main(String args[]) {
new TestJNICallBackMethod().nativeMethod();
}
}
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
#include <jni.h>
#include <stdio.h>
#include "TestJNICallBackMethod.h"
JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod
(JNIEnv *env, jobject thisObj) {
// Get a class reference for this object
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
// Get the Method ID for method "callback", which takes no arg and return void
jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
if (NULL == midCallBack) return;
printf("In C, call back Java's callback()\n");
// Call back the method (which returns void), baed on the Method ID
(*env)->CallVoidMethod(env, thisObj, midCallBack);
jmethodID midCallBackStr = (*env)->GetMethodID(env, thisClass,
"callback", "(Ljava/lang/String;)V");
if (NULL == midCallBackStr) return;
printf("In C, call back Java's called(String)\n");
jstring message = (*env)->NewStringUTF(env, "Hello from C");
(*env)->CallVoidMethod(env, thisObj, midCallBackStr, message);
jmethodID midCallBackAverage = (*env)->GetMethodID(env, thisClass,
"callbackAverage", "(II)D");
if (NULL == midCallBackAverage) return;
jdouble average = (*env)->CallDoubleMethod(env, thisObj, midCallBackAverage, 2, 3);
printf("In C, the average is %f\n", average);
jmethodID midCallBackStatic = (*env)->GetStaticMethodID(env, thisClass,
"callbackStatic", "()Ljava/lang/String;");
if (NULL == midCallBackStatic) return;
jstring resultJNIStr = (*env)->CallStaticObjectMethod(env, thisObj, midCallBackStatic);
const char *resultCStr = (*env)->GetStringUTFChars(env, resultJNIStr, NULL);
if (NULL == resultCStr) return;
printf("In C, the returned string is %s\n", resultCStr);
(*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
}

回调覆盖的父类的实例方法

//TODO

jni 数据类型

jni在本地系统中定义了与java总的基本数据类型相对应的数据类型。

  1. 基本类型: jint jbyte jshort jlong jdouble jchar jboolean与java中的int byte short long double char boolean对应。

  2. 引用类型: jobjectjava.lang.Object也定义了如下的子类型:

  • jclassjava.lang.Class
  • jstringjava.lang.String
  • jthrowablejava.lang.Throwable
  • jarray对应java数组。 java数组是包括8种基本类型数组与Object数组的应用类型。因此, 存在8个基本类型的数组: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArrayjbooleanArray,以及jobjectArray.

本地函数接收到上述的JNI类型的参数,返回JNI类型的数据。在本地函数处理自己的本地类型数据时,需要JNI类型与本地类型的转换。

参数传递与数据类型转换

基本数据类型

Java基本数据类型可以直接传递。在本地系统中定义的jXXX类型, 如jint,jbyte,jshort,jlong,jfloat,jdouble,jcharjboolean与java中基本数据类型int,byte,short,long,float,double,charboolean对应.

1
2
3
4
5
6
7
8
9
10
11
// In "win\jni_mh.h" - machine header which is machine dependent
typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;
// In "jni.h"
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;

String

1
2
3
4
5
6
7
8
9
10
11
12
public class TestJNIString {
static {
System.loadLibrary("myjni");
// myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Native method that receives a Java String and return a Java String
private native String sayHello(String msg);
public static void main(String args[]) {
String result = new TestJNIString().sayHello("Hello from Java");
System.out.println("In Java, the returned string is: " + result);
}
}
1
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);

Java的String是一个对象,而C里面的String是一个以 null 结束的字符数组(字符指针 char*)。JNI环境提供了转换用的函数:

  1. JNI String(jstring)转换为C String(char *): const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)

  2. C-String(char *)转换为JNI String(jstring): jstring NewStringUTF(JNIEnv*, char*)

    注意: 当调用上面的函数产生了新的内存时,需要调用(*env)->ReleaseStringUTFChars(env, <>, <>);

JNI本地字符串函数

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
// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
// Informs the VM that the native code no longer needs access to utf.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
// Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
// Returns the length in bytes of the modified UTF-8 representation of a string.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
// Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding
// and place the result in the given buffer buf.
// Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to the array of Unicode characters
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
// Informs the VM that the native code no longer needs access to chars.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
// Constructs a new java.lang.String object from an array of Unicode characters.
jsize GetStringLength(JNIEnv *env, jstring string);
// Returns the length (the count of Unicode characters) of a Java string.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
// Copies len number of Unicode characters beginning at offset start to the given buffer buf

数组

1
2
3
4
5
6
7
private native double[] sumAndAverage(int[] numbers);
public static void main(String args[]) {
int[] numbers = {22, 33, 33};
double[] results = new TestJNIPrimitiveArray().sumAndAverage(numbers);
System.out.println("In Java, the sum is " + results[0]);
System.out.println("In Java, the average is " + results[1]);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
(JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
// Step 1: Convert the incoming JNI jintarray to C's jint[]
jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
if (NULL == inCArray) return NULL;
jsize length = (*env)->GetArrayLength(env, inJNIArray);
// Step 2: Perform its intended operations
jint sum = 0;
int i;
for (i = 0; i < length; i++) {
sum += inCArray[i];
}
jdouble average = (jdouble)sum / length;
(*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0); // release resources
jdouble outCArray[] = {sum, average};
// Step 3: Convert the C's Native jdouble[] to JNI jdoublearray, and return
jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2); // allocate
if (NULL == outJNIArray) return NULL;
(*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray); // copy
return outJNIArray;
}

JNI基本类型数组函数

1
2
3
4
5
6
7
8
9
10
// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

函数签名

创建对象与对象数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <jni.h>
#include <stdio.h>
#include "TestJNIConstructor.h"
JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
// Get a class reference for java.lang.Integer
jclass cls = (*env)->FindClass(env, "java/lang/Integer");
// Get the Method ID of the constructor which takes an int
jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
if (NULL == midInit) return NULL;
// Call back constructor to allocate a new instance, with an int argument
jobject newObj = (*env)->NewObject(env, cls, midInit, number);
// Try runnning the toString() on this newly create object
jmethodID midToString = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
if (NULL == midToString) return NULL;
jstring resultStr = (*env)->CallObjectMethod(env, newObj, midToString);
const char *resultCStr = (*env)->GetStringUTFChars(env, resultStr, NULL);
printf("In C: the number is %s\n", resultCStr);
return newObj;
}

对象函数

1
2
3
4
5
6
7
jclass FindClass(JNIEnv *env, const char *name);
jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
//Constructs a new Java object.The method ID indicates which constructor method to invoke
jobject AllocObject(JNIEnv *env, jclass cls);
//Allocates a new Java object without invoking any of the constructors for the object.

对象数组

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
#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"
JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
(JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
// Get a class reference for java.lang.Integer
jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
// Use Integer.intValue() to retrieve the int
jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
if (NULL == midIntValue) return NULL;
// Get the value of each Integer object in the array
jsize length = (*env)->GetArrayLength(env, inJNIArray);
jint sum = 0;
int i;
for (i = 0; i < length; i++) {
jobject objInteger = (*env)->GetObjectArrayElement(env, inJNIArray, i);
if (NULL == objInteger) return NULL;
jint value = (*env)->CallIntMethod(env, objInteger, midIntValue);
sum += value;
}
double average = (double)sum / length;
printf("In C, the sum is %d\n", sum);
printf("In C, the average is %f\n", average);
// Get a class reference for java.lang.Double
jclass classDouble = (*env)->FindClass(env, "java/lang/Double");
// Allocate a jobjectArray of 2 java.lang.Double
jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);
// Construct 2 Double objects by calling the constructor
jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
if (NULL == midDoubleInit) return NULL;
jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
// Set to the jobjectArray
(*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
(*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);
return outJNIArray;
}

局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestJNIReference {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// A native method that returns a java.lang.Integer with the given int.
private native Integer getIntegerObject(int number);
// Another native method that also returns a java.lang.Integer with the given int.
private native Integer anotherGetIntegerObject(int number);
public static void main(String args[]) {
TestJNIReference test = new TestJNIReference();
System.out.println(test.getIntegerObject(1));
System.out.println(test.getIntegerObject(2));
System.out.println(test.anotherGetIntegerObject(11));
System.out.println(test.anotherGetIntegerObject(12));
System.out.println(test.getIntegerObject(3));
System.out.println(test.anotherGetIntegerObject(13));
}
}
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
#include <jni.h>
#include <stdio.h>
#include "TestJNIReference.h"
// Global Reference to the Java class "java.lang.Integer"
static jclass classInteger;
static jmethodID midIntegerInit;
jobject getInteger(JNIEnv *env, jobject thisObj, jint number) {
// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
printf("Find java.lang.Integer\n");
classInteger = (*env)->FindClass(env, "java/lang/Integer");
}
if (NULL == classInteger) return NULL;
// Get the Method ID of the Integer's constructor if missing
if (NULL == midIntegerInit) {
printf("Get Method ID for java.lang.Integer's constructor\n");
midIntegerInit = (*env)->GetMethodID(env, classInteger, "<init>", "(I)V");
}
if (NULL == midIntegerInit) return NULL;
// Call back constructor to allocate a new instance, with an int argument
jobject newObj = (*env)->NewObject(env, classInteger, midIntegerInit, number);
printf("In C, constructed java.lang.Integer with number %d\n", number);
return newObj;
}
JNIEXPORT jobject JNICALL Java_TestJNIReference_getIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
return getInteger(env, thisObj, number);
}
JNIEXPORT jobject JNICALL Java_TestJNIReference_anotherGetIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
return getInteger(env, thisObj, number);
}
1
2
3
4
5
6
7
8
9
10
// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
printf("Find java.lang.Integer\n");
// FindClass returns a local reference
jclass classIntegerLocal = (*env)->FindClass(env, "java/lang/Integer");
// Create a global reference from the local reference
classInteger = (*env)->NewGlobalRef(env, classIntegerLocal);
// No longer need the local reference, free it!
(*env)->DeleteLocalRef(env, classIntegerLocal);
}

相关问题

如何避免内存泄露

在多级类继承中, 如何找到方法是在哪个类实现的?

currentObject 是ContextWrapper 的子类的对象, mBase是定义在 currentObject某一个父类的字段,
mBase中的某个方法getSharedPerences()是定义在某个父类中的抽象方法,
如何确定该方法是在哪个父类中实现的?

1
2
3
4
5
6
7
try{
Field field = ContextWrapper.class.getDeclaredField("mBase");
field.setAccessible(true);
Object object = field.get(currentObject);
Log.i(TAG,"the real implement class is " + filed.getClass().getName());
}catch(Exception e){
}

getSharedPerences()方法一定定义在mBase类中. 妙哉!
获得mBase对象的类名就是该方法的实现类.

Android 网络开发

网络功能是 Android 的最基本的功能之一, 本文将详细介绍 Android 网络开发.

  1. Android网络编程专题之二:搭建测试服务器 目的是为了测试 Android 网络代码
  2. Android网络编程专题之三:模拟简单的网络应用
  • 下载并显示图片
  • HttpURLConnection GET和POST请求
  • HttpClient: apache-commons-httpclient GET和POST请求
  • WebView
  1. Android网络编程专题之四:第三方框架
  • AsyncHttpClient
    • post
    • get
    • 文件上传
  • SmartImage
  1. Android网络编程专题之五:多线程断点下载

Android配置

网络连接需要声明网络服务权限:

1
<uses-permission android:name="android.permission.INTERNET"/>

简单的服务器端

要求

为了支持Android的网络测试,搭建一个服务器端工程,满足如下几个要求:

  1. 并支持Session和Cookie
  2. 支持文件上传
  3. 图片查看

访问/SimpleServer进入登陆界面

登陆成功,显示相关的信息

登陆失败,显示失败信息

下载图片地址

/SimpleServer/gyy.jpg

文件上传

/SimpleServer/uploadFile.jsp

实现

Eclipse工程目录如下

  1. 创建打开/SimpleServer/的默认页面login.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登陆</title>
</head>
<body>
<form action="login" method="get">
姓名:<input type="text" name="name" ><br>
密码:<input type="password" name="password" ><br>
<input type="submit" value="get 提交">
</form>
<hr>
<form action="login" method="post">
姓名:<input type="text" name="name" ><br>
密码:<input type="password" name="password" ><br>
<input type="submit" value="post 提交">
</form>
</body>
</html>

上面有两个表单,分别用于使用get方法和post方法请求,发送请求到action–login

  1. web.xml中注册

web-app节点下创建welcome-file-list子节点
添加页面

1
2
3
4
5
6
<web-app ...>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
...
</web-app>

welcome-file是默认的欢迎页面,在不指定子页面的情况下,按照welcome-file-list的顺序寻找页面,直到找到页面。
如果所有的页面都找不到,则只能显示404了

  1. web.xml中注册login action
1
2
3
4
5
6
7
8
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>club.guadazi.ss.LoginAction</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
  1. LoginAction

页面登陆,登陆成功后将用户名记录到Session中,如果Session中用户名不为null则代表已经登陆。
如果登陆失败,输出错误信息。

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
package club.guadazi.ss;

public class LoginAction extends HttpServlet {
public LoginAction() {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession();
Object pwdAttribute = req.getParameter("password");
Object nameAttribute = req.getParameter("name");
if (pwdAttribute != null && "abc.123".equals((String) pwdAttribute)
&& nameAttribute != null) {
String name = (String) nameAttribute;
session.setAttribute("name", name);
resp.setContentType("text/html;charset=utf-8");
resp.sendRedirect("info.jsp");
} else {
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.print("<html><head>");
writer.print("<title>" + "欢迎 post" + "</title>");
writer.print("</head>");
writer.print("<center>登陆失败!</center>");
writer.flush();
writer.close();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
}

Servlet实现的过程,继承HttpServlet,HttpServlet类含有处理post和get请求的方法,重写相应的方法即可。
Session用于保存一次会话的所有信息,Session以键值对的形式保存信息。常常用于记录登陆信息,当登陆成功后记录用户信息到Session,
在其他的页面上可以判断是否登陆,如果未登陆,则显示登陆页面。
HttpServletRequest.getParameter可以获取页面表单在发请求时携带的参数。
resp.sendRedirect("info.jsp");页面重定向

  1. info.jsp 登陆成功后显示的页面

需要在头部增加Session中用户名判断信息

1
2
3
4
5
6
7
8
9
10
11
12
13
<%
HttpSession session2 = request.getSession();
Object nameObject = session2.getAttribute("name");
if (nameObject == null) {
/* response.sendRedirect("/SimpleServer"); */
%>
<center>
未登录 <a href="/SimpleServer">点击登陆</a>
</center>
<%
return;
}
%>

当为登陆时不再显示下面的信息直接return

  1. 上传文件页面
1
2
3
4
5
<form action="uploadFileAction" enctype="multipart/form-data"
method="post">
<input type="file" name="filename"><input type="submit"
value="Press"> to upload the file!
</form>

enctype必须指定为multipart/form-data

  1. 上传文件Action

使用Apache的commons-fileupload工具包在doPost方法中实现上传文件
commons-fileupload依赖commons-io包

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
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileUpload = new ServletFileUpload(factory);
if (!ServletFileUpload.isMultipartContent(req)) {
PrintWriter writer = resp.getWriter();
writer.println("is not multipart content!");
return;
}
try {
List<FileItem> list = fileUpload.parseRequest(req);
for (FileItem fileItem : list) {
if (fileItem.isFormField()) {
String name = fileItem.getName();
String value = fileItem.getString();
ServletOutputStream outputStream = resp.getOutputStream();
outputStream.write((name + ":" + value).getBytes());
outputStream.close();
} else {
String fileName = fileItem.getName();
String realPath = /* this.getServletContext().getRealPath */("/Users/Mariaaron/upload/");
System.out.println(realPath);
FileOutputStream out = new FileOutputStream(realPath + fileName);
InputStream in = fileItem.getInputStream();
byte buffer[] = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.close();
in.close();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}

源文件与war包的下载地址

简单网络操作

Android 网络

Android 网络连接线程

  1. 在 Android 中, 主线程负责更新 UI, 因此,主线程也称为 UI 线程.
  2. 除了ProcessBar等几个少数的控件外, 几乎所有的 UI 控件必须在主线程更新.
  3. 为了防止 UI 载入和更新出现卡顿, 网络连接/大图片载入以及一些大型的计算必须在子线程进行.
  4. 网络操作必须放在子线程, 在子线程使用 Handler 更新 UI.
    异步框架AsyncHttpClient就是基于这个原理实现的.

Android提供了在子线程更新 UI 的 API:
Activity.runOnUiThread(Thread thread) 方法,将 Runnable 中的任务放到UI 线程执行:

// 如果当前线程就是UI 线程立即执行,否者将把 Runnable 线程 join 到UI 线程中执行.

Java Socket

android简单聊天工具

URL

类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。

创建一个到 URL 的连接需要几个步骤:

openConnection() ———— 时间 —————-> connect()
对影响到远程资源连接的参数进行操作 ———— 时间 —————-> 与资源交互;查询头字段和内容。

  1. 通过在 URL 上调用 openConnection 方法创建连接对象。
  2. 处理设置参数和一般请求属性。
  3. 使用 connect 方法建立到远程对象的实际连接。
  4. 远程对象变为可用。远程对象的头字段和内容变为可访问。

使用以下方法修改设置参数:

setAllowUserInteraction
setDoInput
setDoOutput
setIfModifiedSince
setUseCaches

使用以下方法修改一般请求属性:

setRequestProperty

使用 setDefaultAllowUserInteraction 和 setDefaultUseCaches 可设置 AllowUserInteraction 和 UseCaches 参数的默认值。

上面每个 set 方法都有一个用于获取参数值或一般请求属性值的对应 get 方法。适用的具体参数和一般请求属性取决于协议。

在建立到远程对象的连接后,以下方法用于访问头字段和内容:

getContent
getHeaderField
getInputStream
getOutputStream

某些头字段需要经常访问。以下方法:

getContentEncoding
getContentLength
getContentType
getDate
getExpiration
getLastModifed

提供对这些字段的便捷访问。getContent 方法使用 getContentType 方法以确定远程对象类型;子类重写 getContentType 方法很容易。

一般情况下,所有的预连接参数和一般请求属性都可忽略:预连接参数和一般请求属性默认为敏感值。对于此接口的大多数客户端而言,只有两个需要的方法:getInputStream 和 getContent,它们通过便捷方法镜像到 URL 类中。

URLConnection

例子: 布局文件中的Button被调用时,调用loadImage方法

在loadImage方法中

  1. 判断收入的图片地址是否为空
  2. 启动新的子线程,在子线程中

`URLConnection urlConnection = url.openConnection();’
返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。
每次调用此 URL 的协议处理程序的 openConnection 方法都打开一个新的连接。

如果 URL 的协议(例如,HTTP 或 JAR)存在属于以下包或其子包之一的公共、专用 URLConnection 子类:java.lang、java.io、java.util、java.net,
返回的连接将为该子类的类型。例如,对于 HTTP,将返回 HttpURLConnection,对于 JAR,将返回 JarURLConnection。

‘InputStream inputStream = urlConnection.getInputStream();’
从打开的连接读取输入流

‘InputStream inputStream = urlConnection.openStream();’
openConnection().getInputStream()的缩写形式。

‘Bitmap bitmap = BitmapFactory.decodeStream(inputStream);`
把输入流解码成位图对象

  1. 创建Message,将位图对象作为Message.obj, 向Handler发送消息

  2. Handler执行,更新UI

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
private ImageView imageView;
public void loadImage(View view) {
final String imageUrl = imageUrlEdit.getText().toString().trim();
if (TextUtils.isEmpty(imageUrl)) {
return;
}
new Thread() {
@Override
public void run() {
try {
URL url = new URL(imageUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
if (urlConnection.getResponseCode() == 200) {
InputStream inputStream = urlConnection.getInputStream();
Bitmap bitmap;
bitmap = BitmapFactory.decodeStream(inputStream);
Message message = new Message();
message.what = SHOW_IMAGE_BY_URL;
message.obj = bitmap;
handler.sendMessage(message);
} else {
Toast.makeText(MainActivity.this, "请求失败!", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}

public void loadImage2(View view) {
final String imageUrl = imageUrlEdit.getText().toString().trim();
if (TextUtils.isEmpty(imageUrl)) {
return;
}
new Thread() {
@Override
public void run() {
try {
URL url = new URL(imageUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
if (urlConnection.getResponseCode() == 200) {
InputStream inputStream = urlConnection.getInputStream();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
/**
* runOnUiThread 方法,将 Runnable 中的任务放到UI 线程执行:<br/>
* 如果当前线程就是UI 线程立即执行,否者将把 Runnable 线程 join 到UI 线程中执行.
*/
runOnUiThread(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case showImageByUrl:
Bitmap bitmap = (Bitmap) msg.obj;
imageView.setImageBitmap(bitmap);
break;
}
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.iv);
}

HttpURLConnection

对于上面的例子,URL的scheme是HTTP类型的,URL.openConnection返回的对象就是HttpURLConnection对象。

get和post方式发送登录请求的代码和纯Java环境是一模一样的。

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
private void loginUrlGet(final String name, final String password) {
try {
String path = loginPath + "?name=" + name + "&password=" + password;
URL url = new URL(path);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
if (urlConnection.getResponseCode() == 200) {
InputStream inputStream = urlConnection.getInputStream();
String content = readAsString(inputStream);
showResponseOnWebView(content);
} else {
Toast.makeText(MainActivity.this, "请求失败!", Toast.LENGTH_SHORT).show();
}

} catch (IOException e) {
e.printStackTrace();
}
}

private void loginUrlPost(final String name, final String password) {
try {
Map<String, String> requestParams = new HashMap<String, String>();
requestParams.put("name", name);
requestParams.put("password", password);
StringBuilder params = new StringBuilder();
for (Map.Entry<String, String> entry : requestParams.entrySet()) {
params.append(entry.getKey());
params.append("=");
params.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
params.append("&");
}
if (params.length() > 0) params.deleteCharAt(params.length() - 1);
byte[] data = params.toString().getBytes();
URL url = new URL(loginPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");//必须大写
conn.setDoOutput(true);
conn.setRequestProperty("Connection", "Keep-Alive");//维持长连接
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
outStream.write(data);
outStream.flush();
if (conn.getResponseCode() == 200) {
String result = readAsString(conn.getInputStream());
outStream.close();
showResponseOnWebView(result);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

上面的例子只是实现了登录,如果当服务器端使用了Session时,再访问其他页面登录信息是无法联系起来的。
而HttpClient可以

多线程下载

下文

HttpClient: apache-commons-httpclient

HttpClient类似于Http的客户端,也就是浏览器。
它会保存会话,记录登录信息,Cookie等

看登录并访问的例子

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
84
85
86
87
88
89
90
private void loginHttpClientPost(String name, String password) {
HttpClient httpClient = new DefaultHttpClient();
HashMap<String, String> map = new HashMap<String, String>();
map.put("name", name);
map.put("password", password);
String path = loginPath;
path += "?";
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
path += key + "=" + value;
path += "&";
}
path = path.substring(0, path.length() - 1);
HttpPost httpPost = new HttpPost(path);
String responseString = "";
try {
HttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() == 200) {
responseString = EntityUtils.toString(response.getEntity());
} else {
responseString = "Error Response: "
+ response.getStatusLine().toString();
}

} catch (IOException e) {
e.printStackTrace();
}
showResponseOnWebView(responseString);
}

private void loginHttpClientGet(final String name, final String password) {
// 创建 HttpParams 以用来设置 HTTP 参数(这一部分不是必需的)
BasicHttpParams httpParams = new BasicHttpParams();
// 设置连接超时和 Socket 超时,以及 Socket 缓存大小
HttpConnectionParams.setConnectionTimeout(httpParams, 20 * 1000);
HttpConnectionParams.setSoTimeout(httpParams, 20 * 1000);
HttpConnectionParams.setSocketBufferSize(httpParams, 8192);
// 设置重定向,缺省为 true
HttpClientParams.setRedirecting(httpParams, true);
// 设置 user agent
String userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2) Gecko/20100115 Firefox/3.6";
HttpProtocolParams.setUserAgent(httpParams, userAgent);
// 创建一个 HttpClient 实例
// 注意 HttpClient httpClient = new HttpClient(); 是Commons HttpClient
// 中的用法,在 Android 1.5 中我们需要使用 Apache 的缺省实现 DefaultHttpClient
HttpClient httpClient = new DefaultHttpClient(httpParams);
Map params2 = new HashMap();
params2.put("name", name);
params2.put("password", password);
String url = loginPath;
/* 建立HTTPGet对象 */
String paramStr = "";
Iterator iter = params2.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
paramStr += paramStr = "&" + key + "=" + val;
}
if (!paramStr.equals("")) {
paramStr = paramStr.replaceFirst("&", "?");
url += paramStr;
}
HttpGet httpGet = new HttpGet(url);
String strResult = "doGetError";
try {
/* 发送请求并等待响应 */
HttpResponse httpResponse = httpClient.execute(httpGet);
/* 若状态码为200 ok */
if (httpResponse.getStatusLine().getStatusCode() == 200) {
/* 读返回数据 */
strResult = EntityUtils.toString(httpResponse.getEntity());
} else {
strResult = "Error Response: "
+ httpResponse.getStatusLine().toString();
}
} catch (ClientProtocolException e) {
strResult = e.getMessage().toString();
e.printStackTrace();
} catch (IOException e) {
strResult = e.getMessage().toString();
e.printStackTrace();
} catch (Exception e) {
strResult = e.getMessage().toString();
e.printStackTrace();
}
showResponseOnWebView(strResult);
}

示例工程中, 服务器端在登录成功后会跳转到info.jsp页面,详见第一节

使用URLConnection是无法实现的。而HttpClient可以实现跳转并显示登录信息。

WebView

WebView是Android内置的WebKit内核网页显示控件,使用WebView可以显示本地和网站页面。so powerful!
默认使用UTF-8编码

显示本地页面中文乱码

显示本地页面可以使用的API有:loadData 和loadDataWithBaseURL

WebView.loadData(content, “text/html”, “UTF-8”);出现中文乱码了,
改成 loadData(data, “text/html; charset=UTF-8”, null);就不会乱码

1
2
WebView.loadDataWithBaseURL(basePath, content, "text/html", "utf-8", null);// 可以正常显示
WebView.loadDataWithBaseURL(null, content, "text/html", "utf-8", null);// 无乱码 但无法显示图片

使用loadDataWithBaseURL时,如果不传入URL时,刷新会造成白屏,因为刷新时调用的的是reload方法,
reload是根据传入的URL进行一次重新加载即再次loadUrl(url),不传入URL时,默认的的URL是about:blank

使用loadData,刷新只是从缓存里面取,但是在4.0以上的,如果按照API里所写的loadData(data, “UTF-8”, null);时会乱码,
如果写成loadData(data, "text/html; charset=UTF-8", null);
loadData最终的机制是会把传入的三个参数拼接在一起,
然后再进行loadUrl操作,参数就是data, “text/html; charset=UTF-8”, null这三个进行拼装,加入text/html; charset=UTF-8就相当于限定了页面的字符

loadData(detail,”text/html;charset=UTF-8”, null); 在小米 One S上仍然存在中文乱码的情况,
改成 WebView.loadDataWithBaseURL(null, detail, “text/html”, “UTF-8”, null); 就没问题。
在小米手机上设置settings.setDefaultTextEncodingName("utf-8")
在 webview 的 settings 属性里指定 utf-8 就可以了

第三方框架

AsyncHttpClient

Github的地址

get

1
2
3
4
5
6
7
8
9
10
11
12
private String loginASyncHttpGet(String name, String password) {
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
RequestParams requestParams = new RequestParams();
requestParams.put("name", name);
requestParams.put("password", password);
asyncHttpClient.get(loginPath, requestParams, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(String s) {
return s;
}
});
}

post

1
2
3
4
5
6
7
8
9
10
11
12
private String loginASyncHttpPost(String name, String password) {
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
RequestParams requestParams = new RequestParams();
requestParams.put("name", name);
requestParams.put("password", password);
asyncHttpClient.post(loginPath, requestParams, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(String s) {
return 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
public void uploadFileAsyncHttpClient(String  selectFilePath) {
File file = new File(selectFilePath);
if (file.exists() && file.length() > 0) {
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
try {
params.put("profile_picture", file);

client.post(uploadUrlEditTex.getText().toString(), params, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(String s) {
Toast.makeText(MainActivity.this, "文件上传成功!", Toast.LENGTH_SHORT).show();
}

@Override
public void onFailure(Throwable throwable, String s) {
Toast.makeText(MainActivity.this, "文件上传失败!", Toast.LENGTH_SHORT).show();
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
}
}

SmartImage

Github地址

官网

多线程断点下载

示例代码下载地址

多线程下载

多线程下载即将一个文件分割成若干部分,交给多个线程同时去下载,每个线程下载文件的一部分。以达到提速的目的。

为了实现多线程下载,需要如下几个步骤:

  1. 为每个线程分配要下载的文件的位置
  2. 在本地创建同等大小的文件区域
  3. 从服务器下载文件的某一段数据
  4. 每个线程下载指定位置的数据并写入文件对应的位置

为每个线程分配要下载的文件的位置

在本地创建同等大小的文件区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/**
* 创建文件,大小与待下载的文件大小一致
* @param objPath 创建文件文件名,包含完整路径
* @param fileLength 文件的长度
*/
private void createFile(String objPath,long fileLength) {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(objPath, "rwd");
raf.setLength(fileLength);
raf.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

从服务器下载文件的某一段数据

在HTTP协议中, RequestProperty Range用于指定下载文件片段的范围
格式为"bytes=<开始位置>-<结束位置>"
一旦设定了Range属性,服务器会返回 206,表示支持部分下载。
不设定Range属性,服务器返回 200,表示连接正常, 可以下载。

因此发送请求的格式为:

1
2
3
4
5
6
7
8
9
//downloadUrl为下载文件的网址
URL url = new URL(downloadUrl);
//打开远程连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
//设置下载的位置
conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);
if (206 == conn.getResponseCode())
// DO SOMETHING

每个线程下载指定位置的数据并写入文件对应的位置

衔接上一段程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 从打开的连接读入输入流
InputStream inputStream = conn.getInputStream();
// 创建写入文件的RandomAccessFile对象
RandomAccessFile raf = new RandomAccessFile(objPath, "rwd");
//随机写文件,跳到相应的位置
raf.seek(startPosition);
int len = 0;
int lenCount = 0;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer)) != -1) {
lenCount += len;
raf.write(buffer, 0, len);
}
//最后记得关闭掉输入流
raf.close();
inputStream.close();

RandomAccessFile

RandomAccessFile是一个强大的随机文件读写API,创建该对象时的参数设置为"rwd"表示立即写入硬盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
RandomAccessFile
public RandomAccessFile(File file,
String mode)
throws FileNotFoundException
创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。将创建一个新的 FileDescriptor 对象来表示此文件的连接。
mode 参数指定用以打开文件的访问模式。允许的值及其含意为:
值含意
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 "rws""rwd" 模式的工作方式极其类似 FileChannel 类的 force(boolean) 方法,分别传递 truefalse 参数,除非它们始终应用于每个 I/O 操作,并因此通常更为高效。如果该文件位于本地存储设备上,那么当返回此类的一个方法的调用时,可以保证由该调用对此文件所做的所有更改均被写入该设备。这对确保在系统崩溃时不会丢失重要信息特别有用。如果该文件不在本地设备上,则无法提供这样的保证。
"rwd" 模式可用于减少执行的 I/O 操作数量。使用 "rwd" 仅要求更新要写入存储的文件的内容;使用 "rws" 要求更新要写入的文件内容及其元数据,这通常要求至少一个以上的低级别 I/O 操作。
如果存在安全管理器,则使用 file 参数的路径名作为其参数调用它的 checkRead 方法,以查看是否允许对该文件进行读取访问。如果该模式允许写入,那么还使用该路径参数调用该安全管理器的 checkWrite 方法,以查看是否允许对该文件进行写入访问。
参数:
file - 该文件对象
mode - 访问模式,如 上所述
抛出:
IllegalArgumentException - 如果此模式参数与 "r""rw""rws""rwd" 的其中一个不相等
FileNotFoundException - 如果该模式为 "r",但给定的文件对象不表示一个现有的常规文件,或者该模式以 "rw" 开头,但给定的文件对象不表示一个现有的可写常规文件,而且无法创建具有该名称的新常规文件,或者在打开或创建该文件时发生一些其他错误
SecurityException - 如果存在安全管理器,并且其 checkRead 方法拒绝对该文件的读取访问,或者该模式为 "rw",并且该安全管理器的 checkWrite 方法拒绝对该文件的写入访问
1
2
3
seek
public void seek(long pos)
throws IOException

seek是个牛逼的,像一个游标,跳到指定的位置写数据

断点下载

当下载大文件时,异常下载中断或暂停下载, 为了防止造成下载资源浪费,为每一个线程提供一个记录,用于记住已经下载文件的位置。当再次启动下载时,读取记录,从中断位置继续下载。

为了兼容不同设备(PC和Android)存储记录文件,本文定义一个接口为不同的存储提供不同的对象,类似于适配器模式.

  • SameDirFileBreakPointManager: 在目标文件同目录下建立记录文件(PC和Android)
  • SqliteBreakPointManager: 使用Sqlite记录(Android)
  • PropertyFileBreakPointManager:使用SharedPerferences记录(Android)

工程的调用关系

参考文献
  1. itheima的教程 (密码: yfqk)

  1. SimpleServer源码