二叉树的*近公共祖先(JS实现)

二叉树的*近公共祖先(JS实现)

1 题目
给定一个二叉树, 找到该树中两个指定节点的*近公共祖先。
百度百科中*近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,*近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的*近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的*近公共祖先是节点 5。因为根据定义*近公共祖先节点可以为节点本身。

链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree

2 思路
这道题用递归的方法,每个节点返回一个结果对象{p: true, q: false}用来标识该节点的孩子节点中,是否有两个指定节点。当p和q都为true时,该节点即为公共祖先节点

3代码
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/

/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
let findBoth = 0;
let father;
function d(node, p, q) {
let res = {}
if (!node) return res;

if (node.val === p.val) {
res.p = true;
findBoth++;
}
if (node.val === q.val) {
res.q = true;
findBoth++;
}

let resLeft = {};
let resRight = {};
if (findBoth < 2) {
resLeft = d(node.left, p, q);
resRight = d(node.right, p, q);
}

if ((res.p || resLeft.p || resRight.p) && (res.q || resLeft.q || resRight.q) && !father) father = node;

return Object.assign({}, res, resLeft, resRight);

}

d(root, p, q);

return father;
};

删除链表中的节点(JS实现)

删除链表中的节点(JS实现)

1 题目
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
现有一个链表 – head = [4,5,1,9],它可以表示为:
示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

链接:https://leetcode-cn.com/problems/delete-node-in-a-linked-list

2 思路
这道题思路很巧妙,我们不删除当前节点,而是删除当前节点后面的节点,并将后面节点的值赋给当前节点,*后看起来就像删除当前节点一样

3代码
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} node
* @return {void} Do not return anything, modify node in-place instead.
*/
var deleteNode = function(node) {
let temp = node.next;
node.val = temp.val;
node.next = temp.next;
temp.next = null;
};

除自身以外数组的乘积(JS实现)

除自身以外数组的乘积(JS实现)

1 题目
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。

链接:https://leetcode-cn.com/problems/product-of-array-except-self

2 思路
这道题我们用两个数组分别存储乘积,对于第i个元素,*个数组arr1[i]存储0…i-1的乘积,第二个数组arr1[i]存储i+1…n的乘积,*后的结果将二者相乘即可

3代码
/**
* @param {number[]} nums
* @return {number[]}
*/
var productExceptSelf = function(nums) {
const arr1 = [1];
const arr2 = [];

for (let i=1;i<nums.length;i++) {
arr1[i] = arr1[i-1] * nums[i-1];
}

arr2[nums.length-1] = 1;
for (let i=nums.length-2;i>=0;i–) {
arr2[i] = arr2[i+1] * nums[i+1];
}

const res = [];

for (let i=0;i<nums.length;i++) {
res[i] = arr1[i] * arr2[i];
}

return res;
};

android studio之简单调用摄像头并且获取其照片

1.首先让我们来理清一下其中的逻辑:拍一张照片,获取其路径,根据路径进行展示。

2.好了我们已经理清好逻辑了:那我们就想如何实现,首先我们要实现一个app调用拍照功能,很简单,我们学过用一个活动调用另一个活动的intent,很明显是有的,然后我们要如何让拍到的照片返回到我们的app呢,还记得intent有一个putExtra的功能对吧,它用于传输数据,但是它在启动一个摄像头媒体的时候作用就不同了,它会根据传输进去的不同键值而发挥不同的作用,而设定图片路径就是其中一个,它还有其他不同的功能,例如设定图片大小等,需要的可以自己去查,但这个键值需求第二个参数为一个uri,那么我们可以自己新建一个文件,并且生成它的uri,这样不就可以了吗?这个文件新建在哪里也是一个问题,android为我们提供了一个叫做app关联缓存目录的东西,那我们存在哪里可以了。下面给出具体代码。

3.代码:
1.主活动
package com.example.gdzc.cameraalbumtest;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
public static final int TAKE_POTHO=1;
private ImageView imageView;
private Button button;
private Uri uri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView=(ImageView)findViewById(R.id.picture);
button=(Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
File outImage=new File(getExternalCacheDir(),”output_image.jpg”);
try{
if(outImage.exists())
{
outImage.delete();
}
outImage.createNewFile();
}
catch (IOException e)
{
e.printStackTrace();
}
if(Build.VERSION.SDK_INT>=24)
{
uri= FileProvider.getUriForFile(MainActivity.this,”com.example.gdzc.cameraalbumtest.fileprovider”,outImage);
}
else
{
uri=Uri.fromFile(outImage);
}
Intent intent=new Intent(“android.media.action.IMAGE_CAPTURE”);
intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);
startActivityForResult(intent,TAKE_POTHO);
}
});
}
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data)
{
switch (requestCode)
{
case TAKE_POTHO:
if(resultCode==RESULT_OK)
{
try{
Bitmap bitmap= BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
imageView.setImageBitmap(bitmap);
}catch (FileNotFoundException e)
{
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
2.主布局:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
>
<Button
android:id=”@+id/button”
android:text=”Button”
android:layout_width=”match_parent”
android:layout_height=”wrap_content” />
<ImageView
android:layout_gravity=”center_horizontal”
android:id=”@+id/picture”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>
3.注册表:

<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.example.gdzc.cameraalbumtest”>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”></uses-permission>//关联目录的权限
<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />

<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<provider
android:authorities=”com.example.gdzc.cameraalbumtest.fileprovider”//必须与生成时uri的第二个参数相同
android:name=”android.support.v4.content.FileProvider”//固定格式
android:exported=”false”
android:grantUriPermissions=”true”>
<meta-data//用于指定具体的共享路径
android:name=”android.support.FILE_PROVIDER_PATHS”
android:resource=”@xml/file_paths”>//还没有要我们自己建

</meta-data>
</provider>
</application>

</manifest>
4.共享路径的文件:

<?xml version=”1.0″ encoding=”utf-8″?>
<paths xmlns:android=”http://schemas.android.com/apk/res/android”>
<external-path
name=”12″
path=””>//若为空那么就共享整个sd卡,也可以写具体的我们新建文件的路径

</external-path>
</paths>

 

Android studio实现调用相机实现拍照、录像、读取相册并展示在新的页面的功能

本篇文章代码实现的功能是调用手机自带的相机进行拍照、录像和读取相册的功能,并将照片或者视频在指定的页面展示的功能。
我的效果是*页的布局会有拍照、录像、读取相册以及展示视频的选项。选择拍照或读取相册后,跳转到新的Intent进行操作并展示在ImageView中;选择录像会打开摄像头开始录像并保存在手机sd卡某一确定的缓存路径下;选择展示视频会展示这一确定的缓存路径下的视频文件。
下面是代码部分:
首先要对AndroidMainifest.xml进行设置,主要是存储权限的获得

<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.mapscanner.mapscanner”>

<uses-feature
android:name=”android.hardware.camera2″
android:required=”true” />
<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />
<uses-feature android:name=”android.hardware.camera.autofocus” />

<uses-permission android:name=”android.permission.FLASHLIGHT” />
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />
<uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE” />
<uses-permission android:name=”android.permission.INTERNET” />

<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<action android:name=”android.media.action.IMAGE_CAPTURE” />

<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<provider
android:name=”android.support.v4.content.FileProvider”
android:authorities=”com.MapScanner.MapScanner”
android:exported=”false”
android:grantUriPermissions=”true”>
<meta-data
android:name=”android.support.FILE_PROVIDER_PATHS”
android:resource=”@xml/file_paths” />
</provider>

<activity android:name=”.Second_Activity” />
<activity android:name=”.Image_album_showActivity” />
</application>

因为上面有一句android:resource=”@xml/file_paths”,是说在xml文件夹下定义了file_paths的值,所以我们 右键Resource new一个category取名为xml,会看到Resource下面出现了一个名为xml的文件夹,再右键文件夹选择new-> XML resource file,创建一个资源文件,并输入下面代码。

<?xml version=”1.0″ encoding=”utf-8″?>
<paths xmlns:android=”http://schemas.android.com/apk/res/android”>
<external-path name=”my_images” path=”” />
</paths>

下面首先看一下布局文件MainActivity.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:id=”@+id/take_photo”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Take Photos” />

<Button
android:id=”@+id/take_video”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Take videos” />

<Button
android:id=”@+id/choose_from_album”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Choose From Album” />

<Button
android:id=”@+id/show_videos”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Show previous video” />
</LinearLayout>

下面是MainActivity内的内容,主要实现摁键的跳转以及参数传递的功能

package com.mapscanner.mapscanner;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

import java.io.File;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
public static final int CHOOSE_PHOTO = 2;

/*用来记录录像存储路径*/
File file = new File(Environment.getExternalStorageDirectory().getPath() + “/video.mp4”);//设置录像存储路径
Uri uri = Uri.fromFile(file);//文件转成Uri格式

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo);
Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);
Button takeVideo = (Button) findViewById(R.id.take_video);
Button showVideo = (Button) findViewById(R.id.show_videos);

//对照相功能的响应
takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在新的Intent里面打开,并且传递TAKE_PHOTO选项
Intent intent = new Intent();
intent.setClass(MainActivity.this, Image_album_showActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);

Bundle bundle = new Bundle();
bundle.putInt(“id”, TAKE_PHOTO);//使用显式Intent传递参数,用以区分功能
intent.putExtras(bundle);

MainActivity.this.startActivity(intent);//启动新的Intent
}
});

//设置相册选择的响应
chooseFromAlbum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在新的Intent里面打开,并且传递CHOOSE_PHOTO选项
Intent intent = new Intent();
intent.setClass(MainActivity.this, Image_album_showActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);

Bundle bundle = new Bundle();
bundle.putInt(“id”, CHOOSE_PHOTO);
intent.putExtras(bundle);

MainActivity.this.startActivity(intent);
}
});
//设置录像选择的响应
takeVideo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 保存录像到指定的路径
// File file = new File(“storage/sdcard1/video.mp4” );//设置录像存储路径
try {
if (file.exists()) {
file.delete();
}
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 激活系统的照相机进行录像,通过Intent激活相机并实现录像功能
Intent intent = new Intent();
intent.setAction(“android.media.action.VIDEO_CAPTURE”);
intent.addCategory(“android.intent.category.DEFAULT”);

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, 0);
}
});
showVideo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

//initVideoPath();
Intent intent = new Intent();
intent.setClass(MainActivity.this, Second_Activity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);
MainActivity.this.startActivity(intent);
setContentView(R.layout.activity_second_);
}
});
}
}

接着点击包名new一下,选择Activity->Empty Activity生成一组配套的布局和类的文件,取名为image_album_show实现对拍照和读取相册的响应。
下面是对拍照和读取相册进行响应的布局文件Activity_image_album_show.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:orientation=”vertical”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.Image_album_showActivity”>

<Button
android:id=”@+id/Return_Back_to_page1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Return to previous page” />

<ImageView
android:id=”@+id/V_Image”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center_horizontal”
/>
</LinearLayout>

下面是与Activity_image_album_show.xml配套的image_album_showActivity文件

package com.mapscanner.mapscanner;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;

public class Image_album_showActivity extends AppCompatActivity {
private ImageView picture;
private Uri imageUri;
private Button Return_page;
public static final int TAKE_PHOTO = 1;
public static final int CHOOSE_PHOTO = 2;

//接受前一个Intent传入的id
private Bundle bundle;
private int Show_Choice;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_album_show);
picture = (ImageView) findViewById(R.id.V_Image);
Return_page=(Button)findViewById(R.id.Return_Back_to_page1);
bundle = this.getIntent().getExtras();
Show_Choice=bundle.getInt(“id”);

Return_page.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent= new Intent();
intent.setClass(Image_album_showActivity.this, MainActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);
startActivity(intent);
}
});
//接收Intent传递的id值,并判断,照相功能为1,打开相册功能为2
switch (Show_Choice)
{
//如果传递为TAKE_PHOTO
case TAKE_PHOTO:{
File outputImage = new File(getExternalCacheDir(), “output_image.jpg”);
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//判断版本号
if (Build.VERSION.SDK_INT < 24) {
imageUri = Uri.fromFile(outputImage);
} else {
imageUri = FileProvider.getUriForFile(Image_album_showActivity.this, “com.MapScanner.MapScanner”, outputImage);
}
// 启动相机程序
Intent intent = new Intent(“android.media.action.IMAGE_CAPTURE”);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);

try {// 将拍摄的照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
}
break;
//如果传递为CHOOSE_PHOTO
case CHOOSE_PHOTO:
{
//如果没有权限则申请权限
if (ContextCompat.checkSelfPermission(Image_album_showActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(Image_album_showActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
//调用打开相册
openAlbum();
}
default:
break;
}
}
private void openAlbum() {
Intent intent = new Intent(“android.intent.action.GET_CONTENT”);
intent.setType(“image/*”);
startActivityForResult(intent, CHOOSE_PHOTO); // 打开相册
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
}
else {
Toast.makeText(this, “You denied the permission”, Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (Show_Choice) {
case 1:
try {// 将拍摄的照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
break;

case 2:

// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
// 4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
}
else {
// 4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
break;
default:
break;
}
}
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
Log.d(“TAG”, “handleImageOnKitKat: uri is ” + uri);

if (DocumentsContract.isDocumentUri(this, uri)) {
// 如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if(“com.android.providers.media.documents”.equals(uri.getAuthority())) {
String id = docId.split(“:”)[1]; // 解析出数字格式的id
String selection = MediaStore.Images.Media._ID + “=” + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
}
else if (“com.android.providers.downloads.documents”.equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse(“content://downloads/public_downloads”), Long.valueOf(docId));
imagePath = getImagePath(contentUri, null);
}
}
else if (“content”.equalsIgnoreCase(uri.getScheme())) {
// 如果是content类型的Uri,则使用普通方式处理
imagePath = getImagePath(uri, null);
}
else if (“file”.equalsIgnoreCase(uri.getScheme())) {
// 如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath); // 根据图片路径显示图片
}

private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}

private String getImagePath(Uri uri, String selection) {
String path = null;
// 通过Uri和selection来获取真实的图片路径
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}

private void displayImage(String imagePath) {
if (imagePath != null) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
picture.setImageBitmap(bitmap);
}
else {
Toast.makeText(this, “failed to get image”, Toast.LENGTH_SHORT).show();
}
}
}

下面再新建一个Activity->EmptyActivity用于对视频的展示,我取名为Second_Activity,设置了一个button用于返回前一个activity以及用于视频展示的videoview控件。
还是先看一下布局文件activit_second_.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:orientation=”vertical”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.Second_Activity”>

<Button
android:id=”@+id/Return_Back”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Return to previous page” />

<VideoView
android:id=”@+id/V_video”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center_horizontal”
/>
</LinearLayout>

下面是Second_Activity中的内容

package com.mapscanner.mapscanner;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

import java.io.File;

public class Second_Activity extends AppCompatActivity {
private VideoView mVideo;
private Button Turn_back;

File file = new File(Environment.getExternalStorageDirectory().getPath()+”/video.mp4″ );//设置录像存储路径
Uri uri = Uri.fromFile(file);//文件转成Uri格式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_);
Turn_back=(Button)findViewById(R.id.Return_Back);
mVideo=(VideoView)findViewById(R.id.V_video);
initVideoPath();
Turn_back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent= new Intent();
intent.setClass(Second_Activity.this, MainActivity.class);//也可以这样写intent.setClass(MainActivity.this, OtherActivity.class);
startActivity(intent);
}
});

}
private void initVideoPath() {
if (ContextCompat.checkSelfPermission(Second_Activity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(Second_Activity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
Toast.makeText(this, “have found video”, Toast.LENGTH_SHORT).show();
mVideo.setVideoURI(uri);
mVideo.setVideoPath(file.getAbsolutePath());
MediaController mediaController=new MediaController(this);
mVideo.setMediaController(mediaController);
mediaController.setMediaPlayer(mVideo);
mVideo.requestFocus();
mVideo.start();
}
}

以上代码主要借鉴与《*行代码》以及网上的一些实例,在理解后进行了综合的应用,解决了我需要的功能,值得一提的是,本实例调用的相机是利用Intent对本机的相机进行了捕获从而实现媒体设备的使用功能,还有一种实现方法是调用camera库实现此功能。我在其它文章会介绍camera的使用以及如何通过Socket实现实时视频的传输。

*后附上自己总结的一些经验,因为我都是用真机测试程序,有时会出现程序打不开闪退的现象,总结后主要有:
1、可能未获得权限,比如使用媒体设备,比如读取内存都需要获得权限,如果未获得权限就会闪退。
2、当跳转到一个新的页面时,都会有setContentView(R.layout.xxxxx_);
而对于button等的响应需要用到xxx=(Button)findViewById(R.id.xxx); findViewById必须放在setContentView后面,否则会找不到此按钮而闪退。

Android Studio 调用Camera实现拍照功能

今天写个Camera拍照的教程吧。本例子的流程为   首先通过SurfaceView将Camera的实时画面显示在屏幕上,然后通过点击拍照对当前画面进行捕捉,*后将获得的图片保存至本地。

首先创建一个SurfaceHolder实现对SurfaceView的回调,然后重写SurfaceCreate函数,实现对Camera的初始化等一系列工作:代码如下:
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.e(“TAG”,”——surfaceCreated——“);
try {
//这里我优先找后置摄像头,找不到再找前面的
int cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
if (cameraIndex == -1) {
cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
if (cameraIndex == -1) {
Log.e(“TAG”, “No Camera!”);
currentCameraType = CAMERA_NOTEXIST;
currentCameraIndex = -1;
return;
} else {
currentCameraType = FRONT;
}
} else {
currentCameraType = BACK;
}

//找到想要的摄像头后,就打开
if (mCamera == null) {
mCamera = openCamera(currentCameraType);
}

} catch (Exception e) {
e.printStackTrace();
}
}
本例子中,我首先找到想要打开的摄像头,这里的优先寻找后置摄像头,如果没有找到,再找前置的,代码如下:

/**
* 按要求查找摄像头
*
* @param camera_facing 按要求查找,镜头是前还是后
* @return -1表示找不到
*/
private int findBackOrFrontCamera(int camera_facing) {
int cameraCount = 0;
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
cameraCount = Camera.getNumberOfCameras();
for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraInfo.facing == camera_facing) {
return camIdx;
}
}
return -1;
}
当找到摄像头后,便打开Camera,其实打开Camera可以直接用open(CameraId)函数即可,但我在重新封装了一下,直接帖代码:

/**
* 按照type的类型打开相应的摄像头
*
* @param type 标志当前打开前还是后的摄像头
* @return 返回当前打开摄像机的对象
*/
private Camera openCamera(int type) {
int frontIndex = -1;
int backIndex = -1;
int cameraCount = Camera.getNumberOfCameras();

Camera.CameraInfo info = new Camera.CameraInfo();
for (int cameraIndex = 0; cameraIndex < cameraCount; cameraIndex++) {
Camera.getCameraInfo(cameraIndex, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
frontIndex = cameraIndex;
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
backIndex = cameraIndex;
}
}

currentCameraType = type;
if (type == FRONT && frontIndex != -1) {
currentCameraIndex = frontIndex;
return Camera.open(frontIndex);
} else if (type == BACK && backIndex != -1) {
currentCameraIndex = backIndex;
return Camera.open(backIndex);
}
return null;
}
然后在SurfaceChange对Camera进行一系列初始化(对摄像头初始化,就是设定图片格式,图片尺寸等赋值,要打开摄像头才可以初始化,否则会报错)
/**
* 初始化摄像头
* @param holder
*/
private void initCamera(SurfaceHolder holder){
Log.e(“TAG”,”initCamera”);
if (mPreviewRunning)
mCamera.stopPreview();

Camera.Parameters parameters;
try{
//获取预览的各种分辨率
parameters = mCamera.getParameters();
}catch (Exception e){
e.printStackTrace();
return;
}
//这里我设为480*800的尺寸
parameters.setPreviewSize(480,800);
// 设置照片格式
parameters.setPictureFormat(PixelFormat.JPEG);
//设置图片预览的格式
parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);
setCameraDisplayOrientation(this,currentCameraIndex,mCamera);
try{
mCamera.setPreviewDisplay(holder);
}catch(Exception e){
if(mCamera != null){
mCamera.release();
mCamera = null;
}
e.printStackTrace();
}
mCamera.startPreview();
mPreviewRunning = true;
}

/**
* 设置旋转角度
* @param activity
* @param cameraId
* @param camera
*/
private void setCameraDisplayOrientation(Activity activity,int cameraId,Camera camera){
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId,info);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch(rotation){
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
result = (info.orientation + degrees) % 360;
result = (360 – result) % 360;
}else{
result = (info.orientation – degrees +360) % 360;
}
camera.setDisplayOrientation(result);
}

当这些工作完成后,便可以对当前摄像头捕捉的画面进行拍照了:
/**
* 实现拍照功能
*/
public void takePhoto(){
Camera.Parameters parameters;
try{
parameters = mCamera.getParameters();
}catch(Exception e){
e.printStackTrace();
return;
}
//获取摄像头支持的各种分辨率,因为摄像头数组不确定是按降序还是升序,这里的逻辑有时不是很好找得到相应的尺寸
//可先确定是按升还是降序排列,再进对对比吧,我这里拢统地找了个,是个不精确的…
List<Camera.Size> list = parameters.getSupportedPictureSizes();
int size = 0;
for (int i =0 ;i < list.size() – 1;i++){
if (list.get(i).width >= 480){
//完美匹配
size = i;
break;
}
else{
//找不到就找个*接近的吧
size = i;
}
}
//设置照片分辨率,注意要在摄像头支持的范围内选择
parameters.setPictureSize(list.get(size).width,list.get(size).height);
//设置照相机参数
mCamera.setParameters(parameters);

//使用takePicture()方法完成拍照
mCamera.autoFocus(new Camera.AutoFocusCallback() {
//自动聚焦完成后拍照
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success && camera != null){
mCamera.takePicture(new ShutterCallback(), null, new Camera.PictureCallback() {
//拍照回调接口
@Override
public void onPictureTaken(byte[] data, Camera camera) {
savePhoto(data);
//停止预览
mCamera.stopPreview();
//重启预览
mCamera.startPreview();
}
});
}
}
});
}

/* *//**
* 快门回调接口,如果不想拍照声音,直接将new ShutterCallback()修改为null即可
*/
private class ShutterCallback implements Camera.ShutterCallback {
@Override
public void onShutter() {
MediaPlayer mPlayer = new MediaPlayer();
mPlayer = MediaPlayer.create(getApplicationContext(), R.raw.shutter);
try{
mPlayer.prepare();
}catch (IllegalStateException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
mPlayer.start();
}
}
这里要注意下,有些手机调用onAutoFocus函数,会返回失败,因为如果该手机无自动对焦,则无法执行对焦成功后的函数了。。。

当捕捉到数据后,便可以将这些数据保存至设定的地方了:

/**
* 设置照片的路径,具体路径可自定义
* @return
*/
private String setPicSaveFile(){
//创建保存的路径
File storageDir = getOwnCacheDirectory(this,”MyCamera/photos”);
//返回自定义的路径
return storageDir.getPath();
}

private File getOwnCacheDirectory(Context context, String cacheDir) {
File appCacheDir = null;
//判断SD卡正常挂载并且拥有根限的时候创建文件
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
hasExternalStoragePermission(context)){
appCacheDir = new File(Environment.getExternalStorageDirectory(),cacheDir);
}
if (appCacheDir == null || !appCacheDir.exists() && !appCacheDir.mkdirs()){
appCacheDir = context.getCacheDir();
}
return appCacheDir;
}

/**
* 检查是否有权限
* @param context
* @return
*/
private boolean hasExternalStoragePermission(Context context) {
int permission = context.checkCallingOrSelfPermission(“android.permission.WRITE_EXTERNAL_STORAGE”);
//PERMISSION_GRANTED=0
return permission == 0;
}
由于调用了系统Camera和对SD卡读写,所以在AndroidManifest需要申请权限:

<!–摄像头相关权限–>
<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />
<uses-feature android:name=”android.hardware.camera.autofocus” />
<!– 在SDCard中创建与删除文件权限 –>
<uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>
<!– 往SDCard写入数据权限 –>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
 

后记:google在Android5.0后推出了Camera的升级版—Camera2:

按照Android的官方说明,camera 2支持以下5点新特性,有兴趣的可以研究下:

(1)支持每秒30帧的全高清连拍。
(2)支持在每帧之间使用不同的设置。
(3)支持原生格式的图像输出。
(4)支持零延迟快门和电影速拍。
(5)支持相机在其他方面的手动控制,比如设置噪音消除的级别。

Android Studio | 直接调用系统摄像头拍照并回显、在SurfaceView中实现嵌入拍照并回显

1.直接调用系统摄像头拍照
功能实现:点击按钮,调用系统摄像头拍照之后,回显在imageviewl里面。

%title插图%num
public class register extends AppCompatActivity
{
private ImageView shotview ;//shotview定义在这里
//【warning】如果这里写成:private ImageView shotview = findViewById(R.id.imageView);会报错
//因为需要在onCreate()将类实例化之后,才可以进行初始化。
private File currentImageFile = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.regiester);
shotview = findViewById(R.id.imageView); //shotview的初始化在这里

Button takePhoto = findViewById(R.id.getcamera);
takePhoto.setOnClickListener(new View.OnClickListener()
{
//直接调用系统摄像头
@Override
public void onClick(View v)
{
//dir指的是directory,目录。storage directory 存储目录。

//如果你想在外存储上放公共文件你可以使用getExternalStoragePublicDirectory()
//but!如果你的api 版本低于8,那么不能使用getExternalStoragePublicDirectory(),
//而是使用Environment.getExternalStorageDirectory(),不带参数,不能自己创建一个目录,只是返回外部存储的根路径。
File dir = new File(Environment.getExternalStorageDirectory(),”pictures”);
//函数原型:File newFile=new File(directory, filename)
//创建了一个文件夹,名字是dir,路径是外部存储的根路径,名字是”pictures”。
if(dir.exists())
{
dir.mkdirs();//在根路径下建子目录,子目录名是”pictures”
}

//命名临时图片的文件名
currentImageFile = new File(dir,System.currentTimeMillis() + “.jpg”);
if(!currentImageFile.exists())
{
try
{
currentImageFile.createNewFile();
}
catch (IOException e)
{
e.printStackTrace();
}
}

Intent it = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//通过intent调用照相机照相
it.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(currentImageFile));
//存到外部存储的临时文件currentImageFile的路径下
startActivityForResult(it, Activity.DEFAULT_KEYS_DIALER);
//如果想在Activity中得到新打开Activity 关闭后返回的数据,
// 需要使用系统提供的startActivityForResult(Intent intent, int requestCode)方法
//打开新的Activity,新的Activity 关闭后会向前面的Activity传回数据,为了得到传回的数据,
//必须在前面的Activity中重写onActivityResult(int requestCode, int resultCode, Intent data)方法。
}
});
}
//重写onActivityResult(int requestCode, int resultCode, Intent data)方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == Activity.DEFAULT_KEYS_DIALER)
{
shotview.setImageURI(Uri.fromFile(currentImageFile));
}
}
}

关于内部存储与外部存储:

1、写一个相册程序,图片肯定是放在外部存储中;而如果要保存一个应用的一些设置数据,是放在内部存储的data目录下。因此其实在安卓文件管理中,我们都是在操作*对路径。

2、如今的设备,很多中高端机器都将自己的机身存储扩展到了8G以上,他们将存储在概念上分成了”内部internal” 和”外部external” 两部分,但其实都在手机内部。所以不管安卓手机是否有可移动的sdcard,他们总是有外部存储和内部存储。
3、你把手机连接电脑,能被电脑识别的部分就一定是外部存储。
4、公共文件Public files:文件是可以被自由访问,且文件的数据对其他应用或者用户来说都是由意义的,当应用被卸载之后,其卸载前创建的文件仍然保留。
私有文件Private files:外部存储上,应用私有文件的价值在于卸载之后,这些文件也会被删除。
5、如果你想在外存储上放公共文件你可以使用getExternalStoragePublicDirectory()。but!如果你的api 版本低于8,那么不能使用getExternalStoragePublicDirectory(),而是使用Environment.getExternalStorageDirectory(),它不带参数,不能自己创建一个目录,只是返回外部存储的根路径。
【引用总结自:个人认为的一个关于内部存储和外部存储的很好的总结】

2.将拍照界面通过SurfaceView嵌入自己写的界面,存储拍下的照片,照片回调显示

%title插图%num

其中关于surfaceView的设置:

<SurfaceView
android:id=”@+id/sfv_preview”
android:layout_width=”350dp”
android:layout_height=”393dp”
android:text=”@string/photo_preview”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintHorizontal_bias=”0.529″
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent”
app:layout_constraintVertical_bias=”0.135″ />

register.java中的代码如下:

public class register extends AppCompatActivity
{
private SurfaceView sfv_preview;
private Button btn_take_photo;
private Camera camera = null;

//surfaceHolder是surface的监听器,提供访问和控制SurfaceView背后的Surface 相关的方法
private SurfaceHolder.Callback cpHolderCallback = new SurfaceHolder.Callback()
{
@Override
public void surfaceCreated(SurfaceHolder holder)
//当surface对象创建后,该方法就会被立即调用。
{
startPreview();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
//当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用
{
}

@Override
public void surfaceDestroyed(SurfaceHolder holder)
//当surface对象在将要销毁前,该方法会被立即调用。
{
stopPreview();
}
};

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.regiester);
bindViews();
}

private void bindViews()
{
sfv_preview = (SurfaceView) findViewById(R.id.sfv_preview);
btn_take_photo = (Button) findViewById(R.id.getcamera);
sfv_preview.getHolder().addCallback(cpHolderCallback);
//获得sfv_preview的surfaceview,
//再由 addCallback为SurfaceHolder添加一个SurfaceHolder.Callback回调接口。

btn_take_photo.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)//按下“开始拍照”按钮
{
camera.takePicture(null, null, new Camera.PictureCallback()
//拍照函数
{
@Override
public void onPictureTaken(byte[] data, Camera camera)
{
String path = “”;//定义路径
if ((path = saveFile(data)) != null)//如果路径存在
{
Intent it = new Intent(register.this, show_registered_photo.class);
it.putExtra(“path”, path);
startActivity(it);
//用intent实现从当前界面跳转到预览照片的界面
}
else
{
Toast.makeText(register.this, “保存照片失败”, Toast.LENGTH_SHORT).show();
}
}
});
}
});
}

//保存临时文件的方法
private String saveFile(byte[] bytes){
try {
File file = File.createTempFile(“img”,””);
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytes);
fos.flush();
fos.close();
return file.getAbsolutePath();
}
catch (IOException e)
{
e.printStackTrace();
}
return “”;
}

//开始预览
private void startPreview()
{
camera = Camera.open();
try
{
camera.setPreviewDisplay(sfv_preview.getHolder());
camera.setDisplayOrientation(90); //让相机旋转90度
camera.startPreview();
}
catch (IOException e)
{
e.printStackTrace();
}
}

//停止预览
private void stopPreview() {
camera.stopPreview();
camera.release();
camera = null;
}
}

3.1遇到的小插曲(1):
代码没有错的情况下,和camera有关的方法都报错。如下图所示:

%title插图%num

%title插图%num
后来发现是因为import了错误的包,关于Camera有两个不同的包,在快捷import的时候有两个选项,如下图(画×的是我之前调用的包,画√的是导入了之后就不再报错的包),导入的时候要多注意:

%title插图%num
3.2 遇到的小插曲(2)
拍照界面跳出来之后,按下btn_take_photo按钮后闪退回登录界面。
报错原因:是因为show_registered_photo类没有注册,Alt+Enter快捷键,讲其写入 AndroidManifest.xml 即可。

InnoDB意外宕机不难解决,数据恢复原理

InnoDB如果发生意外宕机了,数据会丢么?

 

对于这个问题,稍微了解一点MySQL知识的人,都会斩钉截铁的回答:不会!

 

为什么?

 

他们也会毫不犹豫地说:因为有重做日志(redo log),数据可以通过redo log进行恢复。

 

回答得很好,那么InnoDB怎样通过redo log进行数据恢复的,具体的流程是怎样的?

 

估计能说清楚这个问题的人所剩不多了,更深入一点:除了redo log,InnoDB在恢复过程中,还需要其他信息么?比如是否需要binlog参与?undo日志在恢复过程中又会起到什么作用?

 

到这里,可能很多人会变得疑惑起来:数据恢复跟undo有半毛钱的关系?

 

其实,InnoDB的数据恢复是一个很复杂的过程,这个恢复过程需要redo log、binlog、undo log等参与。这里把InnoDB的恢复过程主要划分为两个阶段,*阶段主要依赖于redo log的恢复;而第二阶段,恰恰需要binlog和undo log的共同参与。

 

接下来,我们来具体了解下整个恢复的过程:

 

一、依赖redo log进行恢复

 

*阶段,数据库启动后,InnoDB会通过redo log找到*近一次checkpoint的位置,然后根据checkpoint相对应的LSN开始,获取需要重做的日志,接着解析获取的日志并且保存到一个哈希表中,*后通过遍历哈希表中的redo log信息,读取相关页进行恢复。

 

InnoDB的checkpoint信息保存在日志文件中,即ib_logfile0的开始2048个字节中,checkpoint有两个,交替更新,checkpoint与日志文件的关系如下图:

 

 

(checkpoint位置)

 

checkpoint信息分别保存在ib_logfile0的512字节和1536字节处,每个checkpoint默认大小为512字节,InnoDB的checkpoint主要由3部分信息组成:

 

  • checkpoint no:主要保存的是checkpoint号,因为InnoDB有两个checkpoint,通过checkpoint号来判断哪个checkpoint更新。
  • checkpoint lsn:主要记录了产生该checkpoint是flush的LSN,确保在该LSN前面的数据页都已经落盘,不再需要通过redo log进行恢复。
  • checkpoint offset:主要记录了该checkpoint产生时,redo log在ib_logfile中的偏移量,通过该offset位置就可以找到需要恢复的redo log开始位置。

 

通过以上checkpoint的信息,我们可以简单得到需要恢复的redo log的位置,然后通过顺序扫描该redo log来读取数据,比如我们通过checkpoint定位到开始恢复的redo log位置在ib_logfile1中的某个位置,那么整个redo log扫描的过程可能是这样的:

 

%title插图%num

(redo log扫描过程)

 

Step 1:从ib_logfile1的指定位置开始读取redo log,每次读取4 * page_size的大小,这里我们默认页面大小为16K,所以每次读取64K的redo log到缓存中,redo log每条记录(block)的大小为512字节。

 

Step 2:读取到缓存中的redo log通过解析、验证等一系列过程后,把redo log的内容部分保存到用于恢复的缓存recv_sys->buf,保存到恢复缓存中的每条信息主要包含两部分:(space,offset)组成的位置信息和具体redo log的内容,我们称之为body。

 

Step 3:同时保存在恢复缓存中的redo信息会根据(space,offset)计算一个哈希值后保存到一个哈希表(recv_sys->addr_hash)中,相同哈希值、不同(space,offset)用链表存储,相同的(space,offset)用列表保存,可能部分事务比较大,redo信息一个block不能保存,所以,每个body中可以用链表链接多body的值。

 

redo log被保存到哈希表中之后,InnoDB就可以开始进行数据恢复,只需要轮询哈希表中的每个节点获取redo信息,根据(space,offset)读取指定页面后进行日志覆盖。

 

在上面整个过程中,InnoDB为了保证恢复的速度,做了几点优化:

 

优化1:

 

在根据(space,offset)读取数据页信息到buffer pool的时候,InnoDB不是只读取一张页面,而是读取相邻的32张页面到buffer pool。这里有个假设,InnoDB认为,如果一张页面被修改了,那么其周围的一些页面很有可能也被修改了,所以一次性连续读入32张页面可以避免后续再重新读取。

 

优化2:

 

在MySQL5.7版本以前,InnoDB恢复时需要依赖数据字典,因为InnoDB根本不知道某个具体的space对应的ibd文件是哪个,这些信息都是数据字典维护的。而且在恢复前,需要把所有的表空间全部打开,如果库中有数以万计的表,把所有表打开一遍,整个过程就会很慢。那么MySQL5.7在这上面做了哪些改进呢?其实很简单,针对上面的问题,InnoDB在redo log中增加了两种redo log的类型来解决。MLOG_FILE_NAME用于记录在checkpoint之后,所有被修改过的信息(space,filepath);MLOG_CHECKPOINT则用于标志MLOG_FILE_NAME的结束。

 

上面两种redo log类型的添加,完美解决了前面遗留的问题,redo log中保存了后续需要恢复的space和filepath对。所以,在恢复的时候,只需要从checkpoint的位置一直往后扫描到MLOG_CHECKPOINT的位置,这样就能获取到需要恢复的space和filepath。在恢复过程中,只需要打开这些ibd文件即可。当然由于space和filepath的对应关系通过redo存了下来,恢复的时候也不再依赖数据字典。

 

这里需要强调的是MLOG_CHECKPOINT在每个checkpoint点中*多存在一次,如果出现多次MLOG_CHECKPOINT类型的日志,则说明redo已经损坏,InnoDB会报错。

 

*多存在一次,那么会不会有不存在的情况?

 

答案是肯定的,在每次checkpoint过后,如果没有发生数据更新,那么MLOG_CHECKPOINT就不会被记录。所以只要查找下redo log*新一个checkpoint后的MLOG_CHECKPOINT是否存在,就能判定上次MySQL是否正常关机。

 

5.7版本的MySQL在InnoDB进行恢复的时候,也正是这样做的,MySQL5.7在进行恢复的时候,一般情况下需要进行*多3次的redo log扫描:

 

  • 首先对redo log的扫描,主要是为了查找MLOG_CHECKPOINT,这里并不进行redo log的解析。如果你没有找到MLOG_CHECKPOINT,则说明InnoDB不需要进行recovery,后面的两次扫描可以省略;如果找到了MLOG_CHECKPOINT,则获取MLOG_FILE_NAME到指定列表,后续只需打开该链表中的表空间即可。
  • 下一步的扫描是在*次找到MLOG_CHECKPOINT基础之上进行的,该次扫描会把redo log解析到哈希表中,如果扫描完整个文件,哈希表还没有被填满,则不需要第三次扫描,直接进行recovery就结束。
  • *后是在第二次基础上进行的,第二次扫描把哈希表填满后,还有redo log剩余,则需要循环进行扫描,哈希表满后立即进行recovery,直到所有的redo log被apply完为止。

 

redo log全部被解析并且apply完成,整个InnoDB recovery的*阶段也就结束了,在该阶段中,所有已经被记录到redo log但是没有完成数据刷盘的记录都被重新落盘。

 

然而,InnoDB单靠redo log的恢复是不够的,这样还是有可能会丢失数据(或者说造成主从数据不一致)。

 

因为在事务提交过程中,写binlog和写redo log提交是两个过程,写binlog在前而redo提交在后,如果MySQL写完binlog后,在redo提交之前发生了宕机,这样就会出现问题:binlog中已经包含了该条记录,而redo没有持久化。binlog已经落盘就意味着slave上可以apply该条数据,redo没有持久化则代表了master上该条数据并没有落盘,也不能通过redo进行恢复。

 

这样就造成了主从数据的不一致,换句话说主上丢失了部分数据,那么MySQL又是如何保证在这样的情况下,数据还是一致的?这就需要进行第二阶段恢复。

 

二、binlog和undo log共同参与

 

前面提到,在第二阶段恢复中,需要用到binlog和undo log,下面我们就来看下具体的恢复逻辑是怎样的?

 

其实该阶段的恢复中,也被划分成两部分:*部分,根据binlog获取所有可能没有提交事务的xid列表;第二部分,根据undo中的信息构造所有未提交事务链表,*后通过上面两部分协调判断事务是否可以提交。

 

 

(根据binlog获取xid列表)

 

如上图所示,MySQL在第二阶段恢复的时候,先会去读取*后一个binlog文件的所有event信息,然后把xid保存到一个列表中,然后进行第二部分的恢复,如下:

 

 

(基于undo构造事务链表)

 

我们知道,InnoDB当前版本有128个回滚段,每个回滚段中保存了undo log的位置指针,通过扫描undo日志,我们可以构造出还未被提交的事务链表(存在于insert_undo_list和update_undo_lsit中的事务都是未被提交的),所以通过起始页(0,5)下的solt信息可以定位到回滚段,然后根据回滚段下的undo的slot定位到undo页,把所有的undo信息构建一个undo_list,然后通过undo_list再创建未提交事务链表trx_sys->trx_list。

 

基于上面两步, 我们已经构建了xid列表和未提交事务列表,那么在这些未提交事务列表中的事务,哪些需要被提交?哪些又该回滚?

 

判断条件很简单:凡是xid在通过binlog构建的xid列表中存在的事务,都需要被提交。换句话说,所有已经记录binlog的事务,需要被提交,而剩下那些没有记录binlog的事务,则需要被回滚。

 

三、回顾优化

 

通过上述两个阶段的数据恢复,InnoDB才*终完成整个recovery过程,回过头来我们再想想,在上述两个阶段中,是否还有优化空间?比如*阶段,在构造完哈希表后,事务的恢复是否可以并发进行?理论上每个hash node是根据(space,offset)生成的,不同的hash node之间不存在冲突,可以并行进行恢复。

 

或者在根据哈希表进行数据页读取时,每次读取连续32张页面,这里读取的32张页面,可能有部分是不需要的,也同时被读入到Buffer Pool中了,是否可以在构建一颗红黑树,根据(space,offset)组合键进行插入,这样如果需要恢复的时候,可以根据红黑树的排序原理,把所有页面的读取顺序化,并不需要读取额外的页面。

Android Studio | 单击按钮闪退、跳转界面闪退问题解决

问题1、单击按钮闪退:
String null 问题。

原报错代码:

%title插图%num

改正代码:

%title插图%num

原因:getText的返回值是一个Charsequence的接口,用.toString()方法转换成string即可。

问题2、单击按钮跳转界面闪退:
Activity没有注册。
在onClick(view v)函数中,代码如下:

%title插图%num

跳转失败的原因:没有注册希望要跳转到的界面(register界面)的Activity。Alt+Enter快捷键之后,选择add activity to maniefest。这样做就可以快捷地在AndroidManifest.xml中注册该Activity了。

%title插图%num
================= 更新============================
问题:在跳转的时候,点击按钮总是闪退

解决过程:
排除了未注册、代码逻辑错误等,*后发现是没有让java里的变量和xml文件里的控件绑定。绑定之后问题解决。这是个小错误,但是报错的提示往往不会指出是这个问题,而是定位在其他行,今后要多注意。

%title插图%num

MySQL中不要将字符串与数字比较

mysql在将字符串与整数比较时存在一个坑:

select * from xxxx
where xx_id =97

%title插图%num

得到下面的数据,可以看到第2列是字符串类型,明显与整数不相等,但是mysql却当做相等查询了出来,mysql在处理字符串与整形数据对比时,会依次将字符串与整形数据对比,直到字符串不为整数的字母为止!

所以我们在使用mysql时一定要注意这一点,使用同类型数据对比!