月度归档: 2021 年 6 月

二维码识别之Android完整编译Zbar

大概刚做Android开发的时候就做过二维码扫描,那时候懂的东西少,就搜出来了ZXing和Zbar两个库。ZXing是纯Java代码实现的,适用于Android平台;Zbar是C实现的,可以供很多语言和平台使用,比如Java、iOS平台、Android平台,Python等等。很明显Zbar的识别率和速度都是明显快于ZXing的,但是无奈那时候不会编译Zbar,只好下载了ZXing,但是由于当时技术能力不足,对于ZXing自定义剪切框也做不出来,只好下载了别人编译好的Zbar,可能由于别人修改了代码或者编译的不是很完整,后期bug层出,废了好大劲才完善好。

后来一直没有机会学习二维码扫描,直到前几天需要给我们平台的APP加上了二维码扫描功能,我决定使用ZBar,于是我完整的编译了一次,今天把这个过程记录下来,希望可以帮助到需要的同学。

比如微信使用的是ZXing,但是我肯定的说他们修改了不少源码,而且有很多地方应该改成了jni实现,所以微信的识别速率和准确率是相当高的,不过今天我编译后的封装也是秒秒钟就可以识别。

因为Zbar是基于LGPL-2.1开源的,因此我基于LGPL-2.1协议,我把一个完整的项目源码和sample放到Github上了,提供直接调用zbar的识别byte[]数据的功能和调用相机识别二维码的功能:
https://github.com/yanzhenjie/android-zbar-sdk

特别声明:本文已经修复了zbar识别中文乱码的问题!!!

编译Zbar
在正式编译之前要注意:编译Zbar需要先编译libiconv,编译libiconv需要linux环境,需要用到gcc。如果你没有linux环境也没有关系,我已经提供了编译好的libiconv。

其实在Zbar的官网也可以下载到他们已经编译好的so和jar,但是so文件他们只提供了armeabi、armeabi-v7a、x86平台:
https://sourceforge.net/projects/zbar/files/?source=navbar

所以我就抛弃了提供的编译包,自己编译了,下面是步骤。

首先在Zbar的开源主页下载Zbar源码:
https://github.com/ZBar/ZBar

顺便在开源主页点开android文件夹,发现编译Zbar需要libiconv,接下来下载libiconv:
http://www.gnu.org/software/libiconv

对于libiconv我是下载的在2017-02-02时发布的*新版1.15。

一、编译libiconv
如果你没有linux环境编译libiconv,那么你可以在这里下载我已经编译好的libiconv1.15:
http://download.csdn.net/detail/yanzhenjie1003/9833225,下好好文件后,你就可以直接跳过这一节,看下面Zbar和libiconv一起编译了。

如果你有linux环境可以编译libiconv,那么继续往下看。
下载好libiconv后,进入libiconv文件夹,如果报权限错误进不去的话执行sudo chmod 777 -R libiconv就可以了:

%title插图%num
进来后先执行:./configure,如果提示没权限那么执行:sudo chmod 777 configure,然后重新执行/.configure即可。

等./configure执行完后再执行make命令即可完成编译

编译时可能遇到以下错误:
1、configure: error: no acceptable C compiler found in $PATH
这个是说你没有安装gcc,安装gcc后再次执行未完成命令即可。

二、Zbar和libiconv一起编译
libiconv编译完成了,接下来把Zbar和libiconv放到一起,编译出我们需要的so文件。

把刚才编译好的libiconv放入我们项目的jni文件夹。
解压刚才下载好的Zbar,首先把Zbar的头文件所在文件夹zbar/include放入我们项目的jni文件夹下。
把Zbar对java的接口文件zbarjni.c放入我们项目的jni文件夹,zbrjni.c在zbar/java文件夹下。
把Zbar的核心库文件所在的文件夹zbar/zbar放到我们项目的jni文件夹下。
把Zbar编译时需要的Android.mk、Applicaiton.mk、config.h从zbar\android\jni下拷贝到我们项目的jni文件夹下。
此时我们项目的jni文件夹是这样的:

%title插图%num
理论上现在可以开始编译了吧,但是呢因为我们改动了zbar的文件夹结构,所以我们要对Android.mk进行改动,主要改的是文件夹路径和文件路径,修改后的Android.mk的内容如下:

MY_LOCAL_PATH := $(call my-dir)

# libiconv
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)
LOCAL_MODULE := libiconv
LOCAL_CFLAGS := \
-Wno-multichar \
-D_ANDROID \
-DLIBDIR=”c” \
-DBUILDING_LIBICONV \
-DBUILDING_LIBCHARSET \
-DIN_LIBRARY

LOCAL_SRC_FILES := \
libiconv-1.15/lib/iconv.c \
libiconv-1.15/libcharset/lib/localcharset.c \
libiconv-1.15/lib/relocatable.c

LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/libiconv-1.15/include \
$(LOCAL_PATH)/libiconv-1.15/libcharset \
$(LOCAL_PATH)/libiconv-1.15/libcharset/include

include $(BUILD_SHARED_LIBRARY)

LOCAL_LDLIBS := -llog -lcharset

# —————————————————–

# libzbar
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)
LOCAL_MODULE := zbar
LOCAL_SRC_FILES := \
zbarjni.c \
zbar/img_scanner.c \
zbar/decoder.c \
zbar/image.c \
zbar/symbol.c \
zbar/convert.c \
zbar/config.c \
zbar/scanner.c \
zbar/error.c \
zbar/refcnt.c \
zbar/video.c \
zbar/video/null.c \
zbar/decoder/code128.c \
zbar/decoder/code39.c \
zbar/decoder/code93.c \
zbar/decoder/codabar.c \
zbar/decoder/databar.c \
zbar/decoder/ean.c \
zbar/decoder/i25.c \
zbar/decoder/qr_finder.c \
zbar/qrcode/bch15_5.c \
zbar/qrcode/binarize.c \
zbar/qrcode/isaac.c \
zbar/qrcode/qrdec.c \
zbar/qrcode/qrdectxt.c \
zbar/qrcode/rs.c \
zbar/qrcode/util.c

LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include \
$(LOCAL_PATH)/zbar \
$(LOCAL_PATH)/libiconv-1.15/include

LOCAL_SHARED_LIBRARIES := libiconv

include $(BUILD_SHARED_LIBRARY)

 

然后在Application.mk中填写你要编译的平台,如果想全部编译:

APP_ABI := all
1
如果要指定编译某几个平台,把平台名称依次空格隔开写上即可:

APP_ABI := armeabi armeabi-v7a x86 x86_64 mips mips_64 arm64_v8a
1
此时我们用命令行进入项目的jni文件夹的父母路,比如一般jni情况下jni文件夹位于ProjectName/ModuleName/src/main/jni,那么我们就进入这个main,然后此时执行ndk-build进行编译。

如果提示没有ndk-build这个命令,那么你需要从http://developer.android.com下载ndk并且在电脑上配置PATH。

等ndk-build执行完后会在libs下生成所有平台的so文件夹,文件夹里面是需要的libiconv和zbar的so文件。

编译Zbar和libiconv时遇到的错误解决
编译过程中可能发现如下错误,请按照修改方案修改即可。

1、libiconv-1.15/jni/libcharset/lib/localcharset.c:51:24: error: langinfo.h: No such file or directory
打开libiconv-1.15/libcharset/config.h文件,搜索#define HAVE_LANGINFO_CODESET,大概在14行,把这行注释了即可:

/* #define HAVE_LANGINFO_CODESET 1 */
1
2、…c undeclaired…
打开libiconv-1.15/libcharset/lib/localcharset.c,搜索到函数get_charset_aliases(),大概在124行。

大概在195行左右,有一个int c;(没有的话你可以搜索int c;),把这个一行代码移动到get_charset_aliases()开头:

%title插图%num
zbar的jar包
现在so文件有了,剩下的就是怎么调用so中的函数来识别条码/二维码了,首先把zbar/java下在net.sourceforge.zbar包和里边的java文件拷贝到你的项目的java目录下,大概结构如下:

%title插图%num
当然你也像这样使用源码,也可以把这几个类打包成jar包。

调用Zbar识别二维码
现在全部都编译好了,jar文件也有了,我们可以调用jar中封装的方法来识别二维码了:

byte[] imageData = …;

Image barcode = new Image(size.width, size.height, “Y800”);
barcode.setData(imageData);
// 指定二维码在图片中的区域,也可以不指定,识别全图。
// barcode.setCrop(startX, startY, width, height);

String qrCodeString = null;

int result = mImageScanner.scanImage(barcode);
if (result != 0) {
SymbolSet symSet = mImageScanner.getResults();
for (Symbol sym : symSet)
qrCodeString = sym.getData();
}

if (!TextUtils.isEmpty(qrCodeString)) {
// 成功识别二维码,qrCodeString就是数据。
}

如何和相机结合使用等复杂操作这里不再说了,一个完整的项目我放到Github上了:
https://github.com/yanzhenjie/android-zbar-sdk

山高水远,江湖再见!

 

linux之exec命令详解

1、介绍shell script:
有两种方法执行shell scripts。
一种是新产生一个shell,然后执行相应的shell scripts;新产生一个shell然后再执行scripts的方法是在scripts文件开头加入以下语句。一般的script文件(.sh)即是这种用法。这种方法先启用新的sub-shell(新的子进程),然后在其下执行命令

#!/bin/sh

一种是在当前shell下执行,不再启用其他shell。即source命令,不再产生新的shell,而在当前shell下执行一切命令。source命令即点(.)命令。

source:
1
2、exec命令:
系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。

执行exec系统调用,一般都是这样,用fork()函数新建立一个进程,然后让进程去执行exec调用。我们知道,在fork()建立新进程之后,父进各与子进程共享代码段,但数据空间是分开的,但父进程会把自己数据空间的内容copy到子进程中去,还有上下文也会copy到子进程中去。而为了提高效率,采用一种写时copy的策略,即创建子进程的时候,并不copy父进程的地址空间,父子进程拥有共同的地址空间,只有当子进程需要写入数据时(如向缓冲区写入数据),这时候会复制地址空间,复制缓冲区到子进程中去。从而父子进程拥有独立的地址空间。而对于fork()之后执行exec后,这种策略能够很好的提高效率,如果一开始就copy,那么exec之后,子进程的数据会被放弃,被新的进程所代替。

3、 exec与system的区别:
(1) exec是直接用新的进程去代替原来的程序运行,运行完毕之后不回到原先的程序中去。
(2) system是调用shell执行你的命令,system=fork+exec+waitpid,执行完毕之后,回到原先的程序中去。继续执行下面的部分。
总之,如果你用exec调用,首先应该fork一个新的进程,然后exec. 而system不需要你fork新进程,已经封装好了。

4、exec I/O重定向:

exec ls //在shell中执行ls,ls结束后不返回原来的shell中了
exec //将file中的内容作为exec的标准输入
exec >file //将file中的内容作为标准写出
exec 3 //将file读入到fd3中
sort <&3 //fd3中读入的内容被分类
exec 4>file //将写入fd4中的内容写入file中
ls >&4 //Ls将不会有显示,直接写入fd4中了,即上面的file中
exec 5<&4 //创建fd4的拷贝fd5
exec 3<&- //关闭fd3

先上我们进如/dev/fd/目录下看一下:

%title插图%num

默认会有这四个项:
0是标准输入,默认是键盘。 stdin
1是标准输出,默认是屏幕/dev/tty stdout
2是标准错误,默认也是屏幕 stderr
255

对 2>&1详细说明一下 :2>&1 也就是 FD2=FD1 ,这里并不是说FD2 的值 等于FD1的值,因为 > 是改变送出的数据信道,也就是说把 FD2 的 “数据输出通道” 改为 FD1 的 “数据输出通道”。如果仅仅这样,这个改变好像没有什么作用,因为 FD2 的默认输出和 FD1的默认输出本来都是 monitor,一样的!

下面举例子说明:

liqian@liqian-Aspire-E1-471G:~$ exec>test
liqian@liqian-Aspire-E1-471G:~$ ls
liqian@liqian-Aspire-E1-471G:~$ pwd
liqian@liqian-Aspire-E1-471G:~$ echo “hello”
liqian@liqian-Aspire-E1-471G:~$ exec>/dev/tty
liqian@liqian-Aspire-E1-471G:~$ cat test
test
公共的
模板
视频
图片
文档
下载
音乐
桌面
/home/liqian
hello
liqian@liqian-Aspire-E1-471G:~$

说明:exec >text 是将当前shell的标准输出都打开到text文件中

root@liqian-Aspire-E1-471G:/home# cd /dev/fd
root@liqian-Aspire-E1-471G:/dev/fd# ls
0 1 2 255
root@liqian-Aspire-E1-471G:/dev/fd#
root@liqian-Aspire-E1-471G:/dev/fd# exec 3>/home/test
root@liqian-Aspire-E1-471G:/dev/fd# ls
0 1 2 255 3
root@liqian-Aspire-E1-471G:/dev/fd#

此处多了个3,也就是又增加了一个设备,这里也可以体会下linux设备即文件的理念。这时候fd3就相当于一个管道了,重定向到fd3中的文件会被写在test中。关闭这个重定向可以用exec 3>&-。

root@liqian-Aspire-E1-471G:/dev/fd# who >&3
root@liqian-Aspire-E1-471G:/dev/fd# ls >&3
root@liqian-Aspire-E1-471G:/dev/fd# exec 3>&-
root@liqian-Aspire-E1-471G:/dev/fd# cat /home/test
liqian :0 2015-12-09 21:20 (:0)
liqian pts/9 2015-12-09 22:11 (:0)

root@liqian-Aspire-E1-471G:/dev/fd#

android利用zbar二维码扫描

之前用zxing做开发,各种奇葩问题,横屏修等等,而且性能也不搞。被测试批了,没办法后来换了zbar。性能好多了。

/*********************重要更新*******************************/

有朋友提到两个问题:

1.扫描框目前只是做的假象,是全屏的图片进行解析

2.中文乱码现象

以上两个问题已经得到解决

/*************************/

直接上图,看看效果

%title插图%num

2.界面上的查找框

/**
* 2014-7-15 上午11:14:21
* Created By niexiaoqiang
*/

package com.example.qu;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

/**
* 查找框
* @author niexiaoqiang
*/
public class FinderView extends View {
private static final long ANIMATION_DELAY = 30;
private Paint finderMaskPaint;
private int measureedWidth;
private int measureedHeight;

public FinderView(Context context) {
super(context);
init(context);
}

public FinderView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(leftRect, finderMaskPaint);
canvas.drawRect(topRect, finderMaskPaint);
canvas.drawRect(rightRect, finderMaskPaint);
canvas.drawRect(bottomRect, finderMaskPaint);
//画框
zx_code_kuang.setBounds(middleRect);
zx_code_kuang.draw(canvas);
if (lineRect.bottom < middleRect.bottom) {
zx_code_line.setBounds(lineRect);
lineRect.top = lineRect.top + lineHeight / 2;
lineRect.bottom = lineRect.bottom + lineHeight / 2;
} else {
lineRect.set(middleRect);
lineRect.bottom = lineRect.top + lineHeight;
zx_code_line.setBounds(lineRect);
}
zx_code_line.draw(canvas);
postInvalidateDelayed(ANIMATION_DELAY, middleRect.left, middleRect.top, middleRect.right, middleRect.bottom);
}

private Rect topRect = new Rect();
private Rect bottomRect = new Rect();
private Rect rightRect = new Rect();
private Rect leftRect = new Rect();
private Rect middleRect = new Rect();

private Rect lineRect = new Rect();
private Drawable zx_code_kuang;
private Drawable zx_code_line;
private int lineHeight;

private void init(Context context) {
int finder_mask = context.getResources().getColor(R.color.finder_mask);
finderMaskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
finderMaskPaint.setColor(finder_mask);
zx_code_kuang = context.getResources().getDrawable(R.drawable.zx_code_kuang);
zx_code_line = context.getResources().getDrawable(R.drawable.zx_code_line);
lineHeight = 30;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureedWidth = MeasureSpec.getSize(widthMeasureSpec);
measureedHeight = MeasureSpec.getSize(heightMeasureSpec);
int borderWidth = measureedWidth / 2 + 100;
middleRect.set((measureedWidth – borderWidth) / 2, (measureedHeight – borderWidth) / 2, (measureedWidth – borderWidth) / 2 + borderWidth, (measureedHeight – borderWidth) / 2 + borderWidth);
lineRect.set(middleRect);
lineRect.bottom = lineRect.top + lineHeight;
leftRect.set(0, middleRect.top, middleRect.left, middleRect.bottom);
topRect.set(0, 0, measureedWidth, middleRect.top);
rightRect.set(middleRect.right, middleRect.top, measureedWidth, middleRect.bottom);
bottomRect.set(0, middleRect.bottom, measureedWidth, measureedHeight);
}
}

2.扫描的activity
package com.example.qu;

import net.sourceforge.zbar.Config;
import net.sourceforge.zbar.Image;
import net.sourceforge.zbar.ImageScanner;
import net.sourceforge.zbar.Symbol;
import net.sourceforge.zbar.SymbolSet;
import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainActivity extends Activity implements SurfaceHolder.Callback {
private Camera mCamera;
private SurfaceHolder mHolder;
private SurfaceView surface_view;
private ImageScanner scanner;
private Handler autoFocusHandler;
private AsyncDecode asyncDecode;

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

private void init() {
surface_view = (SurfaceView) findViewById(R.id.surface_view);
mHolder = surface_view.getHolder();
mHolder.addCallback(this);
scanner = new ImageScanner();
scanner.setConfig(0, Config.X_DENSITY, 3);
scanner.setConfig(0, Config.Y_DENSITY, 3);
autoFocusHandler = new Handler();
asyncDecode = new AsyncDecode();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mHolder.getSurface() == null) {
return;
}
try {
mCamera.stopPreview();
} catch (Exception e) {
}
try {
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.setPreviewCallback(previewCallback);
mCamera.startPreview();
mCamera.autoFocus(autoFocusCallback);
} catch (Exception e) {
Log.d(“DBG”, “Error starting camera preview: ” + e.getMessage());
}
}

/**
* 预览数据
*/
PreviewCallback previewCallback = new PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
if (asyncDecode.isStoped()) {
Camera.Parameters parameters = camera.getParameters();
Size size = parameters.getPreviewSize();
Image barcode = new Image(size.width, size.height, “Y800”);
barcode.setData(data);
asyncDecode = new AsyncDecode();
asyncDecode.execute(barcode);
}
}
};

private class AsyncDecode extends AsyncTask<Image, Void, Void> {
private boolean stoped = true;

@Override
protected Void doInBackground(Image… params) {
stoped = false;
Image barcode = params[0];
int result = scanner.scanImage(barcode);
if (result != 0) {
// mCamera.setPreviewCallback(null);
// mCamera.stopPreview();
SymbolSet syms = scanner.getResults();
System.out.println(syms);
for (Symbol sym : syms) {
Log.d(“xiaoqiang”, sym.getData());
}
}
return null;
}

@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
stoped = true;
}

public boolean isStoped() {
return stoped;
}
}

/**
* 自动对焦回调
*/
AutoFocusCallback autoFocusCallback = new AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
autoFocusHandler.postDelayed(doAutoFocus, 1000);
}
};

//自动对焦
private Runnable doAutoFocus = new Runnable() {
public void run() {
if (null == mCamera || null == autoFocusCallback) {
return;
}
mCamera.autoFocus(autoFocusCallback);
}
};

@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera = Camera.open();
} catch (Exception e) {
mCamera = null;
}
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
}
}

 

Shell中exec、source和shell script的区别

exec和source都属于bash内部命令(builtins commands),在bash下输入man exec或man source可以查看所有的内部命令信息。
bash shell的命令分为两类:外部命令和内部命令。外部命令是通过系统调用或独立的程序实现的,如sed、awk等等。内部命令是由特殊的文件格式(.def)所实现,如cd、history、exec等等。

在说明exe和source的区别之前,先说明一下fork的概念。

fork是linux的系统调用,用来创建子进程(child process)。子进程是父进程(parent process)的一个副本,从父进程那里获得一定的资源分配以及继承父进程的环境。子进程与父进程唯一不同的地方在于pid(process id)。

环境变量(传给子进程的变量,遗传性是本地变量和环境变量的根本区别)只能单向从父进程传给子进程。不管子进程的环境变量如何变化,都不会影响父进程的环境变量。

shell script:

有两种方法执行shell scripts,一种是新产生一个shell,然后执行相应的shell scripts;一种是在当前shell下执行,不再启用其他shell。
新产生一个shell然后再执行scripts的方法是在scripts文件开头加入以下语句
#!/bin/sh
一般的script文件(.sh)即是这种用法。这种方法先启用新的sub-shell(新的子进程),然后在其下执行命令。
另外一种方法就是上面说过的source命令,不再产生新的shell,而在当前shell下执行一切命令。

source:

source命令即点(.)命令。
在bash下输入man source,找到source命令解释处,可以看到解释”Read and execute commands from filename in the current shell environment and …”。从中可以知道,source命令是在当前进程中执行参数文件中的各个命令,而不是另起子进程(或sub-shell)。

exec:

在bash下输入man exec,找到exec命令解释处,可以看到有”No new process is created.”这样的解释,这就是说exec命令不产生新的子进程。那么exec与source的区别是什么呢?
exec命令在执行时会把当前的shell process关闭,然后换到后面的命令继续执行。

三大操作系统比较,为什么开发要用Linux系统?

交流学习java大数据加QQ群460570824.

windows, macOS,linux是当今主流三大操作系统,普通用户一般是选择windows或macOS, linux主要是占据服务器领域市场。这三个操作系统给人的感觉是:windows经济适用,档次较低; macOS高端大气上档次; Linux是*客专用,一个黑乎乎的窗口,各种花花绿绿的指令在闪烁,对着键盘噼里啪啦一通乱敲,就能窃取各种机密…

普通用户对于操作系统的选择很简单,经济实用选windows,钱多讲究格调选macOS, 毕竟普通用户切换操作系统的成本并不大。而程序员就不一样了,换一个操作系统,就意味着所有开发环境都要一并更换。而且在程序员群体中,操作系统有以下的鄙视链:macOS–>linux–>windows。

其实操作系统的比较并不是简单粗暴的单一维度比较,用windows并不意味着low,用linux也不等同于你就是一个*客。关键在于你是如何使用。就好比编程语言的选择,php是*招人黑的语言(没有之一), 但黑php的人中有不少人写的代码同样不堪入目。

不过作为一个程序员,你很有必要学会linux,如果你的程序*终是跑在线上的Linux服务器上,那么你就更应该从现在开始投入linux的怀抱中,早日从windows脱坑。至于macOS,在命令行上与linux*大部分相同,都是类unix的操作系统。简单地说,macOS是一个比windows界面更加美观,同时又兼备linux强大命令行的操作系统. 要说macOS的缺点,那就是贵…

windows与linux的根本区别:自由
%title插图%num

windows与linux的设计理念有根本性的区别:

windows:用户不知道自己想要什么,也不明白自己在做什么,更不打算为自己的行为负责。

因此windows将所有操作都隐藏起来,只给用户提供封装好的功能,用户只能在操作系统限制的范围内操作,如果是普通用户,会觉得很windows很舒服,因为不需要思考。只需要按照指示去操作。但对于开发人员而言,这种设计理念是无法接受的,一旦要做出一些超越封装好的功能之外的事情,就会出现各种难以意料的情况,而且很多情况下,这些问题是无解的。或者只能用*其蹩脚扭曲的方式去勉强处理,然后瑟瑟发抖地期待着程序能正常运行。因为一旦程序崩溃,你也会为之崩溃。在windows下作开发,那种体验就好比在陪伴一个任性的女朋友,虽然长得清秀靓丽,但喜怒无常,她开心时,彼此相安无事,她不开心时,就直接哭闹,你问她:怎么了?她也不说原因,只是一直哭。你只能试着用各种方式去哄她开心,即使这次能哄好她,并不意味着下次同样的方法能奏效。这样的相处方式,很累。不是她不好,而是彼此不适合。即使终日相伴,却依然对其一无所知。

linux:用户知道自己想要什么,也明白自己在做什么,并且会为自己的行为负责。

linux将所有操作权都交给了用户,她相信用户是理性的聪明的,忠实地执行用户的指令,向用户暴露所有的细节。用户在拥有自主权的同时也拥有了破坏力,因此普通用户根本无法驾驭,可能一个指令就把操作系统弄崩溃了。对于开发者而言,linux的开放与自由给了我们无限的可能性,我们能看到程序是如何运行的,运行报错也会有友好的提示。根据报错指引往往能将问题解决。与之相处,好比与一个成熟的女性相处,历经风雨,已经不再关注浮夸的外表,而是关注内在的涵养。刚接触时,可能会觉得她高冷,慢慢地,会发现她很善解人意,心情不好时她会跟你说是什么原因造成的,彼此商量如何解决这个问题,而不是无来由地崩溃。与之相处,会感到很舒服,彼此坦诚,无需套路,你在想什么,我都知道。我在念什么,你也明白。

因此,我选择linux的原因在于自由、舒服,简言之:爽!

至于win10自带的WSL(linux子系统), 说起来都是泪,几个月前,我兴冲冲地折腾,以为windows可以完美地支持linux,结果,连个nginx都跑不起来,去wsl的github源码查看issue,才知道这就是wsl的一个bug!此外,还有其他各种坑,这感觉就像:分手后的任性前女友画风突变:善解人意,人情练达。你欣喜万分地准备复合,她马上又原形毕露。咱能不能多些真诚,少些套路…

迅哥说过:生命是以时间为单位的,浪费别人的时间等于谋财害命,浪费自己的时间等于慢性自杀。对于程序员而言,开发环境远离windows,就是珍惜生命的表现(开发windows程序的除外)

选择哪个发行版的linux

redhat,suse, Debian等各个发行版的linux都有各自的忠实拥护者,无法判断这些发行版孰优孰劣,毕竟“存在即合理”。我从实用的角度来看,选择的是redhat(红帽)系列的centos。

因为线上的服务器大多数是ceotos,我们的程序*终是在线上运行的,如果能将本地环境配置得跟线上一致,就可以减少很多莫名其妙的环境问题。而且熟悉使用centos,基础的运维能力也都掌握了

至于版本选择,肯定是要选择*新的centos7, centos7与centos6存在很多差异,性能有很大的提升

如何学linux

工作中,看到不少工作好几年的程序员,在操作linux时特别生疏,只会*基本的几个命令:cdls mkdir 等。vim编辑器的操作更是让人看了很着急,被誉为编辑器之神的vim在他们手中,完全没有半点灵气,感觉就像是编辑器之屎。

之所以会出现这种现象,很大原因在于他们的学习方式错了,不少人都知道linux很重要,不掌握linux,基本上就只能停留在*基本的增删改查功能上。他们学linux的方式可能是看书,也可能是看视频,甚至有些还会去背命令,但由于他们的操作环境是windows,linux的应用场景很少。根本没办法将所学的知识运用,更加感受不到linux的魅力。

学linux*好地方式,就是直接去用!直接将自己的开发环境都改成linux,一开始很蹩脚,很不适应,这很正常。如果你一直感到很舒服,只能说明你一直没有进步。想想我们学了那么多年英语,*大多数人还是无法掌握英语,看到英语文档就直接自动屏蔽。其原因都是:一直在学,但从来没在用。只学而不用,没有半点用。

 

原来 Linux 内核贡献第二是这么来的

原来 Linux 内核贡献第二是这么来的
27 labulaka521 · labulaka521 · 4 小时 38 分钟前 · 9803 次点击
%title插图%num

Linux 内核 贡献 原来139 条回复 • 2021-06-21 16:46:18 +08:00
1 2
2
❮ ❯
ohwind 101
ohwind 1 小时 47 分钟前 ❤️ 2
@Iserlohn44 这贴下面的不都在讨论这个事?贡献无用代码增加工作量不能嘲讽两句吗?我觉得这种不道德的行为应该被谴责。
Samuelcc 102
Samuelcc 1 小时 46 分钟前 via Android ❤️ 1
@hwdef 你一直让我回答你的问题,我如实回答,没有任何说我“高大上”的意思。是的,大部分开发者没有给 linux 这样的核心项目提交过代码,你就咬定这一点就可以随便喷人了是吗?
另外我发 issue 是有具体的解决方案的,不是只提出问题让开发者去解决。另外这也是开源社区的一部分,我也没有看到哪个大型项目维护者辱骂提出 issue 的人,称他们恶心。你又是在那个位置和立场在这里怒骂呢?
另外我一直觉得你阅读是不是有障碍,邮件中并不是说这类 pr 是违反规则的,而是说华为这种大型公司不应该一直以提大量这种没有技术含量的 pr 来刷 kpi 。你好像一直没理解,令人遗憾。
wuqingdzx 103
wuqingdzx 1 小时 43 分钟前 ❤️ 1
有一说一. 这人及类似行为确实问题很大, 可耻.

但我觉得吧, 上升到公司层面的话, 在注水比例不明确之前, 不至于这么高潮吧?
washbrain 104
washbrain 1 小时 43 分钟前
看了下知乎,有一种可能是华为内部在做代码质量优化,作者可能是修改了公司内部的 linux 版本然后同步到上游?当然提交的方式不太正确
感觉还是有受公司政策影响的
Iserlohn44 105
Iserlohn44 1 小时 40 分钟前
@ohwind 你是不是理解不了什么叫「就事论事」?还有你仿佛只会二分的逻辑,实在让人无语。
ladypxy 106
ladypxy 1 小时 38 分钟前
有一说一,这种明显就是刷 KPI 的。
像我在某血汗工厂,公司会看你写了多少 CR 。然后大家就就故意一个 CR 修复一个 typo…虽然明明是一个 CR 搞定的事
hwdef 107
hwdef 1 小时 34 分钟前
@Samuelcc

我觉得你先开喷的,你觉得我先开喷的,

我们对于“喷”的定义不同,没必要讨论了。
atwoodSoInterest 108
atwoodSoInterest 1 小时 31 分钟前
子贡赎牛,老典故了。利益不是罪恶,合理利用利益来导向世界才是正解。

其实题上这种拍桌而起的都是内心干净的人,但是这种干净却又和世界无序脏乱冲突,变成了清厉。
我能理解这种人,因为我也深陷其中,至今仍在挣扎。
但是为社会为国为世界,不是他们这种偏执的人能胜任的,需要更有容量更有眼界的人。
ohwind 109
ohwind 1 小时 28 分钟前 ❤️ 8
@Iserlohn44
>就事论事,读音是 jiù shì lùn shì,汉语成语,依据事情本身的情况来作评论或判断。现又指仅从事物的表面现象孤立、静止、片面地议
以及究竟是谁只会二分?
一,华为是否做了这件事?做了。
二,这种行为是道德的吗?不道德。
三,针对这种行为可以谴责嘲讽吗?不道德的行为我认为嘲讽几句无关紧要。
请问是谁没有就事论事?
yukiww233 110
yukiww233 1 小时 27 分钟前
之前的 commit 还挺正常的啊…
2021 之后就几乎全是 fix error return code, Remove redundant, fix spelling 和 reformat , 分布还很均匀, 是 kpi 要求没跑了

shuAS 111
shuAS 1 小时 21 分钟前 ❤️ 1
刚一打开帖子看评论,我还以为进到品葱了呢?
idealhs 112
idealhs 1 小时 20 分钟前
@atwoodSoInterest 赎人受牛。
你想表达的是,华为的 KPI 导向有问题是吗。
lakehylia 113
lakehylia 1 小时 19 分钟前
想起了新的 HR 以代码量来评价工作量,于是就有了各种斗法
GalaDOS 114
GalaDOS 1 小时 14 分钟前
already broken reputation 很刺眼呀
Iserlohn44 115
Iserlohn44 1 小时 12 分钟前
@ohwind 原来在你看来无意义的嘲讽,而不是针对事情本身发出有建设性的评论也是就事论事。那确实不用聊了。
其次,你居然真能问出「究竟是谁只会二分」?不会看看自己的发言 – 「只许夸了是吧」?
nicevar 116
nicevar 1 小时 10 分钟前 ❤️ 4
总有些人逮住华为一丁点东西就能嘲讽整个华为甚至上升到民族和国家,脑袋被驴踢过一样,这个地方很多人黑华为已经没有下限了。
phh 117
phh 1 小时 4 分钟前 ❤️ 24
说黑华为没有下限的,那是因为你们吹华为没有底线让人家反感了,遭到反噬了,什么东西都要吹嘘上升到民族高度,先有华为后有天的话都敢说。
ohwind 118
ohwind 1 小时 1 分钟前
@Iserlohn44 对这件事建设性的评论就是这种行为十分无耻以及十分可恶,不然要怎么评论?说他干的好?讨论它的合理性?
对基本是非没有自己的判断,见不得别人对自己维护的产品的批评,这和娱乐圈玩的那一套有何区别?
或者你认为把别人的批评说成“高潮了”是富有建设性的?
dustinth 119
dustinth 57 分钟前
比较好奇这个帖子里面有没有华为的. 哈哈.
做错事就认, 没有做错事, 没有刷 KPI, 理直气壮的就怼回去好了.
turi 120
turi 56 分钟前
提到华为,一帮人就想到国家。

厉害了
sunocean 121
sunocean 55 分钟前 ❤️ 10
@nicevar 华为跟国家和民族有什么关系?你这帖子是想表达什么呢?一个公司做错事了,大众批评他就叫黑吗?一般认为,有事实有依据的事儿叫批评指正,无端臆造才叫黑。你无端把一个没有道德底线的公司和一个国家和民族捆绑在一起,我们有理由怀疑你脑袋被驴踢了,在黑这个国家,黑这个民族。
baiyi 122
baiyi 55 分钟前
华为用这种方式刷成贡献第二也是真的丢人,这样刷 KPI,公司有问题,人也有问题
ohwind 123
ohwind 54 分钟前
@Iserlohn44 请您正面回答我的问题。
1,“有些人真的高潮了,终于找到了喷点了哈哈”这句话是否是在就事论事,是否是在提出”建设性的评论”?
2,对于这种不道德的行为楼下的嘲讽和批评是否是理所应当的?
3,对于[1]的不友好发言我反驳”只允许夸了是吧”是否是在就他的评论”就事论事”?
ugly 124
ugly 48 分钟前
我真的很烦说讲不买华为不爱国思想的人,这事本来就是一群爱国的人在网上表示自己支持中国支持国产,被一群不在乎爱不爱国的人放大成恶心,嘲讽。
Iserlohn44 125
Iserlohn44 47 分钟前
@ohwind 你看,指出你发言的问题就给说成了「对基本是非没有自己的判断」。稍微说句不好听的,你也太把自己当回事了。
twor2 126
twor2 43 分钟前
@ohwind 我知道哪里别扭了,逻辑错在哪里了
因为是华为的错(或者华为某些人),但你嘲讽的不是华为
yigecaiji 127
yigecaiji 41 分钟前
明明我们在嘲讽华为,怎么有人直接上升到民族和国家了
yunshansimon 128
yunshansimon 38 分钟前
华为干得好,什么道德之类的,都是西方国家制定的标准,作为强大如华为者,作为中国的代表,就是要打破西方制定的道德标准和底线,坚持填充垃圾,就是不改。看到华为这么流氓,我们中国人民就放心了!希望广大爱国程序员也学习华为,为华为的项目多多贡献垃圾!为建设伟大华为贡献自己的力量!
ohwind 129
ohwind 36 分钟前 ❤️ 1
@Iserlohn44 一边要求别人「就事论事」,一边回避我的提问,一边对我进行攻击。想必双重标准不外如是吧?我想孰是孰非已有结论,我们两个的讨论就此终止,我也不愿意与你浪费电力。
ioriwong 130
ioriwong 33 分钟前
无耻则无敌
polo3584 131
polo3584 29 分钟前
本来说的是 leizheng 这个开发者,然后就上升到了华为,然后上升到了国家和民族,真有意思。
libook 132
libook 27 分钟前
开源社区也是一个项目团队。

从项目管理的角度上来说,几乎所有问题都可以通过约定规则、流程来解决,所以有问题应该讨论一下:
1. 问题是否实际存在?
2. 影响范围如何?
3. 可以约定什么样的规则、流程来解决和避免这个问题?

华为员工有目的不纯的动机,邮件发送者也有“言外之意”的嫌疑(毕竟邮件里只有无证据的指控,而缺乏建设性)。

如果后续邮件没有人出来按照一个项目问题的正常解决流程来协调解决的话,那么项目管理本身可能是这个社区目前*大的问题。
zchlwj 133
zchlwj 27 分钟前 ❤️ 1
@phh 先有华为后有天的话 我在你这里*次看到
Iserlohn44 134
Iserlohn44 26 分钟前
@ohwind 不好意思,并非回避问题,而是没有看到你的第二条回复。我的上一条回复是针对你的 118 号评论。
针对你的问题:
1. 当然不是;
2. 无意义的嘲讽不应当,有道理的批评当然是应当的;
3. 并不是。用无逻辑去回应无逻辑,不代表你就是对的。

我发现你似乎把指出你的发言问题等价成了认为华为这件事做的对,甚至延伸到了「对基本是非没有自己的判断」,你不觉得太自以为是了吗?
tt67wq 135
tt67wq 22 分钟前
国内公司做开源的态度都有点问题,为了 KPI 一哄而上,KPI 有了就不维护了
miniwade514 136
miniwade514 21 分钟前
建议送进本站的精华版块(水深火热)
manzhiyong 137
manzhiyong 12 分钟前
这个作者真是丑陋,辱骂未开源提供贡献的人。
fanlis 138
fanlis 10 分钟前 ❤️ 1
进来看到前几排,以为进入 4V 社区
喷华为可以,随便喷,不要扯着你的破脸阴阳怪气中国,搁谁看不出来?
nmecury 139
nmecury 3 分钟前
@hwdef 我是某大型项目的使用者,用的时候发现了隐蔽的严重 bug,又不会修,只好提了个 issue,被打了 high-priority 的 tag,然后在大版本更新里被修复了。能不能请您说说我怎么恶心了?啥时候开源社区出了您这么一号大人物?

初级后端的疑惑,如何估算接口 qps,以及 redis 占用多少容量, nginx 能抗多少并发

如题,参与公司业务开发中,经常会遇到这样的问题:

这个业务入口会为接口带来多少的 qps 增长?
这个接口能抗住多少 qps ?
这个业务要上缓存的话,预计会带来多少缓存占用?
现有的 redis 能抗多少并发?内存占用是否过高?是否需要增加机器?
现有的 nginx 集群,能抗住多少并发?是否需要增加机器?
业务上线预计会带来 1000qps 的增长,服务器资源(接口,缓存,数据库)是否能扛得住?
这个业务的性能瓶颈在哪里?怎么查出来? 等等
总结的问题就是,大佬们是如何进行业务的容量评估,性能评估,性能排查的?

希望能有大大能逐点解答一下上面的 7 个问题你们在工作中是怎么去分析的,身为菜鸟的我每次遇到这种问题,都头痛半天,然后还是去问大佬怎么怎么弄,但是几次下来也没有总结到套路,都快怀疑自己适不适合干下去了…. 所以想向各位请教下,学习一下大家都是怎么评估和排查问题的,想在这方面有点成长,万分感谢!

第 1 条附言 · 1 小时 30 分钟前
看到有这么多人默默收藏了,就说明了其实还是有很多人关心这些问题的,看到这个问题的大佬们,有经验的希望还是能尽量分享下呀~ 求求了
qps 并发 Nginx Redis22 条回复 • 2021-06-21 16:24:07 +08:00
tachikomachann 1
tachikomachann 3 小时 9 分钟前 via Android
之前看《 java performance definitive guide 》时,作者在*章说过一句话。大概意思是很多人以为 java 调优就是这样一个按钮,我按下去了调优就完成了。实际上不是的,一个服务的性能瓶颈需要具体问题具体分析,需要结合业务场景,压测结果,profile 工具等一点一点地去分析。然后根据自己需要的性能目标和成本去得出一个折中方案。

lz 可以先看看相关的书籍,学习用一些基本工具(大厂甚至都有一整套自己的工具)和大牛们做性能分析的思路。然后在到自己项目里实践看看。
whcoding 2
whcoding 3 小时 7 分钟前
可以买阿里云的压测得到你想要的数据.
waibunleung 3
waibunleung 3 小时 1 分钟前
@tachikomachann 我知道肯定会有人说具体问题具体分析,但是问题都是是有分析方向的,就比如你怎么估计一个在服务首页下面增加一个功能入口,新业务增加多少 qps 呢?如果你通过接口监控,知道了这个首页的平均 qps 是 1000,运营告诉你,页面底部的点击率是 25%,那预计带来的 qps 就大约是 250qps,这个 qps 不算高,接口逻辑不复杂的话就能轻松扛过去等等

上面说的 转化率就是一个评估 qps 的方向。
导致性能瓶颈的问题有很多,但是排查瓶颈肯定是有套路的

至于你说学习大牛们做性能分析的思路,提出这个问题的我,就是希望能再这里收获一点思路。

感谢回复啦~
waibunleung 4
waibunleung 3 小时 0 分钟前
@whcoding 公司自建机房

想要问题分析的思路和套路,阿里云并不能告诉我预计业务会增长多少 qps…
dream4ever 5
dream4ever 2 小时 2 分钟前
之前也思考过这个问题,在*客时间买了几门架构设计方面的课,有些课程会讲到这类数据,比如在硬件配置不是瓶颈的情况下,nginx 能扛住多少并发,redis 能扛住多少并发之类的数据,可以看看。
dream4ever 6
dream4ever 2 小时 2 分钟前
而且也会讲如何排查性能瓶颈的方法。
janxin 7
janxin 1 小时 42 分钟前
估算都不知道瓶颈在哪怎么估算 orz

所以先做压测找到瓶颈
jmtung 8
jmtung 1 小时 35 分钟前
1 、性能:压测
2 、redis 内存占用: http://www.redis.cn/redis_memory
waibunleung 9
waibunleung 1 小时 31 分钟前
@dream4ever 请问买的是哪几门课程呢?
chenqh 10
chenqh 1 小时 28 分钟前
能上 100QPS 都好事了,

waibunleung 11
waibunleung 1 小时 26 分钟前
@jmtung 卧槽,有这个 redis 的预估神器?它估算是怎么估算的?
Maboroshii 12
Maboroshii 1 小时 25 分钟前 via Android
如果不是那种用户量特别大的服务,就随便搞一个配置,先上线,再观察调整也来得及,就能慢慢积累经验了
waibunleung 13
waibunleung 1 小时 23 分钟前
@Maboroshii 就是那种用户量大的服务
zed1018 14
zed1018 1 小时 21 分钟前
jmeter 可以压测
Maboroshii 15
Maboroshii 1 小时 17 分钟前
@waibunleung #13 压测是一个方案,不过具体还是依赖运营数据和现有数据对比,再考虑配置问题。 总有一个人有经验,任何项目也不是一上线就用户量爆炸的。
iyaozhen 16
iyaozhen 1 小时 6 分钟前
这个问题比较复杂。
往往大家只是说做个压测,但压测*难的不是 jmeter 啥的使用,而是压测场景的分析。

这个之前内部写了个文档,需要再重新写个,可能能回答楼主部分问题
waibunleung 17
waibunleung 56 分钟前
@Maboroshii 压测确实是一个方案,但是肯定有分析技巧和套路的
X0ray 18
X0ray 44 分钟前
性能评估看压测,
性能排查要做好 metrics 监控,如果有异常了,一拉图标出来很快就能反映出实际情况。
fantastM 19
fantastM 43 分钟前
1 和 2 应该都是先由产品 /运营给出一个预估的用户量,然后通过应用当前的部署情况(比如负载均衡了多少台机器,单台机器的配置,应用运行时的配置,接口的响应时间)估算出接口的 QPS 。
3 sizeof 可以算占用量,不过和具体缓存的数据有关,#8 提到的网站就挺不错。
4 单机的话,可以用 redis-benchmark 跑下看看。
7 压测时候看下各个调用链路里的耗时(或者更细一点的,可以自己打印 log ),还有外部依赖的监控指标等等,出现问题的话,总能看出一些端倪。
dream4ever 20
dream4ever 35 分钟前
@waibunleung 《架构实战案例解析》,《许式伟的架构课》,《从 0 开始学架构》,但是上面说的内容具体在哪个专栏里现在印象不深了。
waibunleung 21
waibunleung 24 分钟前
@iyaozhen 大佬可以简单回答一下,然后再把写好的文档分享一下~
waibunleung 22
waibunleung 23 分钟前
@fantastM 太棒了,就是需要类似这样的回复!还有请问下,类似于 4k qps mysql 能不能扛得住,会不会报警这种问题,要怎么思考呢?

11. 盛*多水的容器(JS实现)

11. 盛*多水的容器(JS实现)

1 题目
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳*多的水。

2 思路
该题使用首尾双指针向中间靠拢的办法,逐步比较面积大小

3代码
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
var low = 0;
var high = height.length – 1;
var max = Math.min(height[low], height[high]) * (high – low);

while(low < high) {
if (height[low] < height[high]) {
low++;
} else {
high–;
}

max = Math.max(max, Math.min(height[low], height[high]) * (high – low));
}
return max;
};

15. 三数之和(JS实现)

15. 三数之和(JS实现)

1 题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

2 思路
这道题参考了题解,利用了排序 + 双指针 + 散列表去重,折腾了一晚上

3代码
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
var result = [];
var map = {};
var value;
var left, right;
var sum;
nums.sort(function(a,b){
return a-b;
});

if (nums[0] > 0 || nums[nums.length-1] < 0) return result;

for (var i=0; i<nums.length; i++){
left = i+1;
right = nums.length – 1;
while(left < right) {
sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
var key = ” + nums[i] + nums[left] + nums[right];
if (!map[key]) result.push([nums[i], nums[left], nums[right]]);
map[key] = true;
left++;
right–;
} else if (sum < 0){
left++;
} else {
right–;
}
}
}

return result;
};

16. *接近的三数之和(JS实现)

16. *接近的三数之和(JS实现)

1 题目
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target *接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target *接近的三个数的和为 2. (-1 + 2 + 1 = 2).

2 思路
这道题还是利用了排序 + 双指针的方法解决

3代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var threeSumClosest = function(nums, target) {
if (nums.length <= 3) {
return nums.reduce(function(prev, curr){
return prev + curr;
});
}
nums.sort(function(a, b){
return a-b;
})

var left,right;
var diff = target – nums[0] – nums[1] – nums[2];
var sum;

for (var i=0; i<nums.length; i++) {
left = i + 1;
right = nums.length – 1;
while(left < right) {
sum = nums[i] + nums[left] + nums[right]
if (Math.abs(diff) > Math.abs(target – sum)) { //注意这里对比的是*对值
diff = target – sum;
}
if (diff === 0) return target; //相加等于target,此时直接返回
if (target – sum > 0) {
left++;
} else {
right–;
}
}
}

return target – diff;
};

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速