曾大稳丶


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

自定义View(3) -- 字母索引

发表于 2018-03-08 | 分类于 自定义view |
字数统计: 788字 | 阅读时长 ≈ 4分钟

效果图:
字母索引

自定义view的流程,具体请点此查看:自定义view套路
我们先重写构造器,然后重写onMeasure函数进行测量设置宽高,在本例中,宽我是根据padding和测量一个字母w的宽度来设置的,高度就是默认的,因为一般是设置为match_parent.

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
public AlphabeticalIndexView(Context context) {
this(context, null);
}

public AlphabeticalIndexView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public AlphabeticalIndexView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = (int) (getPaddingLeft() + getPaddingRight() + DisplayUtil.getTextWidth("W", mPaint));
int h = getMeasuredHeight();
setMeasuredDimension(w, h);
}


private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(DisplayUtil.sp2px(context, 15));
}

因为不是viewGroup,所以不必重写onLayout函数,我们直接进入onDraw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 每个字母所占用的高度
for (int i = 0; i < mLetters.length; i++) {
String letter = mLetters[i];
if (letter.equals(mCurrentTouchLetter))
mPaint.setColor(Color.BLUE);
else
mPaint.setColor(Color.GRAY);

// 获取字体的宽度
float measureTextWidth = mPaint.measureText(letter);
// 获取内容的宽度
int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();

canvas.drawText(letter, getPaddingLeft() + (contentWidth - measureTextWidth) / 2, getPaddingTop() + mSingLetterHeight * i + DisplayUtil.getTextBaseLine(mPaint), mPaint);
}
}

这里可能有点疑惑,为什么要用内容的宽度减去字体的宽度然后除于2,这是因为每个字母的宽度都不一样,如果不这样做的画,那么比如I就会和A W等字母左对齐,我们这样做的目的就是为了都居中。
这样我们就实现了一个静态的页面没我们要让他触摸改变,我们就重写onTouchEvent进行操作,并且添加相应的回调。

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
@Override
public boolean onTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 获取当前手指触摸的Y位置
float fingerY = ev.getY();
int pos = (int) (fingerY / mSingLetterHeight);
if (pos > -1 && pos < mLetters.length && !mLetters[pos].equals(mCurrentTouchLetter)) {
mCurrentTouchLetter = mLetters[pos];
triggerTouchListener(true);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
triggerTouchListener(false);
break;
}

return true;
}

private void triggerTouchListener(boolean isTouch) {
if (mTouchListener != null)
mTouchListener.onTouch(mCurrentTouchLetter, isTouch);
}

// 设置触摸监听
private SideBarTouchListener mTouchListener;

public void setOnSideBarTouchListener(SideBarTouchListener touchListener) {
this.mTouchListener = touchListener;
}

public interface SideBarTouchListener {
void onTouch(String letter, boolean isTouch);
}

这样我们就可以实现了这个功能点。
优化思路
一个view绘制出来我们还需要进行优化,这一步是非常重要的。在本例中,我们的优化主要是在 onTouchEvent里面,因为我们知道,invalidate函数是一个一个做了很多事情的函数,具体请看:Android invalidate流程分析 我们要减少它的调用,所以我们应该先判断一下,如果当前的这个索引和触摸的这个不一样的话才调用invalidate,这样的话就优雅很多了。在本例中我没有将需要改变的属性,比如字体大小、选中颜色、默认颜色等提取在attr里面,需要的请自行添加。
参考:Android字母索引列表

源码github下载地址:
https://github.com/ChinaZeng/CustomView

自定义View(2) -- 58同城加载动画

发表于 2018-03-07 | 分类于 自定义view |
字数统计: 1,778字 | 阅读时长 ≈ 9分钟

先上图

思考步骤还是和上一篇讲的一样,相同的套路:
自定义View(1)–QQ运动计步器

构造器不说了,正常情况一般三个都会写,这篇我没从attr里面写东西,所以这一步跳过了,我们直接来要真正思考的地方:首先我们这里实现的话有很多种方式,可以用图片,可以自己绘制,这里我们选择自己绘制,但是绘制的话,我们又要考虑这个动画是需要我们自己算坐标然后重绘?还是写成单独的一个view然后通过动画实现,这里我选择了后者,原因有三个:第一个是如果在 view内部写相关的移动旋转逻辑的话,计算量不用说增加了,这个是很费时间的,而且很容易出错;第二个是因为你不停的调用invalidate,看了源码的都知道,调用这个函数的会做很多工作,造成不必要的gpu耗费,这样不好;第三个是实用性,如果以后我们需要这样一个图像,我们可以直接把这个形状的view复制过去就可以使用,所以我们理清思路。
首先,上面跳动的view作为一个单独的view,下面的弧形阴影也做为一个单独的view,然后在外层通过一个viewGroup把他们组装起来并且控制相关的代码和其性能的优化。
我们先绘制图形的 view,也就是上下跳动的哪个view。我们 需要组装,所以我们肯定要重写onMeasure设置其宽高,这里我打算指定他的高度和宽度为圆的半径的两倍:

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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}

/**
* Determines the width of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = mRadio * 2;
}

return result;
}

/**
* Determines the height of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 2 * mRadio;
}
return result;
}

接下来我们绘制图形,考略到我们需要变换不同形状的view,所以我写的时候分别写了三个绘制不同图案的函数,然后添加一个flag判断绘谁即可

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
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initCenterPoint();

if (mFlog == 0)
drawCircle(canvas);
else if (mFlog == 1)
drawRect(canvas);
else
drawTriangle(canvas);
}

private void initCenterPoint() {
if (mCenterX == -1 || mCenterY == -1) {
mCenterX = getMeasuredWidth() / 2;
mCenterY = getMeasuredHeight() / 2;
// setPivotX(mCenterX);
// setPivotY(mCenterY);
}
}

/**
* 画圆
*
* @param canvas
*/
private void drawCircle(Canvas canvas) {
canvas.drawCircle(mCenterX, mCenterY, mRadio, mCirclePaint);
}




/**
* 画正方形
*
* @param canvas
*/
private void drawRect(Canvas canvas) {
Rect rect = new Rect(mCenterX - mRadio,
mCenterY - mRadio,
mCenterX + mRadio,
mCenterY + mRadio);
canvas.drawRect(rect, mRectPaint);
}


public void setFlog(int mFlog) {
this.mFlog = mFlog;
invalidate();
}

/**
* 绘制三角形
*
* @param canvas
*/
private void drawTriangle(Canvas canvas) {
Path path = new Path();
path.moveTo(mCenterX, mCenterY - mRadio);
path.lineTo(mCenterX - mRadio, mCenterY + mRadio);
path.lineTo(mCenterX + mRadio, mCenterY + mRadio);
canvas.drawPath(path, mTrianglePaint);
}

测试一下这个图案已经绘制完成了,接下来我们绘制下面的阴影,同样的我们需要先测量,我这里设置宽高为半径值的2/3,宽为两倍的半径:

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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}

/**
* Determines the width of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = mRadio * 2;
}

return result;
}

/**
* Determines the height of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 2 * mRadio / 3;
}
return result;
}

接下来我们绘制图案,我在绘制这个图案的时候是采用drawArc的方式,所以我们需要计算一下它的范围,我这采用的方案如下:
因为我们要上面的那块横线挨着物块view掉下的位置,所以rect的中心点就是这个,然后计算如下:

1
2
3
4
5
int left = getWidth() / 2 - mRadio;
int top = -getHeight() / 2;
int right = getWidth() / 2 + mRadio;
int bottom = getHeight() / 2;
mRectF = new RectF(left, top, right, bottom);

其实getHeight就是2/3的半径
我们绘制的时候,绘制180°即可,:

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onDraw(Canvas canvas) {
if (mRectF == null) {
int left = getWidth() / 2 - mRadio;
int top = -getHeight() / 2;
int right = getWidth() / 2 + mRadio;
int bottom = getHeight() / 2;
mRectF = new RectF(left, top, right, bottom);
}
canvas.drawArc(mRectF, 0, 180, false, mArcPaint);
}

最后我们将这两个图案组装起来,加上动画即可:

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
 public void startAnim() {
if (mAnimatorSet == null) {
mAnimatorSet = new AnimatorSet();
//up
ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(mShapeView, "rotation", 0.0f, 360.0f);
rotationAnim.setDuration(JUMP_UP_TIME);
rotationAnim.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢

ObjectAnimator translationAnimUp = ObjectAnimator.ofFloat(mShapeView, "translationY", 0, -JUMP_MAX_HEIGHT);
translationAnimUp.setDuration(JUMP_UP_TIME);
translationAnimUp.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢

ObjectAnimator scaleXAnimUp = ObjectAnimator.ofFloat(mArcView, "scaleX", 1.0f, 0.2f);
scaleXAnimUp.setDuration(JUMP_UP_TIME);
scaleXAnimUp.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢

ObjectAnimator scaleYAnimUp = ObjectAnimator.ofFloat(mArcView, "scaleY", 1.0f, 0.2f);
scaleYAnimUp.setDuration(JUMP_UP_TIME);
scaleYAnimUp.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢

//down
ObjectAnimator translationAnimDown = ObjectAnimator.ofFloat(mShapeView, "translationY", -JUMP_MAX_HEIGHT, 0);
translationAnimDown.setDuration(JUMP_UP_TIME);
translationAnimDown.setInterpolator(new AccelerateInterpolator());//先慢后快

ObjectAnimator scaleXAnimXDown = ObjectAnimator.ofFloat(mArcView, "scaleX", 0.2f, 1.0f);
scaleXAnimXDown.setDuration(JUMP_UP_TIME);
scaleXAnimXDown.setInterpolator(new AccelerateInterpolator());//先慢后快

ObjectAnimator scaleYAnimDown = ObjectAnimator.ofFloat(mArcView, "scaleY", 0.2f, 1.0f);
scaleYAnimDown.setDuration(JUMP_UP_TIME);
scaleYAnimDown.setInterpolator(new AccelerateInterpolator());//先慢后快

mAnimatorSet.play(translationAnimUp)
.with(rotationAnim)
.with(scaleXAnimUp)
.with(scaleYAnimUp)
.before(translationAnimDown)
.before(scaleXAnimXDown)
.before(scaleYAnimDown);
mAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mFlog++;//圆形 0-->1方形 -->2 三角形 -->3->0---->
if (mFlog > 2) {
mFlog = 0;
}
mShapeView.setFlog(mFlog);
startAnim();
}
});
}
mAnimatorSet.start();
}

这样的话效果实现了,感觉没什么问题了。但是不要忘记了优化
优化
在这里我们主要优化这个动画对应Activity的生命周期 ,让其可见的时候加载动画,不可见的时候停止动画。主要使用Application.ActivityLifecycleCallbacks这个接口实现,具体实现的代码如下:

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
@Override
protected void onAttachedToWindow() {
mActivity.getApplication().registerActivityLifecycleCallbacks(animLifecyleCallback);
super.onAttachedToWindow();
}

@Override
protected void onDetachedFromWindow() {
mActivity.getApplication().unregisterActivityLifecycleCallbacks(animLifecyleCallback);
super.onDetachedFromWindow();
}

private SimpleActivityLifecycleCallbacks animLifecyleCallback = new SimpleActivityLifecycleCallbacks() {
@Override
public void onActivityResumed(Activity activity) { // 页面第一次启动的时候不会执行
if (activity == mActivity)
startAnim();
super.onActivityResumed(activity);
}

@Override
public void onActivityPaused(Activity activity) {
if (activity == mActivity)
stopAnim();
super.onActivityPaused(activity);
}
};

这样就更加优雅了。
源码github下载地址:
https://github.com/ChinaZeng/CustomView

自定义View(1)--QQ运动计步器

发表于 2018-03-06 | 分类于 自定义view |
字数统计: 1,202字 | 阅读时长 ≈ 5分钟

无图无真像:
弧度为270
弧度为360
在我们画一个图形之前,我们需要思考,我们先要解析他的步骤,然后根据步骤一步一步来完成。
思考:我们绘制View的一般基本流程为:

1.根据我们的需要,是需要new出来还是在布局文件里面添加,从而得到相应的构造方法。
2.我们需要什么属性?
3.我们是否需要测量?
4.如果是viewGroup我们需要是否要通过onLayout方法来摆放childView的位置?
5.调用onDraw()的时候先画什么,在画什么?然后调用相应的API完成相应的步骤即可。
6.性能优化。

我们从这个基本流程出发

  1. 我们希望这个view能够new出来,也能够在布局文件里面添加使用,所以构造函数如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public QQSportStepView(Context context) {
    this(context, null);
    }

    public QQSportStepView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
    }

    public QQSportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    }

2.我们需要什么属性?在这个view里面,我想实现的是希望其他人可以自己设置文字颜色,线条宽度,弧度大小等等,所以我们在att里面定义:










并且在初始化的时候解析出来:

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

private int mBottomColor;//底层圆的颜色
private int mTopColor;//顶层圆的颜色
private int mTextColor;//文字颜色

private int mMaxStepNum;//最大步数
private int mCurrentStepNum;//当前步数

private int mTextSize;//文字大小
private int mCircleRadio;//圆的半径
private int mCircleStrokeWidth;//圆线条的宽度

private float mArcAngle;//弧度大小
private float mStartAngle;//通过幅度计算出开始的角度位置

private void initAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.QQSportStep);
mBottomColor = ta.getColor(R.styleable.QQSportStep_bottomColor, Color.BLUE);
mTopColor = ta.getColor(R.styleable.QQSportStep_topColor, Color.RED);
mTextColor = ta.getColor(R.styleable.QQSportStep_textColor, Color.RED);
mMaxStepNum = ta.getInteger(R.styleable.QQSportStep_maxStepNum, 0);
mCurrentStepNum = ta.getInteger(R.styleable.QQSportStep_currentStepNum, 0);
mTextSize = ta.getDimensionPixelSize(R.styleable.QQSportStep_textSize, DisplayUtil.sp2px(context, 17));
mCircleRadio = ta.getDimensionPixelSize(R.styleable.QQSportStep_circleRadio, DisplayUtil.dip2px(context, 100));
mArcAngle = ta.getFloat(R.styleable.QQSportStep_arcAngle, 270.0f);
if (mArcAngle > 360.0f || mArcAngle < -360.0f)
mArcAngle = 360.0f;

mCircleStrokeWidth = ta.getDimensionPixelOffset(R.styleable.QQSportStep_circleStrokeWidth, DisplayUtil.dip2px(context, 5));
ta.recycle();
}

3.我们是否需要测量?因为这个view使用的时候一般是写好的固定的大小,所以不必要测量,所以我就没重写onMeasure方法
4.因为这是个view,没有childView,所以不用重写onLayout方法
5.解析步骤:我将这个view解析为三个步骤

1.画底部的圆弧
2.画顶部的圆弧
3.画文字

然后重写onDraw
画底部的圆弧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (mRectF == null) {
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
mRectF = new RectF(centerX - mCircleRadio, centerY mCircleRadio, centerX + mCircleRadio, centerY + mCircleRadio);
}

//1.画底部的圆
float gapAngle = mArcAngle - 180.0f;
if (mArcAngle >= 0) {//大于0表示在上方
mStartAngle = 180.0f - gapAngle / 2;
} else {//小于0表示在下方
mStartAngle = -gapAngle / 2;
}
canvas.drawArc(mRectF, mStartAngle, mArcAngle, false, mBottomPaint);

画顶部的圆弧

1
2
3
4
5
6
//2.画顶部的圆弧
float currentAngle = (float) mCurrentStepNum / mMaxStepNum * mArcAngle;
canvas.drawArc(mRectF, mStartAngle, currentAngle, false, mTopPaint);

if (mMaxStepNum <= 0)
return;

画文字

1
2
3
4
5
String step = String.valueOf(mCurrentStepNum);
int dx = (getWidth() - DisplayUtil.getTextWidth(step, mTextPaint)) / 2;
int baseLine = getHeight() / 2 + DisplayUtil.getTextBaseLine(mTextPaint);
// 绘制步数文字
canvas.drawText(step, dx, baseLine, mTextPaint);

5.性能优化(这一步很重要,请不要忽略它)
我们现在已经可以在手机屏幕上呈现一个静态的view了,但是我们要让他动起来,这里有两种方式,一种是在view内部写实现,一种是在view外部实现,为了降低耦合,我们最好是将动画实现的效果从外部实现。在动画的时候,一定是View不停的调用onDraw方法重绘,所以我们将重复的操作提取出来,一次就行了,不用每一次都在执行,比如:画笔初始化、一些计算等,这样能够降低gpu的消耗,当然如果实现的效果比较复杂的画,可以使用双缓冲的绘图方式来牺牲内存来换取时间,这样gpu就不会起伏太大。

最后我们在外部使用ValueAnimator来实现动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int maxStepNun = 100000;
qqSportStepView.setMaxStepNum(maxStepNun);
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(2000);
valueAnimator.setIntValues(0, maxStepNun);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
qqSportStepView.setCurrentStepNum(value);
}
});
valueAnimator.start();

总结:我们在做自定义一个view的时候,一定要先理清楚思路,我们要实现什么效果?我们要达到什么目的?然后在解析相应的步骤,最后调用相关的api一步步完成即可。
参考:自定义View - 仿QQ运动步数进度效果

本文源码下载地址:
https://github.com/ChinaZeng/CustomView

图

发表于 2017-12-30 | 分类于 数据结构 |
字数统计: 732字 | 阅读时长 ≈ 3分钟

图(Graph) 是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

图的特性

  • 线性表中我们把数据元素叫元素,树中将数据元素叫结点,在图中的数据元素我们称之为顶点(Vertex)。
  • 线性表中可以没有数据元素,成为空表。树中可以没有结点,叫做空树。
  • 线性表中,相邻的数据元素之间具有线性关系。树结构中,相邻两层的结点具有层次关系。而在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

无向图
无向图

有向图
有向图

图的权
图的权

连通图
连通图

图的度

图的度

图的存储结构 :主要有两种方式,一种是邻接矩阵,一种是邻接表。

邻接矩阵:

邻接矩阵

  • 邻接矩阵表示无向图
    邻接矩阵表示无向图
  • 邻接矩阵表示有向图
    邻接矩阵表示有向图

  • 邻接矩阵表示有权值的图
    邻接矩阵表示有权值的图

邻接表:

  • 邻接表表示无向图
    邻接表表示无向图

  • 邻接表表示有向图
    邻接表表示有向图

  • 邻接表表示有权值的图
    邻接表表示有权值的图

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
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
public class Graph {

private int vertexSize;//顶点数量 5个顶点
private int[] vertexs;//顶点 v0 v1 v2 v3 v4
private int[][] matrix;//邻接矩阵
private static final int MAX_WEIGHT = 1000; //表示无穷

public Graph(int vertexSize) {
this.vertexSize = vertexSize;
matrix = new int[vertexSize][vertexSize];
vertexs = new int[vertexSize];
for (int i = 0; i < vertexSize; i++) {
vertexs[i] = i;
}
}

/**
* 获取index顶点的的出度
*
* @param index 顶点index
*/
public int getOutDegree(int index) {
int degree = 0;
for (int i = 0; i < matrix[index].length; i++) {
int weight = matrix[index][i];
if (weight != 0 && weight != MAX_WEIGHT) {
degree++;
}
}
return degree;
}


/**
* 获取index顶点的的入度
*
* @param index 顶点index
*/
public int getInDegree(int index) {
int degree = 0;
for (int i = 0; i < matrix[index].length; i++) {
int weight = matrix[i][index];
if (weight != 0 && weight != MAX_WEIGHT) {
degree++;
}
}
return degree;
}

/**
* 获取v1到v2的权值
*
* @return v1到v2的权值权值
*/
public int getWeight(int v1, int v2) {
int weight = matrix[v1][v2];
return weight == 0 ? 0 : (weight == MAX_WEIGHT ? -1 : weight);
}


public static void main(String[] args) {
Graph graph = new Graph(5);//v0 -> v4
int[] v0 = new int[]{0, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 6};
int[] v1 = new int[]{9, 0, 3, MAX_WEIGHT, MAX_WEIGHT};
int[] v2 = new int[]{2, MAX_WEIGHT, 0, 5, MAX_WEIGHT};
int[] v3 = new int[]{MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0, 1};
int[] v4 = new int[]{MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0};


graph.matrix[0] = v0;
graph.matrix[1] = v1;
graph.matrix[2] = v2;
graph.matrix[3] = v3;
graph.matrix[4] = v4;


int v1OutDegree = graph.getOutDegree(1);
System.out.println("v1的出度 = " + v1OutDegree);//v1的出度 = 2

int v0InDegree = graph.getInDegree(1);
System.out.println("v0的入度 = " + v0InDegree);//v0的入度 = 2

int v02v4Weight = graph.getWeight(0, 4);
System.out.println("v0到v4的权值 = " + v02v4Weight);//v0到v4的权值 = 6
}

}

树(Tree)以及二叉树的遍历

发表于 2017-12-29 | 分类于 数据结构 |
字数统计: 1,924字 | 阅读时长 ≈ 8分钟

树(tree) 是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树的特点:

  • 每个节点有零个或多个子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;

常用到的术语:

  • 节点的度:一个节点含有的子树的个数称为该节点的度(上图 A->2 B->3 J->0);
  • 树的度:一棵树中,最大的节点的度称为树的度(上图B节点 3个度 最大);
  • 叶节点或终端节点:度为零的节点(上图A K O P);
  • 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点(上图A是BC的父节点 B是DEF的父节点);
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点(上图BC是A的子节点 DEF是B的子节点)`;
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点(上图BC DEF LM是相互的兄弟节点 );
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推(上图A为第1层 BC第2层 DEFGH第三层 ...);
  • 树的高度或深度 :树中节点的最大层次(上图为5层);
  • 堂兄弟节点:父节点在同一层的节点互为堂兄弟(上图FG为堂兄弟节点);
  • 森林:由m(m>=0)棵互不相交的树的集合称为森林;

种类

  • 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;
  • 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;
    • 二叉树:每个节点最多含有两个子树的树称为二叉树;
      • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
        • 满二叉树:所有叶节点都在最底层的完全二叉树(下图所示);
      • 平衡二叉树:当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
      • 排序二叉树(二叉查找树)(英语:Binary Search Tree),也称二叉搜索树、有序二叉树);
  • ……

满二叉树

完全二叉树

二叉树的性质:

  1. 在二叉树的第i层上至多有2^(i-1)个结点。
     2. 深度为k的二叉树之多有(2^k)-1个结点。
  2. 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点树为n2,则n0=n2+1。
  3. 具有n个结点的完全二叉树的深度为(logn)+1。
  4. 如果对一棵有n个结点的完全二叉树的结点按层序号遍历,对任意结点i有:
    如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点i/2。
    如果2i>n,则结点i无左孩子;否则其左孩子是结点2i。
    如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1.

二叉树的表示方法:

双亲表示法
在每个结点中,附设一个指示器指示其双亲结点到链表中的位置。这种方式返回来找父节点不方便
双亲表示法

孩子表示法:也不助于查找父节点

孩子兄弟表示法:

比较好的表示方案:借用HashMap的思想,一个组head表示父节点,一组都是该父节点的子节点。

二叉树的遍历:

  • 前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。 中–>左–>右

前序遍历

  • 中序遍历:若二叉树为空,则空操作返回,否则从根结点开始,中序遍历根节点的左子树,然后访问根结点,再中序遍历根结点的右子树。左–>中–>右

中序排列

  • 后序遍历:若二叉树为空,则空操作返回,否则从左到右先叶子后节点的方式遍历访问左子树和右子树,最后是访问根结点。左–>右–>中
    后序遍历
  • 层次遍历:若二叉树为空,则空操作返回,否则从树的第一层开始,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。一层一层的遍历

层次遍历

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
public class BinaryTree {
private TreeNode root = null;

public BinaryTree(){
root = new TreeNode(1, "A");
}

/**
* 构建二叉树
* A
* B C
* D E F
*/
public void createBinaryTree(){
TreeNode nodeB = new TreeNode(2, "B");
TreeNode nodeC = new TreeNode(3, "C");
TreeNode nodeD = new TreeNode(4, "D");
TreeNode nodeE = new TreeNode(5, "E");
TreeNode nodeF = new TreeNode(6, "F");
root.leftChild = nodeB;
root.rightChild = nodeC;
nodeB.leftChild = nodeD;
nodeB.rightChild = nodeE;
nodeC.rightChild = nodeF;
}

/**
* 求二叉树的高度
* @author Administrator
*
*/
public int getHeight(){
return getHeight(root);
}

private int getHeight(TreeNode node) {
if(node == null){
return 0;
}else{
int i = getHeight(node.leftChild);
int j = getHeight(node.rightChild);
return (i<j)?j+1:i+1;
}
}

/**
* 获取二叉树的结点数
* @author Administrator
*
*/
public int getSize(){
return getSize(root);
}


private int getSize(TreeNode node) {
if(node == null){
return 0;
}else{
return 1+getSize(node.leftChild)+getSize(node.rightChild);
}
}

/**
* 前序遍历——迭代
* @author Administrator
*
*/
public void preOrder(TreeNode node){
if(node == null){
return;
}else{
System.out.println("preOrder data:"+node.getData());
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}

/**
* 前序遍历——非迭代
*/

public void nonRecOrder(TreeNode node){
if(node == null){
return;
}
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(node);
while(!stack.isEmpty()){
//出栈和进栈
TreeNode n = stack.pop();//弹出根结点
//压入子结点
System.out.println("nonRecOrder data"+n.getData());
if(n.rightChild!=null){
stack.push(n.rightChild);

}
if(n.leftChild!=null){
stack.push(n.leftChild);
}
}
}
/**
* 中序遍历——迭代
* @author Administrator
*
*/
public void midOrder(TreeNode node){
if(node == null){
return;
}else{
midOrder(node.leftChild);
System.out.println("midOrder data:"+node.getData());
midOrder(node.rightChild);
}
}

/**
* 后序遍历——迭代
* @author Administrator
*
*/
public void postOrder(TreeNode node){
if(node == null){
return;
}else{
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.println("postOrder data:"+node.getData());
}
}
public class TreeNode{
private int index;
private String data;
private TreeNode leftChild;
private TreeNode rightChild;


public int getIndex() {
return index;
}


public void setIndex(int index) {
this.index = index;
}


public String getData() {
return data;
}


public void setData(String data) {
this.data = data;
}


public TreeNode(int index,String data){
this.index = index;
this.data = data;
this.leftChild = null;
this.rightChild = null;
}
}


public static void main(String[] args){
BinaryTree binaryTree = new BinaryTree();
binaryTree.createBinaryTree();
int height = binaryTree.getHeight();
System.out.println("treeHeihgt:"+height);
int size = binaryTree.getSize();
System.out.println("treeSize:"+size);
// binaryTree.preOrder(binaryTree.root);
// binaryTree.midOrder(binaryTree.root);
// binaryTree.postOrder(binaryTree.root);
binaryTree.nonRecOrder(binaryTree.root);
}
}


水平有限,文中有什么不对或者有什么建议希望大家能够指出,谢谢!

栈(Stack源码分析)

发表于 2017-12-28 | 分类于 数据结构 |
字数统计: 1,752字 | 阅读时长 ≈ 9分钟

栈(stack)

  1. 从数据结构的角度理解:是一组数据的存放方式,特点为LIFO,即后进先出(Last in, first out)。在这种数据结构中,数据像积木那样一层层堆起来,后面加入的数据就放在最上层。使用的时候,最上层的数据第一个被用掉,这就叫做”后进先出”。
    1. 从代码运行方式角度理解:是调用栈,表示函数或子例程像堆积木一样存放,以实现层层调用。程序运行的时候,总是先完成最上层的调用,然后将它的值返回到下一层调用,直至完成整个调用栈,返回最后的结果。
    2. 从内存区域的角度上理解:是存放数据的一种内存区域。程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做stack(栈),另一种叫做heap(堆)。
      参考链接:Stack的三种含义

(图片均来源于网络)


本文所述是站在数据结构的角度。
栈可以通过链表和数组实现,先看通过数组实现的方式。


可以通过查看Stack源码学习

可以看到Stack是Vector的子类,Vector本身是一个可增长的线程安全的对象数组( a growable array of objects)

里面主要是如下方法

1
2
3
4
5
6
7
8
9
10
E push(E item)   
把项压入堆栈顶部。
E pop()
移除堆栈顶部的对象,并作为此函数的值返回该对象。
E peek()
查看堆栈顶部的对象,但不从堆栈中移除它。
boolean empty()
测试堆栈是否为空。
int search(Object o)
返回对象在堆栈中的位置,以 1 为基数。

push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Pushes an item onto the top of this stack. This has exactly
* the same effect as:
* <blockquote><pre>
* addElement(item)</pre></blockquote>
*
* @param item the item to be pushed onto this stack.
* @return the <code>item</code> argument.
* @see java.util.Vector#addElement
*/
public E push(E item) {
addElement(item);

return item;
}

就是调用了Vector的addElement

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
/**
* Adds the specified component to the end of this vector,
* increasing its size by one. The capacity of this vector is
* increased if its size becomes greater than its capacity.
*
* <p>This method is identical in functionality to the
* {@link #add(Object) add(E)}
* method (which is part of the {@link List} interface).
*
* @param obj the component to be added
*/
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}

/**
* This implements the unsynchronized semantics of ensureCapacity.
* Synchronized methods in this class can internally call this
* method for ensuring capacity without incurring the cost of an
* extra synchronization.
*
* @see #ensureCapacity(int)
*/
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}


private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}

就是判断是否扩容,然后赋值即可

pop和peek

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
/**
* Removes the object at the top of this stack and returns that
* object as the value of this function.
*
* @return The object at the top of this stack (the last item
* of the <tt>Vector</tt> object).
* @throws EmptyStackException if this stack is empty.
*/
public synchronized E pop() {
E obj;
int len = size();

obj = peek();
removeElementAt(len - 1);

return obj;
}

/**
* Looks at the object at the top of this stack without removing it
* from the stack.
*
* @return the object at the top of this stack (the last item
* of the <tt>Vector</tt> object).
* @throws EmptyStackException if this stack is empty.
*/
public synchronized E peek() {
int len = size();

if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}

Vector中

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
 /**
* Deletes the component at the specified index. Each component in
* this vector with an index greater or equal to the specified
* {@code index} is shifted downward to have an index one
* smaller than the value it had previously. The size of this vector
* is decreased by {@code 1}.
*
* <p>The index must be a value greater than or equal to {@code 0}
* and less than the current size of the vector.
*
* <p>This method is identical in functionality to the {@link #remove(int)}
* method (which is part of the {@link List} interface). Note that the
* {@code remove} method returns the old value that was stored at the
* specified position.
*
* @param index the index of the object to remove
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}

/**
* Returns the component at the specified index.
*
* <p>This method is identical in functionality to the {@link #get(int)}
* method (which is part of the {@link List} interface).
*
* @param index an index into this vector
* @return the component at the specified index
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}

return elementData(index);
}

@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

先调用peek()得到需要出栈的对象,也就是数组顶部的对象,在调用Vector的removeElementAt移除。

empty

1
2
3
4
5
6
7
8
9
/**
* Tests if this stack is empty.
*
* @return <code>true</code> if and only if this stack contains
* no items; <code>false</code> otherwise.
*/
public boolean empty() {
return size() == 0;
}

search

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Returns the 1-based position where an object is on this stack.
* If the object <tt>o</tt> occurs as an item in this stack, this
* method returns the distance from the top of the stack of the
* occurrence nearest the top of the stack; the topmost item on the
* stack is considered to be at distance <tt>1</tt>. The <tt>equals</tt>
* method is used to compare <tt>o</tt> to the
* items in this stack.
*
* @param o the desired object.
* @return the 1-based position from the top of the stack where
* the object is located; the return value <code>-1</code>
* indicates that the object is not on the stack.
*/
public synchronized int search(Object o) {
int i = lastIndexOf(o);

if (i >= 0) {
return size() - i;
}
return -1;
}

参考链接:
java.util.Stack类简介


接下来看看使用链式的方式实现
链式结构
代码表示:

1
2
3
4
5
6
7
 private Node top = null;
private int number = 0;

class Node {
public T Item;
public Node Next;
}

入栈
入栈:将top的指向换为入栈的对象,然后将这个入栈的对象指向上一个入栈的对象即可。

代码表示:

1
2
3
4
5
6
7
public void push(T node) {
Node oldFirst = top;
top = new Node();
top.Item = node;
top.Next = oldFirst;
number++;
}

出栈
出栈:根据出栈的对象得到next,然后top指向即可。
代码表示:

1
2
3
4
5
6
public T pop() {
T item = top.Item;
top = top.Next;
number--;
return item;
}

一个伪代码类表示:

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
/**
* Created by zzw on 2017/6/27.
* Version:
* Des:
*/

public class StackLinkedList<T> {

private Node top = null;
private int number = 0;

class Node {
public T Item;
public Node Next;
}

public void push(T node) {
Node oldFirst = top;
top = new Node();
top.Item = node;
top.Next = oldFirst;
number++;
}

public T pop() {
T item = top.Item;
top = top.Next;
number--;
return item;
}
}

参考连接:
浅谈算法和数据结构: 一 栈和队列


水平有限,文中有什么不对或者有什么建议希望大家能够指出,谢谢!

LinkedHashMap和LruCache源码分析

发表于 2017-12-27 | 分类于 数据结构 |
字数统计: 2,410字 | 阅读时长 ≈ 13分钟

LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashMap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序。可以按照访问顺序和插入顺序来进行排序。LruCahe就是基于LinkedHashMap来实现的。
(图片均来源于网络)


建议先阅读HashMap源码分析在来阅读此篇文章。
先看看结构图,这有助于我们阅读源码:


HashMap里面进行key value绑定的类是HashMapEntry,在LinkedHashMap则是LinkedHashMapEntry,它继承HashMapEntry的一个类并且重写recordAccess recordRemoval来进行重新指向进行排序,里面remove函数主要对自身在链表中进行移除,addBefore(LinkedHashMapEntry<K,V> existingEntry)则是将自身插入到existingEntry之前。

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
/**
* LinkedHashMap entry.
*/
private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {
// These fields comprise the doubly linked list used for iteration.
LinkedHashMapEntry<K,V> before, after;

LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) {
super(hash, key, value, next);
}

/**
* Removes this entry from the linked list.
*/
// 对自身在链表中进行移除
private void remove() {
before.after = after;
after.before = before;
}

/**
* Inserts this entry before the specified existing entry in the list.
*/
//插入到LinkedHashMapEntry existingEntry之前
private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}

/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}

void recordRemoval(HashMap<K,V> m) {
remove();
}
}

有了上面的概念来看LinkedHashMap的操作就相对比较容易了。
LinkedHashMap put的时候是调用HashMap的put函数,只是自身实现了addEntry() createEntry()来实现自身的逻辑

HashMap

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
 /**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//调用子类LinkedHashMapEntry的recordAccess方法
e.recordAccess(this);
return oldValue;
}
}

modCount++;
//调用LinkedHashMap.addEntry方法
addEntry(hash, key, value, i);
return null;
}

/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
//扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//增加新的key value 调用LinkedHashMap.createEntry
createEntry(hash, key, value, bucketIndex);
}

LinkedHashMap

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
 /**
* This override alters behavior of superclass put method. It causes newly
* allocated entry to get inserted at the end of the linked list and
* removes the eldest entry if appropriate.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
// Previous Android releases called removeEldestEntry() before actually
// inserting a value but after increasing the size.
// The RI is documented to call it afterwards.
// **** THIS CHANGE WILL BE REVERTED IN A FUTURE ANDROID RELEASE ****

//得到需要移除的对象
// Remove eldest entry if instructed
LinkedHashMapEntry<K,V> eldest = header.after;
//需要移除的对象是否是自身
if (eldest != header) {
boolean removeEldest;
size++;
try {
//得到是否移除的对象的标识
removeEldest = removeEldestEntry(eldest);
} finally {
size--;
}
if (removeEldest) {
//调用HashMap的removeEntryForKey进行移除
removeEntryForKey(eldest.key);
}
}
//调用HashMap的addEntry
super.addEntry(hash, key, value, bucketIndex);
}

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}

/**
* Like addEntry except that this version is used when creating entries
* as part of Map construction or "pseudo-construction" (cloning,
* deserialization). This version needn't worry about resizing the table.
*
* Subclass overrides this to alter the behavior of HashMap(Map),
* clone, and readObject.
*/
//增加新的key value
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMapEntry<K,V> e = table[bucketIndex];
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}

我们主要看LinkedHashMap里面的东西,涉及到HashMap的就不重复说了。
如果key已经在该LinkedHashMap里面put了,那么将会重新进行赋值,并且调用LinkedHashMapEntry.recordAccess()进行排序,排序方式如果是访问顺序(accessOrder==true),将会将其在链表中移除,然后添加到链尾部。最后在返回oldValue。
如果key不存在,则会调用LinkedHashMap.addEntry,先得到需要移除的对象,就是header.after,接着判断需要移除的对象是否是自身,如果不是自生并且removeEldestEntry()函数返回的是true的话,那么将会调用HashMap的removeEntryForKey移除。移除这里后面在讲。
接着调用HashMap.addEntry进行扩容,在调用LinkedHashMap.createEntry,这一步就是重新new一个HashMapEntry,添加到链尾就可以了。
如图所示:

添加元素


remove过程也是调用HashMap.remove,然后调用LinkedHashMapEntry.recordRemoval函数来进行删除

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
/**
* Removes the mapping for the specified key from this map if present.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.getValue());
}

/**
* Removes and returns the entry associated with the specified key
* in the HashMap. Returns null if the HashMap contains no mapping
* for this key.
*/
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
HashMapEntry<K,V> prev = table[i];
HashMapEntry<K,V> e = prev;

while (e != null) {
HashMapEntry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
//调用LinkedHashMapEntry.recordRemoval
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}

return e;
}

LinkedHashMapEntry中:

1
2
3
4
5
6
7
8
9
10
11
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}

void recordRemoval(HashMap<K,V> m) {
remove();
}

很简单,就是进行一个指向。如图所示:

删除元素

get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>A return value of {@code null} does not <i>necessarily</i>
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*/
public V get(Object key) {
//调用HashMap的getEntry
LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}

通过HashMap.getEntry获取对应的值,然后调用LinkedHashMapEntry.recordAccess排序。


有了LinkedHashMap的基础我们来看LruCache的源码就很好理解了

put

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
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}

V previous;
synchronized (this) {
putCount++;
//根据key, value获取大小,我们需要重写sizeOf自行赋值
size += safeSizeOf(key, value);
//加入到LinkedHashMap
previous = map.put(key, value);
//如果key已经存在了previous 就!=null 就移除刚加的大小
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}

if (previous != null) {
//重写移除的逻辑
entryRemoved(false, key, previous, value);
}
//根据maxSize去除多余的元素
trimToSize(maxSize);
return previous;
}

remove

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
/**
* Removes the entry for {@code key} if it exists.
*
* @return the previous value mapped by {@code key}.
*/
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}

V previous;
synchronized (this) {
//从LinkedHashMap中移除
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}

if (previous != null) {
entryRemoved(false, key, previous, null);
}

return previous;
}

get

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
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}

V mapValue;
synchronized (this) {
//从LinkedHashMap中取出元素
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}

/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
//如果没有这个key,那么你可以重写create来创建一个新的value
V createdValue = create(key);
if (createdValue == null) {
return null;
}

//新的创建的value 添加到LinkedHashMap
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);

if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}

if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}


参考链接:
Java容器框架分析(七)——LinkedHashSet与LinkedHashMap
【Java集合源码剖析】LinkedHashmap源码剖析

水平有限,文中有什么不对或者有什么建议希望大家能够指出,谢谢!

HashMap源码分析

发表于 2017-12-26 | 分类于 数据结构 |
字数统计: 2,722字 | 阅读时长 ≈ 15分钟

HashMap是一个很经典的键值对集合,从它的广泛应用程度和源码的学习角度上我们不得不去解析它。
我们先看一下HashMap的存储结构((图片均来源于网络)),这有助于我们阅读源码

HashMap存储结构

HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对以及指引的下一个Entry

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
/** @hide */  // Android added.
static class HashMapEntry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
HashMapEntry<K,V> next;
int hash;

/**
* Creates new entry.
*/
HashMapEntry(int h, K k, V v, HashMapEntry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}

public final K getKey() {
return key;
}

public final V getValue() {
return value;
}

public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}

public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}

public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}

public final String toString() {
return getKey() + "=" + getValue();
}

/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}

/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}

初始化过程

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
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
} else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
initialCapacity = DEFAULT_INITIAL_CAPACITY;
}

if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Android-Note: We always use the default load factor of 0.75f.

// This might appear wrong but it's just awkward design. We always call
// inflateTable() when table == EMPTY_TABLE. That method will take "threshold"
// to mean "capacity" and then replace it with the real threshold (i.e, multiplied with
// the load factor).
threshold = initialCapacity;
init();
}

/**
* Initialization hook for subclasses. This method is called
* in all constructors and pseudo-constructors (clone, readObject)
* after HashMap has been initialized but before any entries have
* been inserted. (In the absence of this method, readObject would
* require explicit knowledge of subclasses.)
*/
void init() {
}

主要就是进行一个赋值
put过程:

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
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
//初始化table
inflateTable(threshold);
}
if (key == null)//put key==null的值
return putForNullKey(value);
//根据key得到hash
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
//根据hash得到下标
int i = indexFor(hash, table.length);
//进行next链表检测key是否已经存在
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key已经存在 将重新赋值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
//添加新的值
addEntry(hash, key, value, i);
return null;
}

首先检测是否是空的装HashMapEntry数组的table,如果是空的将调用inflateTable进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Inflates the table.
*/
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);

// Android-changed: Replace usage of Math.min() here because this method is
// called from the <clinit> of runtime, at which point the native libraries
// needed by Float.* might not be loaded.
float thresholdFloat = capacity * loadFactor;
if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
thresholdFloat = MAXIMUM_CAPACITY + 1;
}
threshold = (int) thresholdFloat;
table = new HashMapEntry[capacity];
}

接着检测key==null,如果为null,将调用putForNullKey函数给key==null的key赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}

从这就可以看出 HashMap的key可以为null
接下来就到了HashMap的关键地方,HashMap自己实现了一个key的Hash值计算,然后根据计算出的hash值和当前容器的table的长度进行&运算得到index,然后根据这个index确定需要放置的位置。

1
2
3
4
5
6
7
8
9
 int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}

这样计算的目的是为了根据hash值和table.length进行分组,也就是上面图示那样,然后通过链式的结构链接,这样的话就缩短了大量的查询时间。
拿到了所在的组,也就是下标位置,就拿到这个下标的HashMapEntry,然后进行next遍历,如果有存在的key就重新赋值返回即可。

1
2
3
4
5
6
7
8
9
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

如果没有存在的key,那么先判断是否扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
//判断扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);//扩容以及数据重组
hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建一个新的Entry添加
createEntry(hash, key, value, bucketIndex);
}

扩容的方式为当前容量的两倍

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
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
void resize(int newCapacity) {
HashMapEntry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}

HashMapEntry[] newTable = new HashMapEntry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

我们分组(下标)是根据table.length和key的hash值来决定的,所以在扩容之后,table.length变化了对应的分组(下标)就变化了,所以这时候需要重新组装数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Transfers all entries from current table to newTable.
*/
void transfer(HashMapEntry[] newTable) {
int newCapacity = newTable.length;
for (HashMapEntry<K,V> e : table) {
while(null != e) {
HashMapEntry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

组装的方式如下图所示
jdk1.8 hashMap扩容例图

最后根据hash key value index得到一个新的HashMapEntry对象,将原来组(下标)的HashMapEntry作为这个新的对象的next指向即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Like addEntry except that this version is used when creating entries
* as part of Map construction or "pseudo-construction" (cloning,
* deserialization). This version needn't worry about resizing the table.
*
* Subclass overrides this to alter the behavior of HashMap(Map),
* clone, and readObject.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMapEntry<K,V> e = table[bucketIndex];
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}

/**
* Creates new entry.
*/
HashMapEntry(int h, K k, V v, HashMapEntry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}

有了put的分析,get过程理解就比较轻松了

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
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>A return value of {@code null} does not <i>necessarily</i>
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*
* @see #put(Object, Object)
*/
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);

return null == entry ? null : entry.getValue();
}

/**
* Offloaded version of get() to look up null keys. Null keys map
* to index 0. This null case is split out into separate methods
* for the sake of performance in the two most commonly used
* operations (get and put), but incorporated with conditionals in
* others.
*/
private V getForNullKey() {
if (size == 0) {
return null;
}
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}

/**
* Returns the entry associated with the specified key in the
* HashMap. Returns null if the HashMap contains no mapping
* for the key.
*/
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}

int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}

先判断key值是否是null,如果是null的话那么将在第0组(下标)查找,如果不是的话就通过key的hash和table.length得到对应的组(下标)查找,查找的过程就是对其HashMapEntry进行next遍历查找判断即可。

remove过程

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
/**
* Removes the mapping for the specified key from this map if present.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.getValue());
}
/**
* Removes and returns the entry associated with the specified key
* in the HashMap. Returns null if the HashMap contains no mapping
* for this key.
*/
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
HashMapEntry<K,V> prev = table[i];
HashMapEntry<K,V> e = prev;

while (e != null) {
HashMapEntry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}

return e;
}

remove过程也是先得到对应的组(下标),然后申明一个HashMapEntry零时变量prev记录上一个指标,对当前组的HashMapEntry进行next遍历,在遍历过程中将值赋予prev,然后判断key相同重新将其prev的next指向接下来的哪个HashMapEntry即可。
如图所示:

remove


参考链接:
HashMap实现原理及源码分析
HashMap的扩容机制—resize()

水平有限,文中有什么不对或者有什么建议希望大家能够指出,谢谢!

队列(Queue)

发表于 2017-12-25 | 分类于 数据结构 |
字数统计: 1,998字 | 阅读时长 ≈ 11分钟

队列(Queue)

队列(Queue)是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素成为出队。因为队列只允许在一段插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
本文图片均来自网络

队列示意图


队列(Queue)和栈(Stack)一样也有链表和数组两种实现。

链表实现
链表
空队列

入列代码表示:

1
2
3
4
5
6
7
8
9
10
11
public void enqueue(T item) {
Node oldLast = last;
last = new Node();
last.item = item;
if (isEmpty()) {
top = last;
} else {
oldLast.next = last;
}
number++;
}

出列代码表示:

1
2
3
4
5
6
7
8
public T dequeue() {
T temp = top.item;
top = top.next;
number--;
if (isEmpty())
last = null;
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
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
/**
* Created by zzw on 2017/6/28.
* Version:
* Des:
*/

public class MyQuery<T> {

private Node top;
private Node last;
private int number;


class Node {
T item;
Node next;
}

public T dequeue() {
T temp = top.item;
top = top.next;
number--;
if (isEmpty())
last = null;
return temp;
}

public void enqueue(T item) {
Node oldLast = last;
last = new Node();
last.item = item;
if (isEmpty()) {
top = last;
} else {
oldLast.next = last;
}
number++;
}


private boolean isEmpty() {
return size() == 0;
}

private int size() {
return number;
}

}

图示如下:


使用数组实现的称为顺序储存,这种方式出队复杂度高并且容易假溢出。
入列

1
2
3
4
public E enqueue(E item) {
addElement(item);
return item;
}

出列

1
2
3
4
5
6
7
8
9
public E dequeue() {
if (size() <= front)
return null;

E obj = elementAt(front);
setElementAt(null, front);
front++;
return obj;
}

完整伪代码(继承Vector实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Created by zzw on 2017/6/28.
* Version:
* Des:
*/

public class MyQuery<E> extends Vector<E> {
int front = 0;

public E enqueue(E item) {
addElement(item);
return item;
}

public E dequeue() {
if (size() <= front)
return null;

E obj = elementAt(front);
setElementAt(null, front);
front++;
return obj;
}
}

如下图所示:


看看队列在Android里面的使用
Handle消息队列
使用Handle的时候都要使用Looper.loop()

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
 /**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}

final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();
}
}

//MessageQueue中
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

Handle 异步处理中用来存放Message对象的数据结构,按照“先进先出”的原则存放消息。存放并非实际意义的保存,而是将Message对象以链表的方式串联起来的。MessageQueue对象不需要我们自己创建,而是有Looper对象对其进行管理,一个线程最多只可以拥有一个MessageQueue。在Lopper方法中:出现了一个死循环,从队列中不断的取出message,执行msg.target.dispatchMessage(msg);

EventBus
在EventBus里面中,将消息封装成一个PendingPost

1
2
3
4
5
6
7
8
9
10
final class PendingPost {
private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
Object event;
Subscription subscription;
PendingPost next;
private PendingPost(Object event, Subscription subscription) {
this.event = event;
this.subscription = subscription;
}
}

在使用队列PendingPostQueue进行管理

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
final class PendingPostQueue {
private PendingPost head;
private PendingPost tail;
synchronized void enqueue(PendingPost pendingPost) {
if (pendingPost == null) {
throw new NullPointerException("null cannot be enqueued");
}
if (tail != null) {
tail.next = pendingPost;
tail = pendingPost;
} else if (head == null) {
head = tail = pendingPost;
} else {
throw new IllegalStateException("Head present, but no tail");
}
notifyAll();
}
synchronized PendingPost poll() {
PendingPost pendingPost = head;
if (head != null) {
head = head.next;
if (head == null) {
tail = null;
}
}
return pendingPost;
}
synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
if (head == null) {
wait(maxMillisToWait);
}
return poll();
}
}

Handler发送消息

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
final class HandlerPoster extends Handler {
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}

这里面有个死循环,不断的取出event,通过eventBus.invokeSubscriber(pendingPost);执行相关函数。

相关参考链接:
浅谈算法和数据结构: 一 栈和队列
队列在Android中的使用


水平有限,文中有什么不对或者有什么建议希望大家能够指出,谢谢!

线性表(ArrayList 和 LinkedList源码分析)

发表于 2017-12-25 | 分类于 数据结构 |
字数统计: 3,102字 | 阅读时长 ≈ 16分钟

线性表(linear list) 是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。

  • 线性表的相邻元素之间存在着序偶关系。a1是a2的前驱,ai+1 是ai的后继,a1没有前驱,an没有后继
  • n为线性表的长度 ,若n==0时,线性表为空表
  • 存储结构:
    1. 数序存储结构
    2. 链式存储结构
    

(图片均来源于网络)


顺序存储结构

特点: 存储位置连续,可以很方便计算各个元素的地址如每个元素占C个存储单元,那么Loc(An) = Loc(An-1) + C -> Loc(An) = Loc(A1)+(i-1)*C

顺序存储结构.png

优点:查询很快
缺点:插入和删除效率慢

在JAVA里面基本的顺序存储结构线性表是数组,ArrayList是基于它来完成对象的存储,来分析一下ArrayList(Android里面的)的源码

初始化过程:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
    /**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;

/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
* DEFAULT_CAPACITY when the first element is added.
*
* Package private to allow access from java.util.Collections.
*/
transient Object[] elementData;

/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;

/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}

/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}

```
从初始化的过程可以很明显的看出来,就是对内部的一个`数组`对象`elementData `进行初始化。

`add`过程:
```java
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}

add()的时候先判断当前数据容量是否足够,如果不足够那么扩容,扩容的值等于当前数组长度右移一位,也就是x2,然后添加到指定位置即可。
addAll()也是同样的方式,在这就不贴代码,可以自行查看一下源码。

remove过程:

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
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

modCount++;
E oldValue = (E) elementData[index];

int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}

remove过程就是得到对应的值的下标,然后将该下标之后的数据都向前移动一个坐标,最后一个赋值为null

set过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}

set()直接将其赋值即可

get过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

return (E) elementData[index];
}

get()就直接将数组里面值取出来即可。

从源码的角度我们更加的熟悉了顺序线性表的优缺点:查询很快,插入和删除效率慢。


链式存储结构

特点:用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。

链式存储结构.jpg

优点:插入和删除效率高
缺点:查询效率低

插入

删除

插入和删除只需改变next指向的地址即可,所以增删效率比较高。

查询

如上图那样,如果需要查找第9个元素,那么将要从第一个一直指向第九个,所以查找效率低。

链式存储结构又包含循环链表、双向循环链表、单向循环链表等。
单向循环链表就是上图那样的,一个指针对应下一个指针,直到结束,就如上面的那张图所示。
循环链表 : 将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相连的单链表称为单循环链表,简称循环链表

循环链表

双向循环链表: 双向循环链表是单向循环链表的每个结点中,再设置一个指向其前驱结点的指针域

双向循环链表

空的双向循环链表

LinkedList是一个双向循环链表,来看看LinkedList的源码

在LinkedList里面有一个Node类,这个类就是用来确定上一个指针prev和下一个指针next

1
2
3
4
5
6
7
8
9
10
11
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

add

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
91
92
93
94
95
96

/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}

/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);

if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}

/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}

/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}

/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}

/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);

if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

可以很直观的看出,add的时候,将new出一个新的Node对象newNode,然后把上一个Node对象last的next指向它,然后又将last重新赋值。当指定位置add的时候,就需要先找个这个位置的Node对象,然后更改next和prev即可。在指定下标插入的话那么将先判断这个下标是在前半段还是后半段,如果是前半段的话就从头开始next遍历查找,如果是后半部的就从尾prev遍历。add操作如下图所示

双向循环链表插入

remove:

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
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If this list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}

/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;

if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}

if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}

x.item = null;
size--;
modCount++;
return element;
}


/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}

和add差不多,找出相应的Node对象,然后重新对前后的Node重新进行指向即可。

remove主要操作所下图所示

双向循环链表的删除

get:

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
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}

/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);

if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

判断这个下标是在前半段还是后半段,如果是前半段的话就从头开始next遍历查找,如果是后半部的就从尾prev遍历。

set:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}

先查找Node,然后重新赋值即可。


水平有限,文中有什么不对或者有什么建议希望大家能够指出,谢谢!

1…78
曾大稳丶

曾大稳丶

80 日志
11 分类
20 标签
© 2018 — 2019 曾大稳丶
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4
访问人数 人 总访问量 次